Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -41,29 +41,34 @@
/**
* Called on Geyser's startup when looking for custom items. Custom items must be registered through this event.
* <p>
* 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<String, Collection<CustomItemData>> 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<Identifier, Collection<CustomItemDefinition>> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -59,18 +63,24 @@ 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();

/**
* 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.
*
* <p>If multiple item definitions for a model are registered, then only one can have no predicate.</p>
*
* @return the identifier of the Java item model
*/
@NonNull Identifier model();

Expand All @@ -82,6 +92,8 @@ public interface CustomItemDefinition {
*
* <p>{@code my_datapack:my_custom_item} => {@code my_datapack.my_custom_item}</p>
* <p>{@code my_datapack:cool_items/cool_item_1} => {@code my_datapack.cool_items_cool_item_1}</p>
*
* @return the icon shown to Bedrock players
*/
default @NonNull String icon() {
String setIcon = bedrockOptions().icon();
Expand All @@ -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.
Expand All @@ -121,16 +133,16 @@ public interface CustomItemDefinition {
* <p>Currently, the following components are (somewhat) supported:</p>
*
* <ul>
* <li>{@code minecraft:consumable} ({@link DataComponent#CONSUMABLE})</li>
* <li>{@code minecraft:equippable} ({@link DataComponent#EQUIPPABLE})</li>
* <li>{@code minecraft:food} ({@link DataComponent#FOOD})</li>
* <li>{@code minecraft:max_damage} ({@link DataComponent#MAX_DAMAGE})</li>
* <li>{@code minecraft:max_stack_size} ({@link DataComponent#MAX_STACK_SIZE})</li>
* <li>{@code minecraft:use_cooldown} ({@link DataComponent#USE_COOLDOWN})</li>
* <li>{@code minecraft:enchantable} ({@link DataComponent#ENCHANTABLE})</li>
* <li>{@code minecraft:tool} ({@link DataComponent#TOOL})</li>
* <li>{@code minecraft:repairable} ({@link DataComponent#REPAIRABLE})</li>
* <li>{@code minecraft:enchantment_glint_override} ({@link DataComponent#ENCHANTMENT_GLINT_OVERRIDE})</li>
* <li>{@code minecraft:consumable} ({@link ItemDataComponents#CONSUMABLE})</li>
* <li>{@code minecraft:equippable} ({@link ItemDataComponents#EQUIPPABLE})</li>
* <li>{@code minecraft:food} ({@link ItemDataComponents#FOOD})</li>
* <li>{@code minecraft:max_damage} ({@link ItemDataComponents#MAX_DAMAGE})</li>
* <li>{@code minecraft:max_stack_size} ({@link ItemDataComponents#MAX_STACK_SIZE})</li>
* <li>{@code minecraft:use_cooldown} ({@link ItemDataComponents#USE_COOLDOWN})</li>
* <li>{@code minecraft:enchantable} ({@link ItemDataComponents#ENCHANTABLE})</li>
* <li>{@code minecraft:tool} ({@link ItemDataComponents#TOOL})</li>
* <li>{@code minecraft:repairable} ({@link ItemDataComponents#REPAIRABLE})</li>
* <li>{@code minecraft:enchantment_glint_override} ({@link ItemDataComponents#ENCHANTMENT_GLINT_OVERRIDE})</li>
* </ul>
*
* <p>Note: some components, for example {@code minecraft:rarity} and {@code minecraft:attribute_modifiers}, are translated automatically, and do not have to be specified here.</p>
Expand All @@ -148,33 +160,131 @@ public interface CustomItemDefinition {
*/
@NonNull List<Identifier> 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<CustomItemDefinition> {

/**
* Sets the display name, as shown to the Bedrock client.
* When not set, the display name will be derived from the Bedrock item identifier.
*
* @param displayName the display name to show for Bedrock clients.
* @return this builder
*/
@This
Builder displayName(@NonNull String displayName);

/**
* Sets the priority of this definition, used for definition matching.
* @see CustomItemDefinition#priority()
*
* @param priority the priority
* @return this builder
*/
@This
Builder priority(int priority);

/**
* Sets the Bedrock item options for this definition.
* Those determine the icon seen on Bedrock edition, whether the item
* can be placed in the offhand slot, and other options.
*
* @see CustomItemBedrockOptions
* @param options the bedrock item options
* @return this builder
*/
@This
Builder bedrockOptions(CustomItemBedrockOptions.@NonNull Builder options);

/**
* Adds a predicate that must match for Geyser to use this item definition.
* See {@link CustomItemDefinition#predicates()} for details.
*
* @param predicate a predicate that must match for this item to be used
* @return this builder
*/
@This
Builder predicate(@NonNull MinecraftPredicate<? super ItemPredicateContext> predicate);

/**
* Sets the predicate strategy that should be used for item definition matching.
*
* @param strategy the predicate strategy to use
* @return this builder
*/
@This
Builder predicateStrategy(@NonNull PredicateStrategy strategy);

/**
* Sets data components that determine the item behavior. These are assumed to also be
* present server-side on the Java server. See {@link CustomItemDefinition#components()}
* for more information.
*
* @param component the type of the component - found in {@link ItemDataComponents}
* @param value the value of the component
* @return this builder
* @param <T> the value held by the component
*/
@This
<T> Builder component(@NonNull DataComponent<T> 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 <T> the value held by the component
*/
@This
default <T> Builder component(@NonNull DataComponent<T> component, @NonNull GenericBuilder<T> 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();
}
}
Loading