diff --git a/core/src/main/java/org/geysermc/geyser/item/exception/InvalidItemComponentsException.java b/core/src/main/java/org/geysermc/geyser/item/exception/InvalidItemComponentsException.java new file mode 100644 index 000000000..c2109dd8c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/exception/InvalidItemComponentsException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 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.exception; + +public class InvalidItemComponentsException extends Exception { + + public InvalidItemComponentsException(String message) { + super(message); + } +} 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 26bb3b888..3a83f62fd 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 @@ -48,6 +48,7 @@ import org.geysermc.geyser.api.util.Identifier; import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.components.WearableSlot; +import org.geysermc.geyser.item.exception.InvalidItemComponentsException; import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.registry.mappings.MappingsConfigReader; import org.geysermc.geyser.registry.type.GeyserMappingItem; @@ -126,7 +127,9 @@ public class CustomItemRegistryPopulator { } public static GeyserCustomMappingData registerCustomItem(String customItemName, Item javaItem, GeyserMappingItem mapping, - CustomItemDefinition customItemDefinition, int bedrockId) { + CustomItemDefinition customItemDefinition, int bedrockId) throws InvalidItemComponentsException { + checkComponents(customItemDefinition, javaItem); + ItemDefinition itemDefinition = new SimpleItemDefinition(customItemName, bedrockId, true); NbtMapBuilder builder = createComponentNbt(customItemDefinition, javaItem, mapping, customItemName, bedrockId); @@ -190,6 +193,24 @@ public class CustomItemRegistryPopulator { return Optional.empty(); } + /** + * Check for illegal combinations of item components that can be specified in the custom item API. + * + *
Note that, this method only checks for illegal combinations of item components. It is expected that the values of the components separately have + * already been validated (for example, it is expected that stack size is in the range [1, 99]).
+ */ + private static void checkComponents(CustomItemDefinition definition, Item javaItem) throws InvalidItemComponentsException { + DataComponents components = patchDataComponents(javaItem, definition); + int stackSize = components.getOrDefault(DataComponentType.MAX_STACK_SIZE, 0); + int maxDamage = components.getOrDefault(DataComponentType.MAX_DAMAGE, 0); + + if (components.get(DataComponentType.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"); + } + } + public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItemData customItemData, int customItemId, int protocolVersion) { // TODO return null; @@ -374,8 +395,6 @@ public class CustomItemRegistryPopulator { } private static void computeArmorProperties(Equippable equippable, /*String armorType, int protectionValue,*/ NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) { - // TODO set stack size to 1 when armour is effective as Bedrock doesn't allow armour with a stack size above 1 - // This should also be noted in the docs and maybe we should not allow armour with a stack size above 1 at all to prevent issues int protectionValue = 0; // TODO protection value, enchantable stuff and armour type? switch (equippable.slot()) { 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 9596a38d8..bd6bfb668 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 @@ -68,6 +68,7 @@ import org.geysermc.geyser.inventory.item.StoredItemMappings; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.components.Rarity; +import org.geysermc.geyser.item.exception.InvalidItemComponentsException; import org.geysermc.geyser.item.type.BlockItem; import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.level.block.property.Properties; @@ -483,7 +484,7 @@ public class ItemRegistryPopulator { for (CustomItemDefinition customItem : customItemsToLoad) { int customProtocolId = nextFreeBedrockId++; - String customItemName = customItem instanceof NonVanillaCustomItemData nonVanillaItem ? nonVanillaItem.identifier() : customItem.bedrockIdentifier().toString(); // TODO non vanilla stuff + String customItemName = customItem instanceof NonVanillaCustomItemData nonVanillaItem ? nonVanillaItem.identifier() : customItem.bedrockIdentifier().toString(); // TODO non vanilla stuff, simplify this maybe cause it's all identifiers now if (!registeredItemNames.add(customItemName)) { if (firstMappingsPass) { GeyserImpl.getInstance().getLogger().error("Custom item name '" + customItemName + "' already exists and was registered again! Skipping..."); @@ -491,24 +492,30 @@ public class ItemRegistryPopulator { continue; } - GeyserCustomMappingData customMapping = CustomItemRegistryPopulator.registerCustomItem( - customItemName, javaItem, mappingItem, customItem, customProtocolId); + try { + GeyserCustomMappingData customMapping = CustomItemRegistryPopulator.registerCustomItem( + customItemName, javaItem, mappingItem, customItem, customProtocolId); - if (customItem.bedrockOptions().creativeCategory() != CreativeCategory.NONE) { - creativeItems.add(ItemData.builder() + if (customItem.bedrockOptions().creativeCategory() != CreativeCategory.NONE) { + creativeItems.add(ItemData.builder() .netId(creativeNetId.incrementAndGet()) .definition(customMapping.itemDefinition()) .blockDefinition(null) .count(1) .build()); + } + + // ComponentItemData - used to register some custom properties + componentItemData.add(customMapping.componentItemData()); + customItemDefinitions.put(identifierToKey(customItem.model()), customMapping); + registry.put(customMapping.integerId(), customMapping.itemDefinition()); + + customIdMappings.put(customMapping.integerId(), customMapping.stringId()); + } catch (InvalidItemComponentsException exception) { + if (firstMappingsPass) { + GeyserImpl.getInstance().getLogger().error("Not registering custom item " + customItem.bedrockIdentifier() + "!", exception); + } } - - // ComponentItemData - used to register some custom properties - componentItemData.add(customMapping.componentItemData()); - customItemDefinitions.put(identifierToKey(customItem.model()), customMapping); - registry.put(customMapping.integerId(), customMapping.itemDefinition()); - - customIdMappings.put(customMapping.integerId(), customMapping.stringId()); } } else { customItemDefinitions = null;