diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java index 3b871cd74c5..2fab74dd469 100644 --- a/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java +++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/CustomItemData.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.api.item.custom; +import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.GeyserApi; @@ -114,6 +116,88 @@ public interface CustomItemData { */ @NonNull Set tags(); + /** + * Gets the stack size of the item. + * + *

Returns 0 if not set. When not set, Geyser defaults to the stack count of the Java item when based on a vanilla item, or 64 when registering a non-vanilla item.

+ * + *

Note that, to copy Java behaviour, setting the stack size of an item to a value above 1 will set the max damage to 0. If a max damage value above 0 was explicitly set, an exception will be thrown.

+ * + * @return the stack size of the item + */ + @NonNegative int stackSize(); + + /** + * Gets the max damage of the item. + * + *

Returns -1 if not set. When not set, Geyser defaults to the maximum damage of the Java item when based on a vanilla item, or uses 0 when registering a non-vanilla item.

+ * + *

Note that, to copy Java behaviour, setting the max damage value of an item to a value above 0 will set the stack size to 1. If a stack size above 1 was explicitly set, an exception will be thrown.

+ * + * @return the max damage of the item + */ + int maxDamage(); + + /** + * Gets the attack damage of the item. + * This is purely visual, and only applied to tools + * + *

Returns -1 if not set. When not set, Geyser takes the Java item attack damage when based on a vanilla item, or uses 0 when porting a modded item.

+ * + * @return the attack damage of the item + */ + int attackDamage(); + + /** + * Gets the armor type of the item. + * + *

This can be "boots", "leggings", "chestplate", or "helmet", and makes the item able to be equipped into its respective equipment slot. + * This should only be set if the Java item can be placed into the specified equipment slot.

+ * + * @return the armor type of the item + */ + @Nullable String armorType(); + + /** + * Gets the armor protection value of the item. + * + *

Only has a function when {@link CustomItemData#armorType} is set, or when the Java item is an armor item (when based on a vanilla item).

+ * + *

Returns -1 if not set. When not set, Geyser takes the Java item protection value when based on a vanilla item, or uses 0 when porting a modded item.

+ * + * @return the armor protection value of the item + */ + int protectionValue(); + + /** + * Gets if the item is a hat. This is used to determine if the item should be rendered on the player's head, and + * normally allow the player to equip it. This is not meant for armor. + * + * @return if the item is a hat + */ + boolean isHat(); + + /** + * Gets if the item is a foil. This is used to determine if the item should be rendered with an enchantment glint effect. + * + * @return if the item is a foil + */ + boolean isFoil(); + + /** + * Gets if the item is edible. + * + * @return if the item is edible + */ + boolean isEdible(); + + /** + * Gets if the food item can always be eaten. + * + * @return if the item is allowed to be eaten all the time + */ + boolean canAlwaysEat(); + static CustomItemData.Builder builder() { return GeyserApi.api().provider(CustomItemData.Builder.class); } @@ -144,6 +228,24 @@ interface Builder { Builder tags(@Nullable Set tags); + Builder stackSize(@Positive int stackSize); + + Builder maxDamage(@NonNegative int maxDamage); + + Builder attackDamage(@NonNegative int attackDamage); + + Builder armorType(@Nullable String armorType); + + Builder protectionValue(@NonNegative int protectionValue); + + Builder hat(boolean isHat); + + Builder foil(boolean isFoil); + + Builder edible(boolean isEdible); + + Builder canAlwaysEat(boolean canAlwaysEat); + CustomItemData build(); } } 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 2c283780cc1..51d6a984c91 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 @@ -26,6 +26,7 @@ package org.geysermc.geyser.api.item.custom; import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.GeyserApi; @@ -43,35 +44,6 @@ public interface NonVanillaCustomItemData extends CustomItemData { */ @NonNull String identifier(); - /** - * Gets the java item id of the item. - * - * @return the java item id of the item - */ - @NonNegative int javaId(); - - /** - * Gets the stack size of the item. - * - * @return the stack size of the item - */ - @NonNegative int stackSize(); - - /** - * Gets the max damage of the item. - * - * @return the max damage of the item - */ - int maxDamage(); - - /** - * Gets the attack damage of the item. - * This is purely visual, and only applied to tools - * - * @return the attack damage of the item - */ - int attackDamage(); - /** * Gets the tool type of the item. * @@ -87,18 +59,11 @@ public interface NonVanillaCustomItemData extends CustomItemData { @Nullable String toolTier(); /** - * Gets the armor type of the item. - * - * @return the armor type of the item - */ - @Nullable String armorType(); - - /** - * Gets the armor protection value of the item. + * Gets the java item id of the item. * - * @return the armor protection value of the item + * @return the java item id of the item */ - int protectionValue(); + @NonNegative int javaId(); /** * Gets the item's translation string. @@ -115,40 +80,18 @@ public interface NonVanillaCustomItemData extends CustomItemData { @Nullable Set repairMaterials(); /** - * Gets if the item is a hat. This is used to determine if the item should be rendered on the player's head, and - * normally allow the player to equip it. This is not meant for armor. - * - * @return if the item is a hat - */ - boolean isHat(); - - /** - * Gets if the item is a foil. This is used to determine if the item should be rendered with an enchantment glint effect. - * - * @return if the item is a foil - */ - boolean isFoil(); - - /** - * Gets if the item is edible. - * - * @return if the item is edible - */ - boolean isEdible(); - - /** - * Gets if the food item can always be eaten. + * Gets if the item is chargable, like a bow. * - * @return if the item is allowed to be eaten all the time + * @return if the item should act like a chargable item */ - boolean canAlwaysEat(); + boolean isChargeable(); /** - * Gets if the item is chargable, like a bow. + * Gets the block the item places. * - * @return if the item should act like a chargable item + * @return the block the item places */ - boolean isChargeable(); + String block(); /** * @deprecated Use {@link #displayHandheld()} instead. @@ -161,13 +104,6 @@ default boolean isTool() { return displayHandheld(); } - /** - * Gets the block the item places. - * - * @return the block the item places - */ - String block(); - static NonVanillaCustomItemData.Builder builder() { return GeyserApi.api().provider(NonVanillaCustomItemData.Builder.class); } @@ -176,40 +112,63 @@ interface Builder extends CustomItemData.Builder { @Override Builder name(@NonNull String name); - Builder identifier(@NonNull String identifier); + @Override + Builder customItemOptions(@NonNull CustomItemOptions customItemOptions); - Builder javaId(@NonNegative int javaId); + @Override + Builder displayName(@NonNull String displayName); - Builder stackSize(@NonNegative int stackSize); + @Override + Builder icon(@NonNull String icon); - Builder maxDamage(int maxDamage); + @Override + Builder allowOffhand(boolean allowOffhand); - Builder attackDamage(int attackDamage); + @Override + Builder displayHandheld(boolean displayHandheld); - Builder toolType(@Nullable String toolType); + @Override + Builder creativeCategory(int creativeCategory); - Builder toolTier(@Nullable String toolTier); + @Override + Builder creativeGroup(@Nullable String creativeGroup); - Builder armorType(@Nullable String armorType); + @Override + Builder textureSize(int textureSize); - Builder protectionValue(int protectionValue); + @Override + Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets); - Builder translationString(@Nullable String translationString); + @Override + Builder tags(@Nullable Set tags); - Builder repairMaterials(@Nullable Set repairMaterials); + @Override + Builder stackSize(@Positive int stackSize); + + @Override + Builder maxDamage(@NonNegative int maxDamage); + + @Override + Builder attackDamage(@NonNegative int attackDamage); + + @Override + Builder armorType(@Nullable String armorType); + @Override + Builder protectionValue(@NonNegative int protectionValue); + + @Override Builder hat(boolean isHat); + @Override Builder foil(boolean isFoil); + @Override Builder edible(boolean isEdible); + @Override Builder canAlwaysEat(boolean canAlwaysEat); - Builder chargeable(boolean isChargeable); - - Builder block(String block); - /** * @deprecated Use {@link #displayHandheld(boolean)} instead. */ @@ -218,35 +177,21 @@ default Builder tool(boolean isTool) { return displayHandheld(isTool); } - @Override - Builder creativeCategory(int creativeCategory); - - @Override - Builder creativeGroup(@Nullable String creativeGroup); - - @Override - Builder customItemOptions(@NonNull CustomItemOptions customItemOptions); + Builder identifier(@NonNull String identifier); - @Override - Builder displayName(@NonNull String displayName); + Builder javaId(@NonNegative int javaId); - @Override - Builder icon(@NonNull String icon); + Builder toolType(@Nullable String toolType); - @Override - Builder allowOffhand(boolean allowOffhand); + Builder toolTier(@Nullable String toolTier); - @Override - Builder displayHandheld(boolean displayHandheld); + Builder translationString(@Nullable String translationString); - @Override - Builder textureSize(int textureSize); + Builder repairMaterials(@Nullable Set repairMaterials); - @Override - Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets); + Builder chargeable(boolean isChargeable); - @Override - Builder tags(@Nullable Set tags); + Builder block(String block); NonVanillaCustomItemData build(); } diff --git a/core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemData.java b/core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemData.java index a2054f78a6b..28fedb1452b 100644 --- a/core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemData.java +++ b/core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemData.java @@ -25,8 +25,11 @@ package org.geysermc.geyser.item; +import java.util.List; import lombok.EqualsAndHashCode; import lombok.ToString; +import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.item.custom.CustomItemData; @@ -41,6 +44,8 @@ @EqualsAndHashCode @ToString public class GeyserCustomItemData implements CustomItemData { + private static final List VALID_ARMOR_TYPES = List.of("boots", "leggings", "chestplate", "helmet"); + private final String name; private final CustomItemOptions customItemOptions; private final String displayName; @@ -52,29 +57,37 @@ public class GeyserCustomItemData implements CustomItemData { private final int textureSize; private final CustomRenderOffsets renderOffsets; private final Set tags; + private final int stackSize; + private final int maxDamage; + private final int attackDamage; + private final String armorType; + private final int protectionValue; + private final boolean isHat; + private final boolean isFoil; + private final boolean isEdible; + private final boolean canAlwaysEat; - public GeyserCustomItemData(String name, - CustomItemOptions customItemOptions, - String displayName, - String icon, - boolean allowOffhand, - boolean displayHandheld, - OptionalInt creativeCategory, - String creativeGroup, - int textureSize, - CustomRenderOffsets renderOffsets, - Set tags) { - this.name = name; - this.customItemOptions = customItemOptions; - this.displayName = displayName; - this.icon = icon; - this.allowOffhand = allowOffhand; - this.displayHandheld = displayHandheld; - this.creativeCategory = creativeCategory; - this.creativeGroup = creativeGroup; - this.textureSize = textureSize; - this.renderOffsets = renderOffsets; - this.tags = tags; + public GeyserCustomItemData(Builder builder) { + this.name = builder.name; + this.customItemOptions = builder.customItemOptions; + this.displayName = builder.displayName; + this.icon = builder.icon; + this.allowOffhand = builder.allowOffhand; + this.displayHandheld = builder.displayHandheld; + this.creativeCategory = builder.creativeCategory; + this.creativeGroup = builder.creativeGroup; + this.textureSize = builder.textureSize; + this.renderOffsets = builder.renderOffsets; + this.tags = builder.tags; + this.stackSize = builder.stackSize; + this.maxDamage = builder.maxDamage; + this.attackDamage = builder.attackDamage; + this.armorType = builder.armorType; + this.protectionValue = builder.protectionValue; + this.isHat = builder.hat; + this.isFoil = builder.foil; + this.isEdible = builder.edible; + this.canAlwaysEat = builder.canAlwaysEat; } @Override @@ -132,6 +145,51 @@ public CustomRenderOffsets renderOffsets() { return tags; } + @Override + public int stackSize() { + return stackSize; + } + + @Override + public int maxDamage() { + return maxDamage; + } + + @Override + public int attackDamage() { + return attackDamage; + } + + @Override + public @Nullable String armorType() { + return armorType; + } + + @Override + public int protectionValue() { + return protectionValue; + } + + @Override + public boolean isHat() { + return isHat; + } + + @Override + public boolean isFoil() { + return isFoil; + } + + @Override + public boolean isEdible() { + return isEdible; + } + + @Override + public boolean canAlwaysEat() { + return canAlwaysEat; + } + public static class Builder implements CustomItemData.Builder { protected String name = null; protected CustomItemOptions customItemOptions = null; @@ -144,6 +202,15 @@ public static class Builder implements CustomItemData.Builder { protected int textureSize = 16; protected CustomRenderOffsets renderOffsets = null; protected Set tags = new HashSet<>(); + private int stackSize = 0; + private int maxDamage = -1; + private int attackDamage = -1; + private String armorType = null; + private int protectionValue = -1; + private boolean hat = false; + private boolean foil = false; + private boolean edible = false; + private boolean canAlwaysEat = false; @Override public Builder name(@NonNull String name) { @@ -211,6 +278,89 @@ public Builder tags(@Nullable Set tags) { return this; } + @Override + public Builder stackSize(@Positive int stackSize) { + if (stackSize < 1) { + throw new IllegalArgumentException("Stack size cannot be below 1 (" + stackSize + " was given)"); + } else if (stackSize > 1) { + if (this.maxDamage > 0) { + throw new IllegalArgumentException("Stack size cannot be above 1 when max damage is above 0 (" + stackSize + " was given)"); + } + // Explicitly set max damage to 0 instead of falling back to the Java vanilla item value + this.maxDamage = 0; + } + + this.stackSize = stackSize; + return this; + } + + @Override + public Builder maxDamage(@NonNegative int maxDamage) { + if (maxDamage < 0) { + throw new IllegalArgumentException("Max damage cannot be below 0 (" + maxDamage + " was given)"); + } else if (maxDamage > 0) { + if (this.stackSize > 1) { + throw new IllegalArgumentException("Max damage cannot be above 0 when stack size is above 1 (" + maxDamage + " was given)"); + } + // Explicitly set stack size to 1 instead of falling back to the Java vanilla item value + this.stackSize = 1; + } + + this.maxDamage = maxDamage; + return this; + } + + @Override + public Builder attackDamage(@NonNegative int attackDamage) { + if (attackDamage < 0) { + throw new IllegalArgumentException("Protection value cannot be below 0 (" + attackDamage + " was given)"); + } + this.attackDamage = attackDamage; + return this; + } + + @Override + public Builder armorType(@Nullable String armorType) { + if (!VALID_ARMOR_TYPES.contains(armorType)) { + throw new IllegalArgumentException("Invalid armor type " + armorType + "! Can be \"boots\", \"leggings\", \"chestplate\", or \"helmet\""); + } + this.armorType = armorType; + return this; + } + + @Override + public Builder protectionValue(@NonNegative int protectionValue) { + if (protectionValue < 0) { + throw new IllegalArgumentException("Protection value cannot be below 0 (" + protectionValue + " was given)"); + } + this.protectionValue = protectionValue; + return this; + } + + @Override + public Builder hat(boolean isHat) { + this.hat = isHat; + return this; + } + + @Override + public Builder foil(boolean isFoil) { + this.foil = isFoil; + return this; + } + + @Override + public Builder edible(boolean isEdible) { + this.edible = isEdible; + return this; + } + + @Override + public Builder canAlwaysEat(boolean canAlwaysEat) { + this.canAlwaysEat = canAlwaysEat; + return this; + } + @Override public CustomItemData build() { if (this.name == null || this.customItemOptions == null) { @@ -223,8 +373,8 @@ public CustomItemData build() { if (this.icon == null) { this.icon = this.name; } - return new GeyserCustomItemData(this.name, this.customItemOptions, this.displayName, this.icon, this.allowOffhand, - this.displayHandheld, this.creativeCategory, this.creativeGroup, this.textureSize, this.renderOffsets, this.tags); + + return new GeyserCustomItemData(this); } } } diff --git a/core/src/main/java/org/geysermc/geyser/item/GeyserNonVanillaCustomItemData.java b/core/src/main/java/org/geysermc/geyser/item/GeyserNonVanillaCustomItemData.java index 9c9269df363..5ae41479c1f 100644 --- a/core/src/main/java/org/geysermc/geyser/item/GeyserNonVanillaCustomItemData.java +++ b/core/src/main/java/org/geysermc/geyser/item/GeyserNonVanillaCustomItemData.java @@ -27,6 +27,8 @@ import lombok.EqualsAndHashCode; import lombok.ToString; +import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.item.custom.CustomItemOptions; @@ -40,44 +42,22 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData implements NonVanillaCustomItemData { private final String identifier; private final int javaId; - private final int stackSize; - private final int maxDamage; - private final int attackDamage; private final String toolType; private final String toolTier; - private final String armorType; - private final int protectionValue; private final String translationString; private final Set repairMaterials; - private final boolean isHat; - private final boolean isFoil; - private final boolean isTool; - private final boolean isEdible; - private final boolean canAlwaysEat; private final boolean isChargeable; private final String block; public GeyserNonVanillaCustomItemData(Builder builder) { - super(builder.name, builder.customItemOptions, builder.displayName, builder.icon, builder.allowOffhand, - builder.displayHandheld, builder.creativeCategory, builder.creativeGroup, - builder.textureSize, builder.renderOffsets, builder.tags); + super(builder); this.identifier = builder.identifier; this.javaId = builder.javaId; - this.stackSize = builder.stackSize; - this.maxDamage = builder.maxDamage; - this.attackDamage = builder.attackDamage; this.toolType = builder.toolType; this.toolTier = builder.toolTier; - this.armorType = builder.armorType; - this.protectionValue = builder.protectionValue; this.translationString = builder.translationString; this.repairMaterials = builder.repairMaterials; - this.isHat = builder.hat; - this.isFoil = builder.foil; - this.isTool = builder.tool; - this.isEdible = builder.edible; - this.canAlwaysEat = builder.canAlwaysEat; this.isChargeable = builder.chargeable; this.block = builder.block; } @@ -92,21 +72,6 @@ public int javaId() { return javaId; } - @Override - public int stackSize() { - return stackSize; - } - - @Override - public int maxDamage() { - return maxDamage; - } - - @Override - public int attackDamage() { - return attackDamage; - } - @Override public String toolType() { return toolType; @@ -117,16 +82,6 @@ public String toolTier() { return toolTier; } - @Override - public @Nullable String armorType() { - return armorType; - } - - @Override - public int protectionValue() { - return protectionValue; - } - @Override public String translationString() { return translationString; @@ -137,26 +92,6 @@ public Set repairMaterials() { return repairMaterials; } - @Override - public boolean isHat() { - return isHat; - } - - @Override - public boolean isFoil() { - return isFoil; - } - - @Override - public boolean isEdible() { - return isEdible; - } - - @Override - public boolean canAlwaysEat() { - return canAlwaysEat; - } - @Override public boolean isChargeable() { return isChargeable; @@ -170,28 +105,10 @@ public String block() { public static class Builder extends GeyserCustomItemData.Builder implements NonVanillaCustomItemData.Builder { private String identifier = null; private int javaId = -1; - - private int stackSize = 64; - - private int maxDamage = 0; - - private int attackDamage = 0; - private String toolType = null; private String toolTier = null; - - private String armorType = null; - private int protectionValue = 0; - private String translationString; - private Set repairMaterials; - - private boolean hat = false; - private boolean foil = false; - private boolean tool = false; - private boolean edible = false; - private boolean canAlwaysEat = false; private boolean chargeable = false; private String block = null; @@ -206,6 +123,16 @@ public Builder customItemOptions(@NonNull CustomItemOptions customItemOptions) { return this; } + @Override + public Builder displayName(@NonNull String displayName) { + return (Builder) super.displayName(displayName); + } + + @Override + public Builder icon(@NonNull String icon) { + return (Builder) super.icon(icon); + } + @Override public Builder allowOffhand(boolean allowOffhand) { return (Builder) super.allowOffhand(allowOffhand); @@ -217,13 +144,13 @@ public Builder displayHandheld(boolean displayHandheld) { } @Override - public Builder displayName(@NonNull String displayName) { - return (Builder) super.displayName(displayName); + public Builder creativeCategory(int creativeCategory) { + return (Builder) super.creativeCategory(creativeCategory); } @Override - public Builder icon(@NonNull String icon) { - return (Builder) super.icon(icon); + public Builder creativeGroup(@Nullable String creativeGroup) { + return (Builder) super.creativeGroup(creativeGroup); } @Override @@ -242,102 +169,83 @@ public Builder tags(@Nullable Set tags) { } @Override - public Builder identifier(@NonNull String identifier) { - this.identifier = identifier; - return this; + public Builder stackSize(@Positive int stackSize) { + return (Builder) super.stackSize(stackSize); } @Override - public Builder javaId(int javaId) { - this.javaId = javaId; - return this; + public Builder maxDamage(@NonNegative int maxDamage) { + return (Builder) super.maxDamage(maxDamage); } @Override - public Builder stackSize(int stackSize) { - this.stackSize = stackSize; - return this; + public Builder attackDamage(@NonNegative int attackDamage) { + return (Builder) super.attackDamage(attackDamage); } @Override - public Builder maxDamage(int maxDamage) { - this.maxDamage = maxDamage; - return this; + public Builder armorType(@Nullable String armorType) { + return (Builder) super.armorType(armorType); } @Override - public NonVanillaCustomItemData.Builder attackDamage(int attackDamage) { - this.attackDamage = attackDamage; - return this; + public Builder protectionValue(@NonNegative int protectionValue) { + return (Builder) super.protectionValue(protectionValue); } @Override - public Builder toolType(@Nullable String toolType) { - this.toolType = toolType; - return this; + public Builder hat(boolean isHat) { + return (Builder) super.hat(isHat); } @Override - public Builder toolTier(@Nullable String toolTier) { - this.toolTier = toolTier; - return this; + public Builder foil(boolean isFoil) { + return (Builder) super.foil(isFoil); } @Override - public Builder armorType(@Nullable String armorType) { - this.armorType = armorType; - return this; + public Builder edible(boolean isEdible) { + return (Builder) super.edible(isEdible); } @Override - public Builder protectionValue(int protectionValue) { - this.protectionValue = protectionValue; - return this; + public Builder canAlwaysEat(boolean canAlwaysEat) { + return (Builder) super.canAlwaysEat(canAlwaysEat); } @Override - public Builder translationString(@Nullable String translationString) { - this.translationString = translationString; + public Builder identifier(@NonNull String identifier) { + this.identifier = identifier; return this; } @Override - public Builder repairMaterials(@Nullable Set repairMaterials) { - this.repairMaterials = repairMaterials; + public Builder javaId(int javaId) { + this.javaId = javaId; return this; } @Override - public Builder creativeCategory(int creativeCategory) { - return (Builder) super.creativeCategory(creativeCategory); - } - - @Override - public Builder creativeGroup(@Nullable String creativeGroup) { - return (Builder) super.creativeGroup(creativeGroup); - } - - @Override - public Builder hat(boolean isHat) { - this.hat = isHat; + public Builder toolType(@Nullable String toolType) { + this.toolType = toolType; return this; } @Override - public Builder foil(boolean isFoil) { - this.foil = isFoil; + public Builder toolTier(@Nullable String toolTier) { + this.toolTier = toolTier; return this; } @Override - public Builder edible(boolean isEdible) { - this.edible = isEdible; + public Builder translationString(@Nullable String translationString) { + this.translationString = translationString; return this; } @Override - public Builder canAlwaysEat(boolean canAlwaysEat) { - this.canAlwaysEat = canAlwaysEat; + public Builder repairMaterials(@Nullable Set repairMaterials) { + this.repairMaterials = repairMaterials; return this; } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java index b5e25a4ba5c..5560fdb6de2 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java @@ -213,6 +213,42 @@ public CustomItemData readItemMappingEntry(JsonNode node) throws InvalidCustomMa customItemData.tags(tagsSet); } + if (node.has("stack_size")) { + customItemData.stackSize(node.get("stack_size").asInt()); + } + + if (node.has("max_damage")) { + customItemData.maxDamage(node.get("max_damage").asInt()); + } + + if (node.has("attack_damage")) { + customItemData.attackDamage(node.get("attack_damage").asInt()); + } + + if (node.has("armor_type")) { + customItemData.armorType(node.get("armor_type").asText()); + } + + if (node.has("protection_value")) { + customItemData.protectionValue(node.get("protection_value").asInt()); + } + + if (node.has("hat")) { + customItemData.hat(node.get("hat").asBoolean()); + } + + if (node.has("foil")) { + customItemData.foil(node.get("foil").asBoolean()); + } + + if (node.has("edible")) { + customItemData.edible(node.get("edible").asBoolean()); + } + + if (node.has("can_always_eat")) { + customItemData.canAlwaysEat(node.get("can_always_eat").asBoolean()); + } + return customItemData.build(); } 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 0a9c93980a7..1f7bcf506d5 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 @@ -130,8 +130,8 @@ public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItem Set repairMaterials = customItemData.repairMaterials(); Item.Builder itemBuilder = Item.builder() - .stackSize(customItemData.stackSize()) - .maxDamage(customItemData.maxDamage()); + .stackSize(customItemData.stackSize() == 0 ? 64 : customItemData.stackSize()) + .maxDamage(Math.max(customItemData.maxDamage(), 0)); Item item = new Item(customIdentifier, itemBuilder) { @Override public boolean isValidRepairItem(Item other) { @@ -151,8 +151,7 @@ public boolean isValidRepairItem(Item other) { .javaItem(item) .build(); - NbtMapBuilder builder = createComponentNbt(customItemData, customItemData.identifier(), customItemId, - customItemData.isHat(), customItemData.displayHandheld(), protocolVersion); + NbtMapBuilder builder = createComponentNbt(customItemData, customItemId, protocolVersion); ComponentItemData componentItemData = new ComponentItemData(customIdentifier, builder.build()); return new NonVanillaItemRegistration(componentItemData, item, customItemMapping); @@ -167,30 +166,50 @@ private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, I NbtMapBuilder itemProperties = NbtMap.builder(); NbtMapBuilder componentBuilder = NbtMap.builder(); - setupBasicItemInfo(javaItem.maxDamage(), javaItem.maxStackSize(), mapping.getToolType() != null || customItemData.displayHandheld(), customItemData, itemProperties, componentBuilder, protocolVersion); + setupBasicItemInfo(customItemData.maxDamage() < 0 ? javaItem.maxDamage() : customItemData.maxDamage(), + customItemData.stackSize() == 0 ? javaItem.maxStackSize() : customItemData.stackSize(), + mapping.getToolType() != null || customItemData.displayHandheld(), + customItemData, itemProperties, componentBuilder, protocolVersion); boolean canDestroyInCreative = true; - if (mapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here. - canDestroyInCreative = computeToolProperties(mapping.getToolType(), itemProperties, componentBuilder, javaItem.attackDamage()); + if (mapping.getToolType() != null) { + canDestroyInCreative = computeToolProperties(mapping.getToolType(), itemProperties, componentBuilder, + customItemData.attackDamage() == 0 ? javaItem.attackDamage() : customItemData.attackDamage()); } itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); + String armorType = null; + int protectionValue = 0; if (mapping.getArmorType() != null) { - computeArmorProperties(mapping.getArmorType(), mapping.getProtectionValue(), itemProperties, componentBuilder); + armorType = mapping.getArmorType(); + protectionValue = customItemData.protectionValue() == -1 ? mapping.getProtectionValue() : customItemData.protectionValue(); + } else if (customItemData.armorType() != null) { + armorType = customItemData.armorType(); + // Using 0 as fallback here because the Java item doesn't have an armor type - so its protection value would be 0 + protectionValue = customItemData.protectionValue() == -1 ? 0 : customItemData.protectionValue(); + } + + if (armorType != null) { + computeArmorProperties(armorType, protectionValue, itemProperties, componentBuilder); } if (mapping.getFirstBlockRuntimeId() != null) { computeBlockItemProperties(mapping.getBedrockIdentifier(), componentBuilder); } - if (mapping.isEdible()) { - computeConsumableProperties(itemProperties, componentBuilder, 1, false); + if (mapping.isEdible() || customItemData.isEdible()) { + computeConsumableProperties(itemProperties, componentBuilder, 1, + customItemData.canAlwaysEat()); } if (mapping.isEntityPlacer()) { computeEntityPlacerProperties(componentBuilder); } + if (customItemData.isFoil()) { + itemProperties.putBoolean("foil", true); + } + switch (mapping.getBedrockIdentifier()) { case "minecraft:fire_charge", "minecraft:flint_and_steel" -> computeBlockItemProperties("minecraft:fire", componentBuilder); case "minecraft:bow", "minecraft:crossbow", "minecraft:trident" -> computeChargeableProperties(itemProperties, componentBuilder, mapping.getBedrockIdentifier(), protocolVersion); @@ -200,7 +219,7 @@ private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, I } // Hardcoded on Java, and should extend to the custom item - boolean isHat = (javaItem.equals(Items.SKELETON_SKULL) || javaItem.equals(Items.WITHER_SKELETON_SKULL) + boolean isHat = (customItemData.isHat() || javaItem.equals(Items.SKELETON_SKULL) || javaItem.equals(Items.WITHER_SKELETON_SKULL) || javaItem.equals(Items.CARVED_PUMPKIN) || javaItem.equals(Items.ZOMBIE_HEAD) || javaItem.equals(Items.PIGLIN_HEAD) || javaItem.equals(Items.DRAGON_HEAD) || javaItem.equals(Items.CREEPER_HEAD) || javaItem.equals(Items.PLAYER_HEAD) @@ -213,26 +232,26 @@ private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, I return builder; } - private static NbtMapBuilder createComponentNbt(NonVanillaCustomItemData customItemData, String customItemName, - int customItemId, boolean isHat, boolean displayHandheld, int protocolVersion) { + private static NbtMapBuilder createComponentNbt(NonVanillaCustomItemData customItemData, int customItemId, int protocolVersion) { NbtMapBuilder builder = NbtMap.builder(); - builder.putString("name", customItemName) + builder.putString("name", customItemData.identifier()) .putInt("id", customItemId); NbtMapBuilder itemProperties = NbtMap.builder(); NbtMapBuilder componentBuilder = NbtMap.builder(); - setupBasicItemInfo(customItemData.maxDamage(), customItemData.stackSize(), displayHandheld, customItemData, itemProperties, componentBuilder, protocolVersion); + setupBasicItemInfo(Math.max(customItemData.maxDamage(), 0), customItemData.stackSize() == 0 ? 64 : customItemData.stackSize(), + customItemData.displayHandheld(), customItemData, itemProperties, componentBuilder, protocolVersion); boolean canDestroyInCreative = true; if (customItemData.toolType() != null) { // This is not using the isTool boolean because it is not just a render type here. - canDestroyInCreative = computeToolProperties(Objects.requireNonNull(customItemData.toolType()), itemProperties, componentBuilder, customItemData.attackDamage()); + canDestroyInCreative = computeToolProperties(Objects.requireNonNull(customItemData.toolType()), itemProperties, componentBuilder, Math.max(0, customItemData.attackDamage())); } itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative); String armorType = customItemData.armorType(); if (armorType != null) { - computeArmorProperties(armorType, customItemData.protectionValue(), itemProperties, componentBuilder); + computeArmorProperties(armorType, Math.max(0, customItemData.protectionValue()), itemProperties, componentBuilder); } if (customItemData.isEdible()) { @@ -247,7 +266,7 @@ private static NbtMapBuilder createComponentNbt(NonVanillaCustomItemData customI computeChargeableProperties(itemProperties, componentBuilder, "minecraft:" + tooltype, protocolVersion); } - computeRenderOffsets(isHat, customItemData, componentBuilder); + computeRenderOffsets(customItemData.isHat(), customItemData, componentBuilder); if (customItemData.isFoil()) { itemProperties.putBoolean("foil", true);