From b90025d6bbf323c423078db5bde0c2ced2e8ce3b Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Wed, 21 May 2025 16:36:48 +0200 Subject: [PATCH 1/7] Split component interfaces & record implementations, document fields, split java vs geyser data components Also updates mappings, and introduces the GenericBuilder interface for convenience --- .../GeyserDefineCustomItemsEvent.java | 17 ++- .../item/custom/NonVanillaCustomItemData.java | 38 ++--- .../item/custom/v2/CustomItemDefinition.java | 142 ++++++++++++++++-- .../v2/NonVanillaCustomItemDefinition.java | 14 +- .../custom/v2/component/DataComponent.java | 120 +++------------ .../custom/v2/component/ToolProperties.java | 29 ---- .../v2/component/geyser/BlockPlacer.java | 101 +++++++++++++ .../v2/component/geyser/Chargeable.java | 119 +++++++++++++++ .../{ => geyser}/GeyserDataComponent.java | 26 +++- .../v2/component/{ => java}/Consumable.java | 76 +++++++++- .../custom/v2/component/java/Equippable.java | 99 ++++++++++++ .../v2/component/java/FoodProperties.java | 112 ++++++++++++++ .../v2/component/java/ItemDataComponents.java | 128 ++++++++++++++++ .../custom/v2/component/java/Repairable.java | 90 +++++++++++ .../v2/component/java/ToolProperties.java | 88 +++++++++++ .../custom/v2/component/java/UseCooldown.java | 100 ++++++++++++ .../api/predicate/MinecraftPredicate.java | 2 +- .../api/predicate/PredicateStrategy.java | 3 + .../GenericBuilder.java} | 15 +- .../geysermc/geyser/api/util/Identifier.java | 51 ++++++- .../GeyserDefineCustomItemsEventImpl.java | 2 +- .../geysermc/geyser/impl/IdentifierImpl.java | 13 ++ .../item/custom/ComponentConverters.java | 30 ++-- .../custom/GeyserCustomItemDefinition.java | 24 ++- .../GeyserNonVanillaCustomItemDefinition.java | 2 +- .../item/custom/impl/BlockPlacerImpl.java | 62 ++++++++ .../item/custom/impl/ChargeableImpl.java | 74 +++++++++ .../item/custom/impl/ConsumableImpl.java | 65 ++++++++ .../item/custom/impl/DataComponentImpl.java | 22 ++- .../item/custom/impl/EquippableImpl.java | 32 ++-- .../item/custom/impl/FoodPropertiesImpl.java | 69 +++++++++ .../item/custom/impl/RepairableImpl.java | 22 ++- .../item/custom/impl/ToolPropertiesImpl.java | 23 ++- .../item/custom/impl/UseCooldownImpl.java | 31 +++- .../loader/ProviderRegistryLoader.java | 44 +++++- .../components/DataComponentReaders.java | 8 +- .../components/readers/ConsumableReader.java | 9 +- .../components/readers/EnchantableReader.java | 4 +- .../components/readers/EquippableReader.java | 9 +- .../readers/FoodPropertiesReader.java | 9 +- .../components/readers/RepairableReader.java | 11 +- .../readers/ToolPropertiesReader.java | 9 +- .../components/readers/UseCooldownReader.java | 9 +- .../registry/mappings/util/NodeReader.java | 14 +- .../mappings/versions/MappingsReader_v2.java | 11 +- .../CustomItemRegistryPopulator.java | 49 +++--- .../populator/ItemRegistryPopulator.java | 9 +- .../geyser/session/GeyserSession.java | 4 +- .../geysermc/geyser/util/MinecraftKey.java | 13 +- core/src/main/resources/mappings | 2 +- 50 files changed, 1730 insertions(+), 325 deletions(-) delete mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/ToolProperties.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/geyser/BlockPlacer.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/geyser/Chargeable.java rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/{ => geyser}/GeyserDataComponent.java (72%) rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/{ => java}/Consumable.java (53%) create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/Equippable.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/FoodProperties.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/ItemDataComponents.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/Repairable.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/ToolProperties.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/UseCooldown.java rename api/src/main/java/org/geysermc/geyser/api/{item/custom/v2/component/BlockPlacer.java => util/GenericBuilder.java} (82%) create mode 100644 core/src/main/java/org/geysermc/geyser/item/custom/impl/BlockPlacerImpl.java create mode 100644 core/src/main/java/org/geysermc/geyser/item/custom/impl/ChargeableImpl.java create mode 100644 core/src/main/java/org/geysermc/geyser/item/custom/impl/ConsumableImpl.java rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/UseCooldown.java => core/src/main/java/org/geysermc/geyser/item/custom/impl/DataComponentImpl.java (76%) rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Equippable.java => core/src/main/java/org/geysermc/geyser/item/custom/impl/EquippableImpl.java (63%) create mode 100644 core/src/main/java/org/geysermc/geyser/item/custom/impl/FoodPropertiesImpl.java rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Chargeable.java => core/src/main/java/org/geysermc/geyser/item/custom/impl/RepairableImpl.java (70%) rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/FoodProperties.java => core/src/main/java/org/geysermc/geyser/item/custom/impl/ToolPropertiesImpl.java (64%) rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Repairable.java => core/src/main/java/org/geysermc/geyser/item/custom/impl/UseCooldownImpl.java (59%) 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..3503c473786 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..be591b656b3 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:

* * * *

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); + /** + * Sets the predicates that must match for Geyser to use this item definition. + * See {@link CustomItemDefinition#predicates()} for details. + * + * @param predicate the predicates that must match for this item to be used + * @return this builder + */ + @This Builder predicate(@NonNull MinecraftPredicate 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/DataComponent.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java index 70e84aca00c..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,122 +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. - * - * @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/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..9a37685879f --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/geyser/BlockPlacer.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.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. + * + * @return whether to use the 3d block rendering for the + * item icon + */ + boolean useBlockIcon(); + + /** + * Creates a builder for the BlockPlacer component. + * + * @return a new builder + */ + static Builder builder() { + return GeyserApi.api().provider(BlockPlacer.Builder.class); + } + + /** + * Builder for the BlockPlacer 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 identifier the identifier of the block + * @return this builder + */ + @This + Builder block(@NonNull Identifier identifier); + + /** + * 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 + * @return this builder + */ + @This + Builder useBlockIcon(boolean useBlockIcon); + + /** + * Creates the BlockPlacer 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..e853fa1497d --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/geyser/Chargeable.java @@ -0,0 +1,119 @@ +/* + * 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; + +// 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. + * + * @return the maximum draw duration + */ + @NonNegative float maxDrawDuration(); + + /** + * Whether the item is being charged when being drawn. + * + * @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 + */ + Identifier @NonNull [] 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 + * @return this builder + */ + @This + Builder maxDrawDuration(@NonNegative float maxDrawDuration); + + /** + * Sets whether the item is charged when drawing. + * + * @param chargeOnDraw whether drawing charges the item + * @return this builder + */ + @This + Builder chargeOnDraw(boolean chargeOnDraw); + + /** + * Sets the valid items that can be used as ammunition. + * This should include all valid items, such as {@code minecraft:arrow}. + * + * @param ammunition the Bedrock item identifiers of possible ammunition + * @return this builder + */ + @This + Builder ammunition(@NonNull Identifier @NonNull... 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 72% 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..8147eb5bc82 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 @@ -39,21 +44,32 @@ public final class 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"); + public static final 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); + public static final 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"); + public static final DataComponent BLOCK_PLACER = createGeyser("block_placer"); + + static DataComponent createGeyser(String id) { + return createGeyser(id, t -> true); + } + + static DataComponent createGeyser(String id, Predicate predicate) { + return GeyserApi.api().provider(DataComponent.class, Identifier.of("geysermc", id), predicate, false); + } private GeyserDataComponent() {} } 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/java/Consumable.java similarity index 53% rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java rename to api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/Consumable.java index 561b2dc7c42..e03be998c0a 100644 --- 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/java/Consumable.java @@ -23,22 +23,48 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.item.custom.v2.component; +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; -public record Consumable(@Positive float consumeSeconds, Animation animation) { +/** + * 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. + * + * @return the consume duration, in seconds + */ + @Positive float consumeSeconds(); - public Consumable { - if (consumeSeconds <= 0.0F) { - throw new IllegalArgumentException("Consume seconds must be above 0"); - } + /** + * The animation that should play when consuming the item. + * @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); } /** * 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 { + enum Animation { /** * Does nothing in 1st person, appears as eating in 3rd person. */ @@ -74,6 +100,40 @@ public enum Animation { /** * Brush in 1st and 3rd person. Will look weird when not displayed handheld. */ - BRUSH + 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 + * @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 + * @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..fde1ce61f2c --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/Equippable.java @@ -0,0 +1,99 @@ +/* + * 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 a 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 + * @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..ebcab6408ba --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/FoodProperties.java @@ -0,0 +1,112 @@ +/* + * 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 create + * edible items. This includes setting the nutrition, + * saturation and whether an item can be always eaten. + */ +public interface FoodProperties { + + /** + * The nutrition of the item. + * + * @return the nutrition + */ + @NonNegative int nutrition(); + + /** + * The saturation of the item. + * + * @return the saturation + */ + @NonNegative float saturation(); + + /** + * Whether this item can be always eaten, + * even when not hungry. In vanilla, this would + * include items such as golden apples. + * + * @return whether an item can be always 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); + } + + /** + * 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. + * @return this builder + */ + @This + Builder nutrition(@NonNegative int nutrition); + + /** + * Sets the saturation of the item + * @param saturation the saturation of the ite + * @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 + * @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..046f2db37fc --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/ItemDataComponents.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.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 class 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 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. + * + * @see UseCooldown + */ + public static final 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.

+ */ + public static final DataComponent ENCHANTABLE = create("enchantable", i -> i >= 0); + + /** + * This component is only used for the {@link ToolProperties#canDestroyBlocksInCreative()} option. + * + *

Like other components, when not set this will fall back to the default value.

+ * + * @see ToolProperties + */ + public static final DataComponent TOOL = create("tool"); + + /** + * Marks which items can be used to repair the item. + * + * @see Repairable + */ + public static final DataComponent REPAIRABLE = create("repairable"); + + /** + * Overrides the item's enchantment glint. + */ + public static final DataComponent ENCHANTMENT_GLINT_OVERRIDE = create("enchantment_glint_override"); + + static DataComponent create(String id) { + return create(id, t -> true); + } + + 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..6fd293432a7 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/Repairable.java @@ -0,0 +1,90 @@ +/* + * 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; + +/** + * 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 + */ + Identifier @NonNull [] 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) { + return builder().items(items).build(); + } + + /** + * Builder for the repairable component. + */ + interface Builder extends GenericBuilder { + + /** + * Sets which item(s) can be used to repair the item. + * + * @param identifier the Bedrock item identifier that can be used to repair the item + * @return this builder + */ + @This + Builder items(Identifier[] identifier); + + /** + * 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..97be1ed179c --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/ToolProperties.java @@ -0,0 +1,88 @@ +/* + * 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 + * which items can destroy blocks when used in creative mode. + */ +public interface ToolProperties { + + /** + * Whether this item can destroy blocks when clicking in + * creative mode. + * + * @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 instantly + * while in creative mode. + * + * @param canDestroyBlocksInCreative determines if the item will break blocks in creative mode + * @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..5af50760330 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/UseCooldown.java @@ -0,0 +1,100 @@ +/* + * 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. + * + * @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 + * @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()} + * + * @param cooldownGroup the cooldown group identifier + * @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..7cd4373903d 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,53 @@ package org.geysermc.geyser.api.util; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.GeyserApi; +/** + * An identifying object for getting and/or storing 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: + *
    + *
  • minecraft:fox
  • + *
  • 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 +88,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 53842b8a089..0cda81bee7d 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; @@ -51,7 +53,7 @@ // First of all, Java generics are a bit limited :( // Second, the API module has its own set of component classes, because MCPL can't be used in there. // However, those component classes have the same names as the MCPL ones, which causes some issues when they both have to be used in the same file. -// One can't be imported, and as such its full qualifier (e.g. org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable) would have to be used. +// One can't be imported, and as such its full qualifier (e.g. Consumable) would have to be used. // That would be a mess to code in, and as such this code here was carefully designed to only require one set of component classes by name (the MCPL ones). // // It is VERY IMPORTANT to note that for every component in the API, a converter to MCPL must be put here (there are some exceptions as noted in the Javadoc, better solutions are welcome). @@ -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)); }); - 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..bca1842cf29 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 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(component, "component 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..f73ca67167e --- /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 identifier) { + Objects.requireNonNull(identifier, "identifier cannot be null"); + this.block = identifier; + 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..0c6e95b33b1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/custom/impl/ChargeableImpl.java @@ -0,0 +1,74 @@ +/* + * 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.Objects; + +public record ChargeableImpl( + @NonNegative float maxDrawDuration, + boolean chargeOnDraw, + @NonNull Identifier @NonNull [] ammunition +) implements Chargeable { + + public static class Builder implements Chargeable.Builder { + float maxDrawDuration; + boolean chargeOnDraw; + Identifier[] ammunition; + + @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 @NonNull... ammunition) { + Objects.requireNonNull(ammunition, "ammunition"); + this.ammunition = ammunition; + return this; + } + + @Override + public Chargeable build() { + Objects.requireNonNull(ammunition, "ammunition"); + 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..6dc2ec0617c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/custom/impl/ConsumableImpl.java @@ -0,0 +1,65 @@ +/* + * 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 { + float consumeSeconds; + Animation animation; + + @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() { + Objects.requireNonNull(animation, "Animation cannot be null"); + return new ConsumableImpl(consumeSeconds, animation); + } + } +} 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/DataComponentImpl.java similarity index 76% 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/DataComponentImpl.java index 05e01a412f3..d735b4a68eb 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/DataComponentImpl.java @@ -23,16 +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.checkerframework.checker.index.qual.Positive; +import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; import org.geysermc.geyser.api.util.Identifier; -public record UseCooldown(@Positive float seconds, Identifier cooldownGroup) { - - public UseCooldown { - if (seconds <= 0.0F) { - throw new IllegalArgumentException("Cooldown seconds must be above 0"); - } +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/Equippable.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/EquippableImpl.java similarity index 63% rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Equippable.java rename to core/src/main/java/org/geysermc/geyser/item/custom/impl/EquippableImpl.java index b0589fd77ce..0d084ba6500 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Equippable.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/impl/EquippableImpl.java @@ -23,16 +23,30 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.item.custom.v2.component; +package org.geysermc.geyser.item.custom.impl; -public record Equippable(EquipmentSlot slot) { +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.item.custom.v2.component.java.Equippable; - public enum EquipmentSlot { - HEAD, - CHEST, - LEGS, - FEET, - BODY, - SADDLE +import java.util.Objects; + +public record EquippableImpl( + EquipmentSlot slot +) implements Equippable { + + public static class Builder implements Equippable.Builder { + 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() { + 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/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/RepairableImpl.java similarity index 70% 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/RepairableImpl.java index dedcd1b0fb5..66c1c51dc6a 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/RepairableImpl.java @@ -23,17 +23,25 @@ * @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.item.custom.v2.component.java.Repairable; import org.geysermc.geyser.api.util.Identifier; -// TODO projectile component -public record Chargeable(@NonNegative float maxDrawDuration, boolean chargeOnDraw, Identifier... ammunition) { +public record RepairableImpl(Identifier... items) implements Repairable { - public Chargeable { - if (maxDrawDuration < 0.0F) { - throw new IllegalArgumentException("Max draw duration must be at or above 0"); + public static class Builder implements Repairable.Builder { + Identifier[] items; + + @Override + public Builder items(Identifier[] items) { + this.items = items; + 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/FoodProperties.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/ToolPropertiesImpl.java similarity index 64% 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/ToolPropertiesImpl.java index aa1229fa230..555cb4c12e7 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/ToolPropertiesImpl.java @@ -23,15 +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.item.custom.v2.component.java.ToolProperties; -public record FoodProperties(@NonNegative int nutrition, @NonNegative float saturation, boolean canAlwaysEat) { +public record ToolPropertiesImpl( + boolean canDestroyBlocksInCreative +) implements ToolProperties { - public FoodProperties { - if (nutrition < 0 || saturation < 0.0F) { - throw new IllegalArgumentException("Nutrition and saturation must be at or above 0"); + public static class Builder implements ToolProperties.Builder { + boolean destroyBlocksInCreative; + + @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/Repairable.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/UseCooldownImpl.java similarity index 59% 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/UseCooldownImpl.java index 9a7c01d0696..9467f745bb0 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/UseCooldownImpl.java @@ -23,9 +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.nullness.qual.Nullable; +import org.geysermc.geyser.api.item.custom.v2.component.java.UseCooldown; import org.geysermc.geyser.api.util.Identifier; -public record Repairable(Identifier... items) { +public record UseCooldownImpl( + float seconds, + @Nullable Identifier cooldownGroup +) implements UseCooldown { + + public static class Builder implements UseCooldown.Builder { + Identifier cooldownGroup; + 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, Prov @Override public Map, ProviderSupplier> load(Map, ProviderSupplier> providers) { // misc - providers.put(Identifier.class, args -> new IdentifierImpl(Key.key((String) args[0], (String) args[1]))); + providers.put(Identifier.class, args -> IdentifierImpl.of((String) args[0], (String) args[1])); // commands providers.put(Command.Builder.class, args -> new GeyserExtensionCommand.Builder<>((Extension) args[0])); @@ -116,10 +134,28 @@ public Map, ProviderSupplier> load(Map, ProviderSupplier> prov providers.put(NonVanillaCustomItemDefinition.Builder.class, args -> new GeyserNonVanillaCustomItemDefinition.Builder((Identifier) args[0], (Identifier) args[1], (int) args[2])); providers.put(CustomItemBedrockOptions.Builder.class, args -> new GeyserCustomItemBedrockOptions.Builder()); + providers.put(DataComponent.class, args -> dataComponentProvider((Identifier) args[1], (Predicate) args[2], (Boolean) args[3])); + + // item components + providers.put(Consumable.Builder.class, args -> new ConsumableImpl.Builder()); + providers.put(Equippable.Builder.class, args -> new EquippableImpl.Builder()); + providers.put(FoodProperties.Builder.class, args -> new FoodPropertiesImpl.Builder()); + providers.put(Repairable.Builder.class, args -> new RepairableImpl.Builder()); + providers.put(ToolProperties.Builder.class, args -> new ToolPropertiesImpl.Builder()); + providers.put(UseCooldown.Builder.class, args -> new UseCooldownImpl.Builder()); + + // geyser components + providers.put(Chargeable.Builder.class, args -> new ChargeableImpl.Builder()); + providers.put(BlockPlacer.Builder.class, args -> new BlockPlacerImpl.Builder()); + // cameras providers.put(CameraFade.Builder.class, args -> new GeyserCameraFade.Builder()); providers.put(CameraPosition.Builder.class, args -> new GeyserCameraPosition.Builder()); return providers; } + + public DataComponentImpl dataComponentProvider(Identifier identifier, Predicate predicate, boolean vanilla) { + return new DataComponentImpl<>(identifier, predicate, vanilla); + } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java index d225828eb5c..d7dde8ae490 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/DataComponentReaders.java @@ -29,7 +29,7 @@ import net.kyori.adventure.key.Key; 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.DataComponent; +import org.geysermc.geyser.api.item.custom.v2.component.java.ItemDataComponents; import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.readers.BooleanComponentReader; @@ -67,12 +67,12 @@ public static void readDataComponent(CustomItemDefinition.Builder builder, Strin READERS.put(MinecraftKey.key("consumable"), new ConsumableReader()); READERS.put(MinecraftKey.key("equippable"), new EquippableReader()); READERS.put(MinecraftKey.key("food"), new FoodPropertiesReader()); - READERS.put(MinecraftKey.key("max_damage"), new IntComponentReader(DataComponent.MAX_DAMAGE, 0)); - READERS.put(MinecraftKey.key("max_stack_size"), new IntComponentReader(DataComponent.MAX_STACK_SIZE, 1, 99)); + READERS.put(MinecraftKey.key("max_damage"), new IntComponentReader(ItemDataComponents.MAX_DAMAGE, 0)); + READERS.put(MinecraftKey.key("max_stack_size"), new IntComponentReader(ItemDataComponents.MAX_STACK_SIZE, 1, 99)); READERS.put(MinecraftKey.key("use_cooldown"), new UseCooldownReader()); READERS.put(MinecraftKey.key("enchantable"), new EnchantableReader()); READERS.put(MinecraftKey.key("tool"), new ToolPropertiesReader()); READERS.put(MinecraftKey.key("repairable"), new RepairableReader()); - READERS.put(MinecraftKey.key("enchantment_glint_override"), new BooleanComponentReader(DataComponent.ENCHANTMENT_GLINT_OVERRIDE)); + READERS.put(MinecraftKey.key("enchantment_glint_override"), new BooleanComponentReader(ItemDataComponents.ENCHANTMENT_GLINT_OVERRIDE)); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java index 077094d7e23..a47b1809f2d 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ConsumableReader.java @@ -27,8 +27,9 @@ import com.google.gson.JsonElement; import org.checkerframework.checker.nullness.qual.NonNull; -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.java.Consumable; +import org.geysermc.geyser.api.item.custom.v2.component.java.ItemDataComponents; +import org.geysermc.geyser.item.custom.impl.ConsumableImpl; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; import org.geysermc.geyser.registry.mappings.util.MappingsUtil; @@ -37,7 +38,7 @@ public class ConsumableReader extends DataComponentReader { public ConsumableReader() { - super(DataComponent.CONSUMABLE); + super(ItemDataComponents.CONSUMABLE); } @Override @@ -45,6 +46,6 @@ protected Consumable readDataComponent(@NonNull JsonElement element, String... c float consumeSeconds = MappingsUtil.readOrDefault(element, "consume_seconds", NodeReader.POSITIVE_DOUBLE.andThen(Double::floatValue), 1.6F, context); Consumable.Animation animation = MappingsUtil.readOrDefault(element, "animation", NodeReader.CONSUMABLE_ANIMATION, Consumable.Animation.EAT, context); - return new Consumable(consumeSeconds, animation); + return new ConsumableImpl(consumeSeconds, animation); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EnchantableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EnchantableReader.java index ed6b05108fd..ef0e9bfd803 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EnchantableReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EnchantableReader.java @@ -28,7 +28,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonPrimitive; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; +import org.geysermc.geyser.api.item.custom.v2.component.java.ItemDataComponents; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; import org.geysermc.geyser.registry.mappings.util.MappingsUtil; @@ -62,7 +62,7 @@ public class EnchantableReader extends DataComponentReader { public EnchantableReader() { - super(DataComponent.ENCHANTABLE); + super(ItemDataComponents.ENCHANTABLE); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java index cfdf2cac029..06287d9e712 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/EquippableReader.java @@ -27,8 +27,9 @@ import com.google.gson.JsonElement; import org.checkerframework.checker.nullness.qual.NonNull; -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.java.Equippable; +import org.geysermc.geyser.api.item.custom.v2.component.java.ItemDataComponents; +import org.geysermc.geyser.item.custom.impl.EquippableImpl; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; import org.geysermc.geyser.registry.mappings.util.MappingsUtil; @@ -37,12 +38,12 @@ public class EquippableReader extends DataComponentReader { public EquippableReader() { - super(DataComponent.EQUIPPABLE); + super(ItemDataComponents.EQUIPPABLE); } @Override protected Equippable readDataComponent(@NonNull JsonElement element, String... context) throws InvalidCustomMappingsFileException { Equippable.EquipmentSlot slot = MappingsUtil.readOrThrow(element, "slot", NodeReader.EQUIPMENT_SLOT, context); - return new Equippable(slot); + return new EquippableImpl(slot); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java index e07c41a43ec..b5f59b8fce3 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/FoodPropertiesReader.java @@ -27,8 +27,9 @@ import com.google.gson.JsonElement; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; -import org.geysermc.geyser.api.item.custom.v2.component.FoodProperties; +import org.geysermc.geyser.api.item.custom.v2.component.java.FoodProperties; +import org.geysermc.geyser.api.item.custom.v2.component.java.ItemDataComponents; +import org.geysermc.geyser.item.custom.impl.FoodPropertiesImpl; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; import org.geysermc.geyser.registry.mappings.util.MappingsUtil; @@ -37,7 +38,7 @@ public class FoodPropertiesReader extends DataComponentReader { public FoodPropertiesReader() { - super(DataComponent.FOOD); + super(ItemDataComponents.FOOD); } @Override @@ -46,6 +47,6 @@ protected FoodProperties readDataComponent(@NonNull JsonElement element, String. float saturation = MappingsUtil.readOrDefault(element, "saturation", NodeReader.NON_NEGATIVE_DOUBLE.andThen(Double::floatValue), 0.0F, context); boolean canAlwaysEat = MappingsUtil.readOrDefault(element, "can_always_eat", NodeReader.BOOLEAN, false, context); - return new FoodProperties(nutrition, saturation, canAlwaysEat); + return new FoodPropertiesImpl(nutrition, saturation, canAlwaysEat); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/RepairableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/RepairableReader.java index 79ee41d64d0..971774d5c8a 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/RepairableReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/RepairableReader.java @@ -27,9 +27,10 @@ import com.google.gson.JsonElement; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; -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.item.custom.impl.RepairableImpl; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; import org.geysermc.geyser.registry.mappings.util.MappingsUtil; @@ -40,17 +41,17 @@ public class RepairableReader extends DataComponentReader { public RepairableReader() { - super(DataComponent.REPAIRABLE); + super(ItemDataComponents.REPAIRABLE); } @Override protected Repairable readDataComponent(@NonNull JsonElement node, String... context) throws InvalidCustomMappingsFileException { try { Identifier item = MappingsUtil.readOrThrow(node, "items", NodeReader.IDENTIFIER, context); - return new Repairable(item); + return new RepairableImpl(item); } catch (InvalidCustomMappingsFileException exception) { List items = MappingsUtil.readArrayOrThrow(node, "items", NodeReader.IDENTIFIER, context); - return new Repairable(items.toArray(Identifier[]::new)); + return new RepairableImpl(items.toArray(Identifier[]::new)); } } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ToolPropertiesReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ToolPropertiesReader.java index 3c8a08f8707..815a949d43e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ToolPropertiesReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/ToolPropertiesReader.java @@ -27,8 +27,9 @@ import com.google.gson.JsonElement; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; -import org.geysermc.geyser.api.item.custom.v2.component.ToolProperties; +import org.geysermc.geyser.api.item.custom.v2.component.java.ItemDataComponents; +import org.geysermc.geyser.api.item.custom.v2.component.java.ToolProperties; +import org.geysermc.geyser.item.custom.impl.ToolPropertiesImpl; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; import org.geysermc.geyser.registry.mappings.util.MappingsUtil; @@ -37,11 +38,11 @@ public class ToolPropertiesReader extends DataComponentReader { public ToolPropertiesReader() { - super(DataComponent.TOOL); + super(ItemDataComponents.TOOL); } @Override protected ToolProperties readDataComponent(@NonNull JsonElement element, String... context) throws InvalidCustomMappingsFileException { - return new ToolProperties(MappingsUtil.readOrDefault(element, "can_destroy_blocks_in_creative", NodeReader.BOOLEAN, true, context)); + return new ToolPropertiesImpl(MappingsUtil.readOrDefault(element, "can_destroy_blocks_in_creative", NodeReader.BOOLEAN, true, context)); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java index 85197b4ffa2..7a2976d263c 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/UseCooldownReader.java @@ -27,9 +27,10 @@ import com.google.gson.JsonElement; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.item.custom.v2.component.DataComponent; -import org.geysermc.geyser.api.item.custom.v2.component.UseCooldown; +import org.geysermc.geyser.api.item.custom.v2.component.java.ItemDataComponents; +import org.geysermc.geyser.api.item.custom.v2.component.java.UseCooldown; import org.geysermc.geyser.api.util.Identifier; +import org.geysermc.geyser.item.custom.impl.UseCooldownImpl; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReader; import org.geysermc.geyser.registry.mappings.util.MappingsUtil; @@ -38,7 +39,7 @@ public class UseCooldownReader extends DataComponentReader { public UseCooldownReader() { - super(DataComponent.USE_COOLDOWN); + super(ItemDataComponents.USE_COOLDOWN); } @Override @@ -46,6 +47,6 @@ protected UseCooldown readDataComponent(@NonNull JsonElement element, String... float seconds = MappingsUtil.readOrThrow(element, "seconds", NodeReader.POSITIVE_DOUBLE.andThen(Double::floatValue), context); Identifier cooldownGroup = MappingsUtil.readOrThrow(element, "cooldown_group", NodeReader.IDENTIFIER, context); - return new UseCooldown(seconds, cooldownGroup); + return new UseCooldownImpl(seconds, cooldownGroup); } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java index 8c07dfcf23d..5adc61b744f 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/util/NodeReader.java @@ -26,8 +26,9 @@ package org.geysermc.geyser.registry.mappings.util; import com.google.gson.JsonPrimitive; -import org.geysermc.geyser.api.item.custom.v2.component.Consumable; -import org.geysermc.geyser.api.item.custom.v2.component.Equippable; +import org.geysermc.geyser.Constants; +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.predicate.PredicateStrategy; import org.geysermc.geyser.api.predicate.context.item.ChargedProjectile; import org.geysermc.geyser.api.util.CreativeCategory; @@ -90,6 +91,15 @@ public interface NodeReader { NodeReader IDENTIFIER = NON_EMPTY_STRING.andThen(Identifier::of); + NodeReader GEYSER_IDENTIFIER = NON_EMPTY_STRING.validate(s -> !s.startsWith("minecraft:"), "namespace cannot be minecraft") + .andThen(Identifier::of) + .andThen(identifier -> { + if (identifier.namespace().equals(Identifier.DEFAULT_NAMESPACE)) { + return Identifier.of(Constants.GEYSER_CUSTOM_NAMESPACE, identifier.path()); + } + return identifier; + }); + NodeReader CREATIVE_CATEGORY = NON_EMPTY_STRING.andThen(CreativeCategory::fromName).validate(Objects::nonNull, "unknown creative category"); NodeReader PREDICATE_STRATEGY = ofEnum(PredicateStrategy.class); diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index 0b426d16606..9cd8db2be70 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -28,15 +28,14 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions; import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition; import org.geysermc.geyser.api.predicate.MinecraftPredicate; import org.geysermc.geyser.api.predicate.context.item.ItemPredicateContext; import org.geysermc.geyser.api.util.Identifier; +import org.geysermc.geyser.item.custom.GeyserCustomItemDefinition; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReaders; import org.geysermc.geyser.registry.mappings.predicate.ItemConditionProperty; @@ -45,7 +44,6 @@ import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping; import org.geysermc.geyser.registry.mappings.util.MappingsUtil; import org.geysermc.geyser.registry.mappings.util.NodeReader; -import org.geysermc.geyser.util.MinecraftKey; import java.nio.file.Path; import java.util.HashSet; @@ -119,7 +117,7 @@ private void readItemDefinitionEntry(JsonElement data, Identifier itemIdentifier @Override public CustomItemDefinition readItemMappingEntry(Identifier parentModel, JsonElement element) throws InvalidCustomMappingsFileException { - Identifier bedrockIdentifier = MappingsUtil.readOrThrow(element, "bedrock_identifier", NodeReader.IDENTIFIER, "item definition"); + Identifier bedrockIdentifier = MappingsUtil.readOrThrow(element, "bedrock_identifier", NodeReader.GEYSER_IDENTIFIER, "item definition"); // We now know the Bedrock identifier, make a base context so that the error can be easily located in the JSON file String context = "item definition (bedrock identifier=" + bedrockIdentifier + ")"; @@ -129,10 +127,7 @@ public CustomItemDefinition readItemMappingEntry(Identifier parentModel, JsonEle throw new InvalidCustomMappingsFileException("reading item model", "no model present", context); } - if (bedrockIdentifier.namespace().equals(Key.MINECRAFT_NAMESPACE)) { - bedrockIdentifier = Identifier.of(Constants.GEYSER_CUSTOM_NAMESPACE, bedrockIdentifier.path()); // Use geyser_custom namespace when no namespace or the minecraft namespace was given - } - CustomItemDefinition.Builder builder = CustomItemDefinition.builder(bedrockIdentifier, model); + CustomItemDefinition.Builder builder = new GeyserCustomItemDefinition.Builder(bedrockIdentifier, model); MappingsUtil.readIfPresent(element, "display_name", builder::displayName, NodeReader.NON_EMPTY_STRING, context); MappingsUtil.readIfPresent(element, "priority", builder::priority, NodeReader.INT, context); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index d9722404d70..066ae5db36b 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -39,11 +39,11 @@ 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.DataComponent; -import org.geysermc.geyser.api.item.custom.v2.component.GeyserDataComponent; -import org.geysermc.geyser.api.item.custom.v2.component.Repairable; +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.geyser.GeyserDataComponent; +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.predicate.MinecraftPredicate; import org.geysermc.geyser.api.predicate.context.item.ItemPredicateContext; import org.geysermc.geyser.api.predicate.item.ItemConditionPredicate; @@ -61,7 +61,6 @@ import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.NonVanillaItemRegistration; import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable; -import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable; import org.geysermc.mcprotocollib.protocol.data.game.item.component.FoodProperties; @@ -240,16 +239,16 @@ private static String checkPredicate(Map.Entry */ private static DataComponents checkComponents(CustomItemDefinition definition, Item javaItem) throws InvalidItemComponentsException { DataComponents components = patchDataComponents(javaItem, definition); - int stackSize = components.getOrDefault(DataComponentTypes.MAX_STACK_SIZE, 0); - int maxDamage = components.getOrDefault(DataComponentTypes.MAX_DAMAGE, 0); + int stackSize = components.getOrDefault(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.MAX_STACK_SIZE, 0); + int maxDamage = components.getOrDefault(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.MAX_DAMAGE, 0); - if (components.get(DataComponentTypes.EQUIPPABLE) != null && stackSize > 1) { + if (components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.EQUIPPABLE) != null && stackSize > 1) { throw new InvalidItemComponentsException("Bedrock doesn't support equippable items with a stack size above 1"); } else if (stackSize > 1 && maxDamage > 0) { throw new InvalidItemComponentsException("Stack size must be 1 when max damage is above 0"); } - Repairable repairable = definition.components().get(DataComponent.REPAIRABLE); + Repairable repairable = definition.components().get(ItemDataComponents.REPAIRABLE); if (repairable != null) { for (Identifier item : repairable.items()) { if (Registries.JAVA_ITEM_IDENTIFIERS.get(item.toString()) == null) { @@ -281,27 +280,27 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD .build()); } - ToolData toolData = components.get(DataComponentTypes.TOOL); + ToolData toolData = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.TOOL); boolean canDestroyInCreative = toolData == null || toolData.isCanDestroyBlocksInCreative(); computeCreativeDestroyProperties(canDestroyInCreative, itemProperties, componentBuilder); // Using API component here because MCPL one is just an ID holder set - Repairable repairable = customItemDefinition.components().get(DataComponent.REPAIRABLE); + Repairable repairable = customItemDefinition.components().get(ItemDataComponents.REPAIRABLE); if (repairable != null) { computeRepairableProperties(repairable, componentBuilder); } - Equippable equippable = components.get(DataComponentTypes.EQUIPPABLE); + Equippable equippable = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.EQUIPPABLE); if (equippable != null) { computeArmorProperties(equippable, customItemDefinition.bedrockOptions().protectionValue(), componentBuilder); } - Integer enchantmentValue = components.get(DataComponentTypes.ENCHANTABLE); + Integer enchantmentValue = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.ENCHANTABLE); if (enchantmentValue != null) { computeEnchantableProperties(enchantmentValue, itemProperties, componentBuilder); } - Boolean glint = components.get(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE); + Boolean glint = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE); if (glint != null) { itemProperties.putBoolean("foil", glint); componentBuilder.putCompound("minecraft:glint", NbtMap.builder() @@ -309,13 +308,13 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD .build()); } - Consumable consumable = components.get(DataComponentTypes.CONSUMABLE); + Consumable consumable = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.CONSUMABLE); if (consumable != null) { - FoodProperties foodProperties = components.get(DataComponentTypes.FOOD); + FoodProperties foodProperties = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.FOOD); computeConsumableProperties(consumable, foodProperties, itemProperties, componentBuilder); } - UseCooldown useCooldown = components.get(DataComponentTypes.USE_COOLDOWN); + UseCooldown useCooldown = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.USE_COOLDOWN); if (useCooldown != null) { computeUseCooldownProperties(useCooldown, componentBuilder); } @@ -323,9 +322,9 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD BlockPlacer blockPlacer = vanillaMapping.map(mapping -> { String bedrockIdentifier = mapping.getBedrockIdentifier(); if (bedrockIdentifier.equals("minecraft:fire_charge") || bedrockIdentifier.equals("minecraft:flint_and_steel")) { - return new BlockPlacer(Identifier.of("fire"), false); + return BlockPlacer.builder().block(Identifier.of("fire")).build(); } else if (mapping.getFirstBlockRuntimeId() != null) { - return new BlockPlacer(Identifier.of(mapping.getBedrockIdentifier()), false); + return BlockPlacer.builder().block(Identifier.of(mapping.getBedrockIdentifier())).build(); } return null; }).orElse(customItemDefinition.components().get(GeyserDataComponent.BLOCK_PLACER)); @@ -335,8 +334,8 @@ private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemD } Chargeable chargeable = vanillaMapping.map(GeyserMappingItem::getBedrockIdentifier).map(identifier -> switch (identifier) { - case "minecraft:bow" -> new Chargeable(1.0F, false, Identifier.of("arrow")); - case "minecraft:crossbow" -> new Chargeable(0.0F, true, Identifier.of("arrow")); + case "minecraft:bow" -> Chargeable.builder().maxDrawDuration(1.0F).ammunition(Identifier.of("arrow")).build(); + case "minecraft:crossbow" -> Chargeable.builder().chargeOnDraw(true).ammunition(Identifier.of("arrow")).build(); default -> null; }).orElse(customItemDefinition.components().get(GeyserDataComponent.CHARGEABLE)); @@ -397,10 +396,10 @@ private static void setupBasicItemInfo(CustomItemDefinition definition, DataComp itemProperties.putBoolean("allow_off_hand", options.allowOffhand()); itemProperties.putBoolean("hand_equipped", options.displayHandheld()); - int maxDamage = components.getOrDefault(DataComponentTypes.MAX_DAMAGE, 0); - Equippable equippable = components.get(DataComponentTypes.EQUIPPABLE); + int maxDamage = components.getOrDefault(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.MAX_DAMAGE, 0); + Equippable equippable = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.EQUIPPABLE); // Java requires stack size to be 1 when max damage is above 0, and bedrock requires stack size to be 1 when the item can be equipped - int stackSize = maxDamage > 0 || equippable != null ? 1 : components.getOrDefault(DataComponentTypes.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla ones + int stackSize = maxDamage > 0 || equippable != null ? 1 : components.getOrDefault(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla ones itemProperties.putInt("max_stack_size", stackSize); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index 5d3bef5df1c..82eaed76c66 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -92,7 +92,6 @@ import java.lang.reflect.Method; import java.lang.reflect.RecordComponent; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -162,7 +161,7 @@ public static void populate() { throw new AssertionError("Unable to load Bedrock item components", e); } - boolean customItemsAllowed = GeyserImpl.getInstance().getConfig().isAddNonBedrockItems(); + boolean customItemsAllowed = bootstrap.getGeyserConfig().isAddNonBedrockItems(); Multimap customItems = MultimapBuilder.hashKeys().arrayListValues().build(); Multimap nonVanillaCustomItems = MultimapBuilder.hashKeys().arrayListValues().build(); @@ -679,7 +678,11 @@ public static void populate() { .creativeItems(creativeItems) .creativeItemGroups(creativeItemGroups) .itemDefinitions(registry) - .componentItemData(componentItemData) + // Can be removed when we drop 1.21.50 + .componentItemData(registry.values() + .stream() + .filter(itemDefinition -> !itemDefinition.getIdentifier().startsWith("minecraft:")) + .toList()) .storedItems(new StoredItemMappings(javaItemToMapping)) .javaOnlyItems(javaOnlyItems) .buckets(buckets) diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 867db2d5629..cef68ede6be 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1353,8 +1353,8 @@ public boolean hasFishingRodCast() { */ public void setFishingRodCast(boolean cast) { this.hasFishingRodCast = cast; - InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateSlot(this, playerInventoryHolder.inventory(), - playerInventoryHolder.inventory().getOffsetForHotbar(playerInventoryHolder.inventory().getHeldItemSlot())); + int slot = getPlayerInventory().getOffsetForHotbar(getPlayerInventory().getHeldItemSlot()); + this.playerInventoryHolder.updateSlot(slot); } /** diff --git a/core/src/main/java/org/geysermc/geyser/util/MinecraftKey.java b/core/src/main/java/org/geysermc/geyser/util/MinecraftKey.java index bca979e82df..891c3ed8211 100644 --- a/core/src/main/java/org/geysermc/geyser/util/MinecraftKey.java +++ b/core/src/main/java/org/geysermc/geyser/util/MinecraftKey.java @@ -40,14 +40,21 @@ public static Key key(@Subst("empty") String s) { return Key.key(s); } - public static Key identifierToKey(@Nullable Identifier identifier) { + /** + * To prevent constant warnings from invalid regex. + */ + public static Key key(@Subst("empty") String namespace, @Subst("empty") String value) { + return Key.key(namespace, value); + } + + public static @Nullable Key identifierToKey(@Nullable Identifier identifier) { if (identifier == null) { return null; } - return identifier instanceof IdentifierImpl impl ? impl.identifier() : Key.key(identifier.namespace(), identifier.path()); + return identifier instanceof IdentifierImpl impl ? impl.identifier() : key(identifier.namespace(), identifier.path()); } - public static Identifier keyToIdentifier(@Nullable Key key) { + public static @Nullable Identifier keyToIdentifier(@Nullable Key key) { if (key == null) { return null; } diff --git a/core/src/main/resources/mappings b/core/src/main/resources/mappings index 3fcfc7bab43..f4be1233040 160000 --- a/core/src/main/resources/mappings +++ b/core/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 3fcfc7bab4345b6807d7507a02e52ed4e2da8b31 +Subproject commit f4be1233040b977ddc1b40382c2cc778655a5f35 From 438e91970e1cbd9d03d96caf6ad1835564395f7d Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sat, 28 Jun 2025 17:17:52 +0200 Subject: [PATCH 2/7] Address review --- .../GeyserDefineCustomItemsEvent.java | 2 +- .../v2/component/geyser/Chargeable.java | 4 ++- .../custom/v2/component/java/Repairable.java | 4 ++- .../item/custom/ComponentConverters.java | 2 +- .../item/custom/impl/ChargeableImpl.java | 19 ++++++++--- .../item/custom/impl/ConsumableImpl.java | 4 +-- .../item/custom/impl/EquippableImpl.java | 2 +- .../item/custom/impl/RepairableImpl.java | 18 ++++++++-- .../item/custom/impl/ToolPropertiesImpl.java | 2 +- .../item/custom/impl/UseCooldownImpl.java | 4 +-- .../mappings/versions/MappingsReader_v2.java | 3 +- .../CustomItemRegistryPopulator.java | 33 +++++++++---------- 12 files changed, 60 insertions(+), 37 deletions(-) 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 3503c473786..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 @@ -50,7 +50,7 @@ public interface GeyserDefineCustomItemsEvent extends Event { * The map returned here will only contain items registered with the deprecated * {@link GeyserDefineCustomItemsEvent#register(String, CustomItemData)} method. * - * @deprecated replaced with {@link GeyserDefineCustomItemsEvent#customItemDefinitions()}. + * @deprecated replaced with {@link GeyserDefineCustomItemsEvent#customItemDefinitions()} */ @Deprecated @NonNull 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 index e853fa1497d..7888fb5878b 100644 --- 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 @@ -32,6 +32,8 @@ import org.geysermc.geyser.api.util.GenericBuilder; import org.geysermc.geyser.api.util.Identifier; +import java.util.List; + // TODO projectile component (?) /** @@ -64,7 +66,7 @@ public interface Chargeable { * * @return all valid ammunition items */ - Identifier @NonNull [] ammunition(); + List<@NonNull Identifier> ammunition(); /** * Creates a builder for the Chargeable component. 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 index 6fd293432a7..f14652d9aec 100644 --- 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 @@ -31,6 +31,8 @@ import org.geysermc.geyser.api.util.GenericBuilder; import org.geysermc.geyser.api.util.Identifier; +import java.util.List; + /** * The repairable component determines which other items can be used * to repair the item. @@ -43,7 +45,7 @@ public interface Repairable { * * @return the identifiers */ - Identifier @NonNull [] items(); + List<@NonNull Identifier> items(); /** * Creates a builder for the repairable component. 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 9016e470ac4..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 @@ -53,7 +53,7 @@ // First of all, Java generics are a bit limited :( // Second, the API module has its own set of component classes, because MCPL can't be used in there. // However, those component classes have the same names as the MCPL ones, which causes some issues when they both have to be used in the same file. -// One can't be imported, and as such its full qualifier (e.g. Consumable) would have to be used. +// One can't be imported, and as such its full qualifier (e.g. org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable) would have to be used. // That would be a mess to code in, and as such this code here was carefully designed to only require one set of component classes by name (the MCPL ones). // // It is VERY IMPORTANT to note that for every component in the API, a converter to MCPL must be put here (there are some exceptions as noted in the Javadoc, better solutions are welcome). 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 index 0c6e95b33b1..13b87c84962 100644 --- 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 @@ -30,18 +30,20 @@ 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 Identifier @NonNull [] ammunition + @NonNull List<@NonNull Identifier> ammunition ) implements Chargeable { public static class Builder implements Chargeable.Builder { - float maxDrawDuration; - boolean chargeOnDraw; - Identifier[] ammunition; + private float maxDrawDuration; + private boolean chargeOnDraw; + private final List ammunition = new ArrayList<>(); @Override public Chargeable.Builder maxDrawDuration(@NonNegative float maxDrawDuration) { @@ -61,7 +63,14 @@ public Chargeable.Builder chargeOnDraw(boolean chargeOnDraw) { @Override public Chargeable.Builder ammunition(@NonNull Identifier @NonNull... ammunition) { Objects.requireNonNull(ammunition, "ammunition"); - this.ammunition = ammunition; + for (Identifier identifier : ammunition) { + Objects.requireNonNull(identifier, "ammunition"); + if (this.ammunition.contains(identifier)) { + throw new IllegalArgumentException("duplicate ammunition " + identifier); + } + this.ammunition.add(identifier); + } + return this; } 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 index 6dc2ec0617c..2885f16406c 100644 --- 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 @@ -37,8 +37,8 @@ public record ConsumableImpl( ) implements Consumable { public static class Builder implements Consumable.Builder { - float consumeSeconds; - Animation animation; + private float consumeSeconds; + private Animation animation; @Override public Builder consumeSeconds(@Positive float consumeSeconds) { diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/impl/EquippableImpl.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/EquippableImpl.java index 0d084ba6500..e3fe1c030bd 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/impl/EquippableImpl.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/impl/EquippableImpl.java @@ -35,7 +35,7 @@ public record EquippableImpl( ) implements Equippable { public static class Builder implements Equippable.Builder { - EquipmentSlot slot; + private EquipmentSlot slot; @Override public Equippable.Builder slot(@NonNull EquipmentSlot slot) { 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 index 66c1c51dc6a..b65054a7e62 100644 --- 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 @@ -25,17 +25,29 @@ 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; -public record RepairableImpl(Identifier... items) implements Repairable { +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 { - Identifier[] items; + private final List items = new ArrayList<>(); @Override public Builder items(Identifier[] items) { - this.items = items; + Objects.requireNonNull(items, "items cannot be null"); + for (Identifier item : items) { + Objects.requireNonNull(item, "item cannot be null"); + if (this.items.contains(item)) { + throw new IllegalArgumentException("duplicate repairable item: " + item); + } + this.items.add(item); + } return this; } diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/impl/ToolPropertiesImpl.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/ToolPropertiesImpl.java index 555cb4c12e7..15228b21d44 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/impl/ToolPropertiesImpl.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/impl/ToolPropertiesImpl.java @@ -32,7 +32,7 @@ public record ToolPropertiesImpl( ) implements ToolProperties { public static class Builder implements ToolProperties.Builder { - boolean destroyBlocksInCreative; + private boolean destroyBlocksInCreative; @Override public ToolProperties.Builder canDestroyBlocksInCreative(boolean destroyBlocksInCreative) { diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/impl/UseCooldownImpl.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/UseCooldownImpl.java index 9467f745bb0..de01dc42206 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/impl/UseCooldownImpl.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/impl/UseCooldownImpl.java @@ -35,8 +35,8 @@ public record UseCooldownImpl( ) implements UseCooldown { public static class Builder implements UseCooldown.Builder { - Identifier cooldownGroup; - float seconds; + private Identifier cooldownGroup; + private float seconds; @Override public Builder cooldownGroup(@Nullable Identifier cooldownGroup) { diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java index 9cd8db2be70..ba9c4c4701b 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java @@ -35,7 +35,6 @@ import org.geysermc.geyser.api.predicate.MinecraftPredicate; import org.geysermc.geyser.api.predicate.context.item.ItemPredicateContext; import org.geysermc.geyser.api.util.Identifier; -import org.geysermc.geyser.item.custom.GeyserCustomItemDefinition; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; import org.geysermc.geyser.registry.mappings.components.DataComponentReaders; import org.geysermc.geyser.registry.mappings.predicate.ItemConditionProperty; @@ -127,7 +126,7 @@ public CustomItemDefinition readItemMappingEntry(Identifier parentModel, JsonEle throw new InvalidCustomMappingsFileException("reading item model", "no model present", context); } - CustomItemDefinition.Builder builder = new GeyserCustomItemDefinition.Builder(bedrockIdentifier, model); + CustomItemDefinition.Builder builder = CustomItemDefinition.builder(bedrockIdentifier, model); MappingsUtil.readIfPresent(element, "display_name", builder::displayName, NodeReader.NON_EMPTY_STRING, context); MappingsUtil.readIfPresent(element, "priority", builder::priority, NodeReader.INT, context); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java index 46b417168ce..d4abf2bc099 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomItemRegistryPopulator.java @@ -63,6 +63,7 @@ import org.geysermc.geyser.registry.type.NonVanillaItemRegistration; import org.geysermc.geyser.util.MinecraftKey; import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable; import org.geysermc.mcprotocollib.protocol.data.game.item.component.FoodProperties; @@ -70,10 +71,8 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.UseCooldown; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -243,10 +242,10 @@ private static String checkPredicate(Map.Entry */ private static DataComponents checkComponents(CustomItemDefinition definition, Item javaItem) throws InvalidItemComponentsException { DataComponents components = patchDataComponents(javaItem, definition); - int stackSize = components.getOrDefault(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.MAX_STACK_SIZE, 0); - int maxDamage = components.getOrDefault(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.MAX_DAMAGE, 0); + int stackSize = components.getOrDefault(DataComponentTypes.MAX_STACK_SIZE, 0); + int maxDamage = components.getOrDefault(DataComponentTypes.MAX_DAMAGE, 0); - if (components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.EQUIPPABLE) != null && stackSize > 1) { + if (components.get(DataComponentTypes.EQUIPPABLE) != null && stackSize > 1) { throw new InvalidItemComponentsException("Bedrock doesn't support equippable items with a stack size above 1"); } else if (stackSize > 1 && maxDamage > 0) { throw new InvalidItemComponentsException("Stack size must be 1 when max damage is above 0"); @@ -284,7 +283,7 @@ private static NbtMapBuilder createComponentNbt(Key itemIdentifier, CustomItemDe .build()); } - ToolData toolData = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.TOOL); + ToolData toolData = components.get(DataComponentTypes.TOOL); boolean canDestroyInCreative = toolData == null || toolData.isCanDestroyBlocksInCreative(); computeCreativeDestroyProperties(canDestroyInCreative, itemProperties, componentBuilder); @@ -294,17 +293,17 @@ private static NbtMapBuilder createComponentNbt(Key itemIdentifier, CustomItemDe computeRepairableProperties(repairable, componentBuilder); } - Equippable equippable = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.EQUIPPABLE); + Equippable equippable = components.get(DataComponentTypes.EQUIPPABLE); if (equippable != null) { computeArmorProperties(equippable, customItemDefinition.bedrockOptions().protectionValue(), componentBuilder); } - Integer enchantmentValue = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.ENCHANTABLE); + Integer enchantmentValue = components.get(DataComponentTypes.ENCHANTABLE); if (enchantmentValue != null) { computeEnchantableProperties(enchantmentValue, itemProperties, componentBuilder); } - Boolean glint = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE); + Boolean glint = components.get(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE); if (glint != null) { itemProperties.putBoolean("foil", glint); componentBuilder.putCompound("minecraft:glint", NbtMap.builder() @@ -312,13 +311,13 @@ private static NbtMapBuilder createComponentNbt(Key itemIdentifier, CustomItemDe .build()); } - Consumable consumable = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.CONSUMABLE); + Consumable consumable = components.get(DataComponentTypes.CONSUMABLE); if (consumable != null) { - FoodProperties foodProperties = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.FOOD); + FoodProperties foodProperties = components.get(DataComponentTypes.FOOD); computeConsumableProperties(consumable, foodProperties, itemProperties, componentBuilder); } - UseCooldown useCooldown = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.USE_COOLDOWN); + UseCooldown useCooldown = components.get(DataComponentTypes.USE_COOLDOWN); if (useCooldown != null) { computeUseCooldownProperties(useCooldown, itemIdentifier, componentBuilder); } @@ -400,10 +399,10 @@ private static void setupBasicItemInfo(CustomItemDefinition definition, DataComp itemProperties.putBoolean("allow_off_hand", options.allowOffhand()); itemProperties.putBoolean("hand_equipped", options.displayHandheld()); - int maxDamage = components.getOrDefault(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.MAX_DAMAGE, 0); - Equippable equippable = components.get(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.EQUIPPABLE); + int maxDamage = components.getOrDefault(DataComponentTypes.MAX_DAMAGE, 0); + Equippable equippable = components.get(DataComponentTypes.EQUIPPABLE); // Java requires stack size to be 1 when max damage is above 0, and bedrock requires stack size to be 1 when the item can be equipped - int stackSize = maxDamage > 0 || equippable != null ? 1 : components.getOrDefault(org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla ones + int stackSize = maxDamage > 0 || equippable != null ? 1 : components.getOrDefault(DataComponentTypes.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla ones itemProperties.putInt("max_stack_size", stackSize); @@ -457,7 +456,7 @@ private static void computeCreativeDestroyProperties(boolean canDestroyInCreativ *

This method passes the Java identifiers straight to bedrock - which isn't perfect.

*/ private static void computeRepairableProperties(Repairable repairable, NbtMapBuilder componentBuilder) { - List items = Arrays.stream(repairable.items()) + List items = repairable.items().stream() .map(identifier -> NbtMap.builder() .putString("name", identifier.toString()) .build()).toList(); @@ -531,7 +530,7 @@ private static void computeChargeableProperties(NbtMapBuilder itemProperties, Nb } componentBuilder.putCompound("minecraft:shooter", NbtMap.builder() - .putList("ammunition", NbtType.COMPOUND, Arrays.stream(chargeable.ammunition()) + .putList("ammunition", NbtType.COMPOUND, chargeable.ammunition().stream() .map(ammunition -> NbtMap.builder() .putCompound("item", NbtMap.builder() From 1c24ee645fcb79e04f36572aed2bb1f7000fb3ce Mon Sep 17 00:00:00 2001 From: chris Date: Sat, 28 Jun 2025 17:21:47 +0200 Subject: [PATCH 3/7] Apply suggestions from code review Co-authored-by: Eclipse --- .../api/item/custom/v2/CustomItemDefinition.java | 8 ++++---- .../item/custom/v2/component/geyser/BlockPlacer.java | 2 +- .../item/custom/v2/component/geyser/Chargeable.java | 4 ++-- .../api/item/custom/v2/component/java/Consumable.java | 3 ++- .../api/item/custom/v2/component/java/Equippable.java | 3 ++- .../item/custom/v2/component/java/FoodProperties.java | 11 ++++++----- .../custom/v2/component/java/ItemDataComponents.java | 9 ++++----- .../item/custom/v2/component/java/ToolProperties.java | 8 ++++---- .../item/custom/v2/component/java/UseCooldown.java | 2 +- .../java/org/geysermc/geyser/api/util/Identifier.java | 7 ++++--- 10 files changed, 30 insertions(+), 27 deletions(-) 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 be591b656b3..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 @@ -200,8 +200,8 @@ interface Builder extends GenericBuilder { * 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 * + * @see CustomItemBedrockOptions * @param options the bedrock item options * @return this builder */ @@ -209,10 +209,10 @@ interface Builder extends GenericBuilder { Builder bedrockOptions(CustomItemBedrockOptions.@NonNull Builder options); /** - * Sets the predicates that must match for Geyser to use this item definition. + * Adds a predicate that must match for Geyser to use this item definition. * See {@link CustomItemDefinition#predicates()} for details. * - * @param predicate the predicates that must match for this item to be used + * @param predicate a predicate that must match for this item to be used * @return this builder */ @This @@ -265,7 +265,7 @@ default Builder component(@NonNull DataComponent component, @NonNull Gene Builder removeComponent(@NonNull Identifier component); /** - * Convenience method for {@link CustomItemDefinition.Builder#removeComponent(Identifier)} + * Convenience method for {@link CustomItemDefinition.Builder#removeComponent(Identifier)}. * * @param component the component type to remove * @return this builder 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 index 9a37685879f..b478b2c29f1 100644 --- 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 @@ -40,7 +40,7 @@ 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 + * This is a Bedrock edition block identifier. * * @return the identifier of the block to place */ 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 index 7888fb5878b..c3957f3e840 100644 --- 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 @@ -53,7 +53,7 @@ public interface Chargeable { @NonNegative float maxDrawDuration(); /** - * Whether the item is being charged when being drawn. + * Whether the item is being charged when being drawn, like a crossbow. * * @return whether drawing the item charges it */ @@ -83,7 +83,7 @@ static Builder builder() { interface Builder extends GenericBuilder { /** - * Sets the maximum draw duration before the item is released + * Sets the maximum draw duration before the item is released. * * @param maxDrawDuration the non-negative maximum charging duration * @return this builder 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 index e03be998c0a..6353bbbf422 100644 --- 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 @@ -40,7 +40,7 @@ public interface Consumable { /** * The seconds it takes to consume the item. - * This it the amount of time the animation will play. + * This it the amount of time the animation will play for. * * @return the consume duration, in seconds */ @@ -48,6 +48,7 @@ public interface Consumable { /** * The animation that should play when consuming the item. + * * @return the animation to play */ @NonNull Animation animation(); 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 index fde1ce61f2c..5592050ff07 100644 --- 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 @@ -31,7 +31,7 @@ import org.geysermc.geyser.api.util.GenericBuilder; /** - * The equippable component is used to mark a item as equippable. + * 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 { @@ -70,6 +70,7 @@ interface Builder extends GenericBuilder { /** * The equipment slot where the item can be equipped + * * @param slot the equipment slot * @return this builder */ 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 index ebcab6408ba..79a4b061e81 100644 --- 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 @@ -31,9 +31,9 @@ import org.geysermc.geyser.api.util.GenericBuilder; /** - * The food properties component can be used to create - * edible items. This includes setting the nutrition, - * saturation and whether an item can be always eaten. + * The food properties component can be used to define properties + * for consumable items. This includes setting the nutrition and + * saturation values, and whether an item can always be eaten. */ public interface FoodProperties { @@ -84,8 +84,9 @@ interface Builder extends GenericBuilder { Builder nutrition(@NonNegative int nutrition); /** - * Sets the saturation of the item - * @param saturation the saturation of the ite + * Sets the saturation of the item. + * + * @param saturation the saturation of the item * @return this builder */ @This 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 index a5a88544562..7230a1ecb17 100644 --- 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 @@ -47,9 +47,9 @@ public class 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 particles aren't possible. + * 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}

+ *

Note that due to a bug on Bedrock, not all consume animations appear perfectly. See {@link Consumable.Animation}.

* * @see Consumable */ @@ -73,12 +73,12 @@ public class ItemDataComponents { 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. + * 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. */ 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. + * 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. */ public static final DataComponent MAX_STACK_SIZE = create("max_stack_size", i -> i >= 1 && i <= 99); // Reverse lambda @@ -103,7 +103,6 @@ public class ItemDataComponents { /** * This component is only used for the {@link ToolProperties#canDestroyBlocksInCreative()} option. * - *

Like other components, when not set this will fall back to the default value.

* * @see ToolProperties */ 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 index 97be1ed179c..c9b6cb3eec5 100644 --- 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 @@ -31,12 +31,12 @@ /** * The tool properties component can be used to mark - * which items can destroy blocks when used in creative mode. + * if the item can destroy blocks when used in creative mode. */ public interface ToolProperties { /** - * Whether this item can destroy blocks when clicking in + * Whether this item can destroy blocks when trying to break them in * creative mode. * * @return whether this item can destroy blocks in creative mode @@ -68,8 +68,8 @@ static ToolProperties of(boolean canDestroyBlocksInCreative) { interface Builder extends GenericBuilder { /** - * Sets whether this item can destroy blocks instantly - * while in creative mode. + * 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 * @return this builder 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 index a3675ba83eb..02d3867490d 100644 --- 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 @@ -65,7 +65,7 @@ static Builder builder() { } /** - * Builder for the use cooldown component + * Builder for the use cooldown component. */ interface Builder extends GenericBuilder { 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 7cd4373903d..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 @@ -29,7 +29,7 @@ import org.geysermc.geyser.api.GeyserApi; /** - * An identifying object for getting and/or storing unique objects. + * An identifying object for representing unique objects. * This identifier consists of two parts: *
    *
  • @@ -42,8 +42,8 @@ * * Examples of identifiers: *
      - *
    • minecraft:fox
    • - *
    • geysermc:one_fun_example
    • + *
    • {@code minecraft:fox}
    • + *
    • {@code geysermc:one_fun_example}
    • *
    * * If this identifier is referencing anything not in the @@ -59,6 +59,7 @@ public interface Identifier { /** * 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. */ From e33df07ae996d7e1df6eb25e18f36a7a98f0f2bd Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sat, 28 Jun 2025 17:33:22 +0200 Subject: [PATCH 4/7] fix repairable mapping reader --- .../mappings/components/readers/RepairableReader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/RepairableReader.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/RepairableReader.java index 971774d5c8a..a500e0b218e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/RepairableReader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/components/readers/RepairableReader.java @@ -48,10 +48,10 @@ public RepairableReader() { protected Repairable readDataComponent(@NonNull JsonElement node, String... context) throws InvalidCustomMappingsFileException { try { Identifier item = MappingsUtil.readOrThrow(node, "items", NodeReader.IDENTIFIER, context); - return new RepairableImpl(item); + return Repairable.of(item); } catch (InvalidCustomMappingsFileException exception) { List items = MappingsUtil.readArrayOrThrow(node, "items", NodeReader.IDENTIFIER, context); - return new RepairableImpl(items.toArray(Identifier[]::new)); + return new RepairableImpl(items); } } } From 0128c419073763bbf341b9cee09d27355e143784 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 28 Jun 2025 22:04:06 +0000 Subject: [PATCH 5/7] Small fixes and consistency things, default values for some components, shorthand constructors for BlockPlacer, Consumable, FoodProperties --- .../v2/component/geyser/BlockPlacer.java | 15 +++++++++++++-- .../custom/v2/component/geyser/Chargeable.java | 8 ++++---- .../custom/v2/component/java/Consumable.java | 11 +++++++++++ .../v2/component/java/FoodProperties.java | 18 +++++++++++++++--- .../custom/v2/component/java/Repairable.java | 12 ++++++++---- .../custom/GeyserCustomItemDefinition.java | 2 +- .../item/custom/impl/BlockPlacerImpl.java | 6 +++--- .../item/custom/impl/ChargeableImpl.java | 14 +++++--------- .../item/custom/impl/ConsumableImpl.java | 9 ++++----- .../item/custom/impl/EquippableImpl.java | 1 + .../item/custom/impl/RepairableImpl.java | 13 +++++-------- .../item/custom/impl/ToolPropertiesImpl.java | 2 +- 12 files changed, 71 insertions(+), 40 deletions(-) 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 index b478b2c29f1..83c1cb9d1b5 100644 --- 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 @@ -64,6 +64,17 @@ static Builder builder() { return GeyserApi.api().provider(BlockPlacer.Builder.class); } + /** + * Creates a BlockPlacer 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 BlockPlacer component + */ + static BlockPlacer of(Identifier block, boolean useBlockIcon) { + return BlockPlacer.builder().block(block).useBlockIcon(useBlockIcon).build(); + } + /** * Builder for the BlockPlacer component. */ @@ -74,11 +85,11 @@ interface Builder extends GenericBuilder { * This should be the block identifier as it is * known to the Bedrock client. * - * @param identifier the identifier of the block + * @param block the identifier of the block * @return this builder */ @This - Builder block(@NonNull Identifier identifier); + Builder block(@NonNull Identifier block); /** * Whether to use the block's icon over the item icon. 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 index c3957f3e840..9a4a80a6b2a 100644 --- 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 @@ -101,14 +101,14 @@ interface Builder extends GenericBuilder { Builder chargeOnDraw(boolean chargeOnDraw); /** - * Sets the valid items that can be used as ammunition. - * This should include all valid items, such as {@code minecraft:arrow}. + * 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 identifiers of possible ammunition + * @param ammunition the Bedrock item identifier of possible ammunition * @return this builder */ @This - Builder ammunition(@NonNull Identifier @NonNull... ammunition); + Builder ammunition(@NonNull Identifier ammunition); /** * Creates the chargeable component. 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 index 6353bbbf422..d6fee5b66c1 100644 --- 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 @@ -62,6 +62,17 @@ 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. */ 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 index 79a4b061e81..5d1214b2738 100644 --- 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 @@ -33,7 +33,7 @@ /** * The food properties component can be used to define properties * for consumable items. This includes setting the nutrition and - * saturation values, and whether an item can always be eaten. + * saturation values, and whether the item can always be eaten. */ public interface FoodProperties { @@ -52,11 +52,11 @@ public interface FoodProperties { @NonNegative float saturation(); /** - * Whether this item can be always eaten, + * Whether this item can always be eaten, * even when not hungry. In vanilla, this would * include items such as golden apples. * - * @return whether an item can be always eaten + * @return whether the item can always be eaten */ boolean canAlwaysEat(); @@ -69,6 +69,18 @@ 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. */ 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 index f14652d9aec..c3b1d07f1d5 100644 --- 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 @@ -31,6 +31,7 @@ import org.geysermc.geyser.api.util.GenericBuilder; import org.geysermc.geyser.api.util.Identifier; +import java.util.Arrays; import java.util.List; /** @@ -64,7 +65,9 @@ static Builder builder() { * @return the repairable component */ static Repairable of(Identifier... items) { - return builder().items(items).build(); + Repairable.Builder builder = builder(); + Arrays.stream(items).forEach(builder::item); + return builder.build(); } /** @@ -73,13 +76,14 @@ static Repairable of(Identifier... items) { interface Builder extends GenericBuilder { /** - * Sets which item(s) can be used to repair the item. + * 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 identifier the Bedrock item identifier that can be used to repair the item + * @param item the Bedrock item identifier that can be used to repair the item * @return this builder */ @This - Builder items(Identifier[] identifier); + Builder item(@NonNull Identifier item); /** * Creates the repairable component. 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 bca1842cf29..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 @@ -173,7 +173,7 @@ public CustomItemDefinition.Builder predicateStrategy(@NonNull PredicateStrategy @Override public CustomItemDefinition.Builder component(@NonNull DataComponent component, @NonNull T value) { Objects.requireNonNull(component, "component cannot be null"); - 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()); } 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 index f73ca67167e..2e161e6eded 100644 --- 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 @@ -41,9 +41,9 @@ public static class Builder implements BlockPlacer.Builder { private boolean useBlockIcon; @Override - public Builder block(@NonNull Identifier identifier) { - Objects.requireNonNull(identifier, "identifier cannot be null"); - this.block = identifier; + public Builder block(@NonNull Identifier block) { + Objects.requireNonNull(block, "block cannot be null"); + this.block = block; return this; } 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 index 13b87c84962..7c439e5cd53 100644 --- 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 @@ -61,22 +61,18 @@ public Chargeable.Builder chargeOnDraw(boolean chargeOnDraw) { } @Override - public Chargeable.Builder ammunition(@NonNull Identifier @NonNull... ammunition) { + public Chargeable.Builder ammunition(@NonNull Identifier ammunition) { Objects.requireNonNull(ammunition, "ammunition"); - for (Identifier identifier : ammunition) { - Objects.requireNonNull(identifier, "ammunition"); - if (this.ammunition.contains(identifier)) { - throw new IllegalArgumentException("duplicate ammunition " + identifier); - } - this.ammunition.add(identifier); + 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"); + 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 index 2885f16406c..ca2de75d058 100644 --- 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 @@ -37,13 +37,13 @@ public record ConsumableImpl( ) implements Consumable { public static class Builder implements Consumable.Builder { - private float consumeSeconds; - private Animation animation; + 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"); + throw new IllegalArgumentException("consume seconds must be above 0"); } this.consumeSeconds = consumeSeconds; return this; @@ -51,14 +51,13 @@ public Builder consumeSeconds(@Positive float consumeSeconds) { @Override public Builder animation(@NonNull Animation animation) { - Objects.requireNonNull(animation, "Animation cannot be null"); + Objects.requireNonNull(animation, "animation cannot be null"); this.animation = animation; return this; } @Override public Consumable build() { - Objects.requireNonNull(animation, "Animation cannot be null"); return new ConsumableImpl(consumeSeconds, animation); } } diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/impl/EquippableImpl.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/EquippableImpl.java index e3fe1c030bd..f8883a27df3 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/impl/EquippableImpl.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/impl/EquippableImpl.java @@ -46,6 +46,7 @@ public Equippable.Builder slot(@NonNull EquipmentSlot slot) { @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/RepairableImpl.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/RepairableImpl.java index b65054a7e62..a58b2eb1993 100644 --- 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 @@ -39,15 +39,12 @@ public static class Builder implements Repairable.Builder { private final List items = new ArrayList<>(); @Override - public Builder items(Identifier[] items) { - Objects.requireNonNull(items, "items cannot be null"); - for (Identifier item : items) { - Objects.requireNonNull(item, "item cannot be null"); - if (this.items.contains(item)) { - throw new IllegalArgumentException("duplicate repairable item: " + item); - } - this.items.add(item); + 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; } diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/impl/ToolPropertiesImpl.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/ToolPropertiesImpl.java index 15228b21d44..749ee1f181b 100644 --- a/core/src/main/java/org/geysermc/geyser/item/custom/impl/ToolPropertiesImpl.java +++ b/core/src/main/java/org/geysermc/geyser/item/custom/impl/ToolPropertiesImpl.java @@ -32,7 +32,7 @@ public record ToolPropertiesImpl( ) implements ToolProperties { public static class Builder implements ToolProperties.Builder { - private boolean destroyBlocksInCreative; + private boolean destroyBlocksInCreative = true; @Override public ToolProperties.Builder canDestroyBlocksInCreative(boolean destroyBlocksInCreative) { From d93ccf98775b180f8a610b7aa8922454717aa1b2 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 28 Jun 2025 22:12:03 +0000 Subject: [PATCH 6/7] Consistency things --- .../v2/component/geyser/BlockPlacer.java | 10 +++---- .../component/geyser/GeyserDataComponent.java | 14 +++++----- .../v2/component/java/ItemDataComponents.java | 26 +++++++++---------- .../item/custom/impl/ChargeableImpl.java | 2 +- 4 files changed, 25 insertions(+), 27 deletions(-) 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 index 83c1cb9d1b5..06b1c17089f 100644 --- 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 @@ -56,7 +56,7 @@ public interface BlockPlacer { boolean useBlockIcon(); /** - * Creates a builder for the BlockPlacer component. + * Creates a builder for the block placer component. * * @return a new builder */ @@ -65,18 +65,18 @@ static Builder builder() { } /** - * Creates a BlockPlacer component. + * 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 BlockPlacer component + * @return the block placer component */ static BlockPlacer of(Identifier block, boolean useBlockIcon) { return BlockPlacer.builder().block(block).useBlockIcon(useBlockIcon).build(); } /** - * Builder for the BlockPlacer component. + * Builder for the block placer component. */ interface Builder extends GenericBuilder { @@ -102,7 +102,7 @@ interface Builder extends GenericBuilder { Builder useBlockIcon(boolean useBlockIcon); /** - * Creates the BlockPlacer component. + * Creates the block placer component. * * @return the new component */ diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/geyser/GeyserDataComponent.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/geyser/GeyserDataComponent.java index 8147eb5bc82..1ca32b11c25 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/geyser/GeyserDataComponent.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/geyser/GeyserDataComponent.java @@ -41,7 +41,7 @@ * @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. @@ -49,27 +49,25 @@ public final class GeyserDataComponent { * * @see Chargeable */ - public static final DataComponent CHARGEABLE = 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 = 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 = createGeyser("block_placer"); + DataComponent BLOCK_PLACER = createGeyser("block_placer"); - static DataComponent createGeyser(String id) { + private static DataComponent createGeyser(String id) { return createGeyser(id, t -> true); } - static DataComponent createGeyser(String id, Predicate predicate) { + private static DataComponent createGeyser(String id, Predicate predicate) { return GeyserApi.api().provider(DataComponent.class, Identifier.of("geysermc", id), predicate, false); } - - private GeyserDataComponent() {} } 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 index 7230a1ecb17..c612b15cea0 100644 --- 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 @@ -43,7 +43,7 @@ * @see CustomItemDefinition#components() * @see GeyserDataComponent */ -public class ItemDataComponents { +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, @@ -53,7 +53,7 @@ public class ItemDataComponents { * * @see Consumable */ - public static final DataComponent CONSUMABLE = create("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), @@ -63,24 +63,24 @@ public class ItemDataComponents { * * @see Equippable */ - public static final DataComponent EQUIPPABLE = create("equippable"); + 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"); + 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. */ - public static final DataComponent MAX_DAMAGE = create("max_damage", i -> i >= 0); + 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. */ - public static final DataComponent MAX_STACK_SIZE = create("max_stack_size", i -> i >= 1 && i <= 99); // Reverse lambda + 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. @@ -90,7 +90,7 @@ public class ItemDataComponents { * * @see UseCooldown */ - public static final DataComponent USE_COOLDOWN = create("use_cooldown"); + DataComponent USE_COOLDOWN = create("use_cooldown"); /** * Marks the item to be enchantable. Must be at or above 0. @@ -98,7 +98,7 @@ public class ItemDataComponents { *

    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.

    */ - public static final DataComponent ENCHANTABLE = create("enchantable", i -> i >= 0); + DataComponent ENCHANTABLE = create("enchantable", i -> i >= 0); /** * This component is only used for the {@link ToolProperties#canDestroyBlocksInCreative()} option. @@ -106,25 +106,25 @@ public class ItemDataComponents { * * @see ToolProperties */ - public static final DataComponent TOOL = create("tool"); + DataComponent TOOL = create("tool"); /** * Marks which items can be used to repair the item. * * @see Repairable */ - public static final DataComponent REPAIRABLE = create("repairable"); + DataComponent REPAIRABLE = create("repairable"); /** * Overrides the item's enchantment glint. */ - public static final DataComponent ENCHANTMENT_GLINT_OVERRIDE = create("enchantment_glint_override"); + DataComponent ENCHANTMENT_GLINT_OVERRIDE = create("enchantment_glint_override"); - static DataComponent create(String id) { + private static DataComponent create(String id) { return create(id, t -> true); } - static DataComponent create(String id, Predicate consumer) { + private static DataComponent create(String id, Predicate consumer) { return GeyserApi.api().provider(DataComponent.class, Identifier.of(id), consumer, true); } } 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 index 7c439e5cd53..c19276c0754 100644 --- 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 @@ -62,7 +62,7 @@ public Chargeable.Builder chargeOnDraw(boolean chargeOnDraw) { @Override public Chargeable.Builder ammunition(@NonNull Identifier ammunition) { - Objects.requireNonNull(ammunition, "ammunition"); + Objects.requireNonNull(ammunition, "ammunition cannot be null"); if (this.ammunition.contains(ammunition)) { throw new IllegalArgumentException("duplicate ammunition " + ammunition); } From 8171fbd77db6d968f73cbdf117dfa5f5b4d50c13 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sun, 29 Jun 2025 07:57:46 +0000 Subject: [PATCH 7/7] Note down default component values in Javadocs --- .../api/item/custom/v2/component/geyser/BlockPlacer.java | 4 +++- .../api/item/custom/v2/component/geyser/Chargeable.java | 7 +++++-- .../api/item/custom/v2/component/java/Consumable.java | 6 ++++-- .../api/item/custom/v2/component/java/Equippable.java | 1 + .../item/custom/v2/component/java/FoodProperties.java | 9 ++++++--- .../api/item/custom/v2/component/java/Repairable.java | 1 + .../item/custom/v2/component/java/ToolProperties.java | 3 ++- .../api/item/custom/v2/component/java/UseCooldown.java | 2 ++ 8 files changed, 24 insertions(+), 9 deletions(-) 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 index 06b1c17089f..3f0e2650828 100644 --- 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 @@ -48,7 +48,7 @@ public interface BlockPlacer { /** * Whether to use the block's rendering - * as the icon for the item. + * as the icon for the item. Defaults to {@code false}. * * @return whether to use the 3d block rendering for the * item icon @@ -86,6 +86,7 @@ interface Builder extends GenericBuilder { * known to the Bedrock client. * * @param block the identifier of the block + * @see BlockPlacer#block() * @return this builder */ @This @@ -96,6 +97,7 @@ interface Builder extends GenericBuilder { * Block items have a 3d-generated block icon. * * @param useBlockIcon whether to use the block icon + * @see BlockPlacer#useBlockIcon() * @return this builder */ @This 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 index 9a4a80a6b2a..a39d739f4d2 100644 --- 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 @@ -46,14 +46,14 @@ public interface Chargeable { /** * The maximum draw duration determines how long the weapon - * can be drawn before releasing automatically. + * 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. + * Whether the item is being charged when being drawn, like a crossbow. Defaults to {@code false}. * * @return whether drawing the item charges it */ @@ -86,6 +86,7 @@ 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 @@ -95,6 +96,7 @@ interface Builder extends GenericBuilder { * Sets whether the item is charged when drawing. * * @param chargeOnDraw whether drawing charges the item + * @see Chargeable#chargeOnDraw() * @return this builder */ @This @@ -105,6 +107,7 @@ interface Builder extends GenericBuilder { * 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 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 index d6fee5b66c1..102c3000806 100644 --- 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 @@ -40,14 +40,14 @@ public interface Consumable { /** * The seconds it takes to consume the item. - * This it the amount of time the animation will play for. + * 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. + * The animation that should play when consuming the item. Defaults to {@link Animation#EAT}. * * @return the animation to play */ @@ -124,6 +124,7 @@ interface Builder extends GenericBuilder { * determines the animation length. * * @param consumeSeconds the seconds it takes to consume the item + * @see Consumable#consumeSeconds() * @return this builder */ @This @@ -135,6 +136,7 @@ interface Builder extends GenericBuilder { * do not work correctly. * * @param animation the animation to play + * @see Consumable#animation() * @return this builder */ @This 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 index 5592050ff07..d28ab19f6fd 100644 --- 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 @@ -72,6 +72,7 @@ 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 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 index 5d1214b2738..97fe528769a 100644 --- 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 @@ -38,14 +38,14 @@ public interface FoodProperties { /** - * The nutrition of the item. + * The nutrition of the item. Defaults to {@code 0}. * * @return the nutrition */ @NonNegative int nutrition(); /** - * The saturation of the item. + * The saturation of the item. Defaults to {@code 0.0}. * * @return the saturation */ @@ -54,7 +54,7 @@ public interface FoodProperties { /** * Whether this item can always be eaten, * even when not hungry. In vanilla, this would - * include items such as golden apples. + * include items such as golden apples. Defaults to {@code false}. * * @return whether the item can always be eaten */ @@ -90,6 +90,7 @@ 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 @@ -99,6 +100,7 @@ interface Builder extends GenericBuilder { * Sets the saturation of the item. * * @param saturation the saturation of the item + * @see FoodProperties#saturation() * @return this builder */ @This @@ -109,6 +111,7 @@ interface Builder extends GenericBuilder { * even when the hunger bar is full. * * @param canAlwaysEat whether the item can always be eaten + * @see FoodProperties#canAlwaysEat() * @return this builder */ @This 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 index c3b1d07f1d5..579784997b2 100644 --- 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 @@ -80,6 +80,7 @@ interface Builder extends GenericBuilder { * 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 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 index c9b6cb3eec5..caebfd2e137 100644 --- 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 @@ -37,7 +37,7 @@ public interface ToolProperties { /** * Whether this item can destroy blocks when trying to break them in - * creative mode. + * creative mode. Defaults to {@code true}. * * @return whether this item can destroy blocks in creative mode */ @@ -72,6 +72,7 @@ interface Builder extends GenericBuilder { * creative mode. * * @param canDestroyBlocksInCreative determines if the item will break blocks in creative mode + * @see ToolProperties#canDestroyBlocksInCreative() * @return this builder */ @This 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 index 02d3867490d..b876f7da8be 100644 --- 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 @@ -74,6 +74,7 @@ interface Builder extends GenericBuilder { * cannot be used again. * * @param seconds the cooldown time + * @see UseCooldown#seconds() * @return this builder */ @This @@ -86,6 +87,7 @@ interface Builder extends GenericBuilder { * {@code null} results in the item identifier being specified instead. * * @param cooldownGroup the cooldown group identifier + * @see UseCooldown#cooldownGroup() * @return this builder */ @This