From 7283e380c20ba70b30cdbff725315610faf36f5c Mon Sep 17 00:00:00 2001 From: Valaphee Date: Sat, 4 Jul 2026 18:23:29 +0200 Subject: [PATCH 1/3] Make repairable behave like Java Edition --- .../CustomItemRegistryPopulator.java | 83 ++++++++++++++----- 1 file changed, 61 insertions(+), 22 deletions(-) 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 947e70c424f..d76af9dad4e 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 @@ -74,6 +74,7 @@ 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; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.HolderSet; import org.geysermc.mcprotocollib.protocol.data.game.item.component.KineticWeapon; import org.geysermc.mcprotocollib.protocol.data.game.item.component.PiercingWeapon; import org.geysermc.mcprotocollib.protocol.data.game.item.component.SwingAnimation; @@ -253,6 +254,9 @@ private static NbtMapBuilder createComponentNbt(Key itemIdentifier, CustomItemCo setupBasicItemInfo(context.definition(), context.components(), itemProperties, componentBuilder); + // Add a base item tag to the item, as in Java Edition they are still the same items and that we can behave the same in Bedrock + context.vanillaMapping().ifPresent(mapping -> addItemTag(componentBuilder, "geyser:" + mapping.getBedrockIdentifier())); + computeToolProperties(itemProperties, componentBuilder); Integer attackDamage = context.definition().components().get(GeyserItemDataComponents.ATTACK_DAMAGE); if (attackDamage != null) { @@ -266,10 +270,9 @@ private static NbtMapBuilder createComponentNbt(Key itemIdentifier, CustomItemCo boolean canDestroyInCreative = toolData == null || toolData.isCanDestroyBlocksInCreative(); computeCreativeDestroyProperties(canDestroyInCreative, itemProperties, componentBuilder); - // Using API component here because MCPL one is just an ID holder set, and we can't get identifiers from that - JavaRepairable repairable = context.definition().components().get(JavaItemDataComponents.REPAIRABLE); + HolderSet repairable = context.components().get(DataComponentTypes.REPAIRABLE); if (repairable != null) { - computeRepairableProperties(repairable, componentBuilder); + computeRepairableProperties(repairable, componentBuilder, context.vanillaMapping().map(GeyserMappingItem::getBedrockIdentifier).orElse(null)); } Equippable equippable = context.components().get(DataComponentTypes.EQUIPPABLE); @@ -444,12 +447,12 @@ private static void setupBasicItemInfo(CustomItemDefinition definition, DataComp componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", definition.displayName()).build()); // Add a Geyser tag to the item, allowing Molang queries - addItemTag(componentBuilder, Identifier.of("geyser:is_custom")); + addItemTag(componentBuilder, "geyser:is_custom"); // Add other defined tags to the item Set tags = options.tags(); for (Identifier tag : tags) { - addItemTag(componentBuilder, tag); + addItemTag(componentBuilder, tag.toString()); } itemProperties.putBoolean("allow_off_hand", options.allowOffhand()); @@ -518,21 +521,57 @@ private static void computeCreativeDestroyProperties(boolean canDestroyInCreativ /** * This method passes the Java identifiers straight to bedrock - which isn't perfect. Also doesn't work with holder sets that use a tag. */ - private static void computeRepairableProperties(JavaRepairable repairable, NbtMapBuilder componentBuilder) { - List identifiers = ((HoldersImpl) repairable.items()).identifiers(); - if (identifiers == null) { - return; + private static void computeRepairableProperties(HolderSet repairable, NbtMapBuilder componentBuilder, String baseItem) { + // TODO also map individual items and do not hardcode tag mappings + List items = repairable.getLocation() != null ? switch (repairable.getLocation().asString()) { + case "minecraft:wooden_tool_materials" -> List.of(NbtMap.builder() + .putString("tags", "q.all_tags('minecraft:planks')") + .build()); + case "minecraft:repairs_leather_armor" -> List.of(NbtMap.builder() + .putString("name", "minecraft:leather") + .build()); + case "minecraft:iron_tool_materials", "minecraft:repairs_iron_armor" -> List.of(NbtMap.builder() + .putString("name", "minecraft:iron_ingot") + .build()); + case "minecraft:gold_tool_materials", "minecraft:repairs_gold_armor" -> List.of(NbtMap.builder() + .putString("name", "minecraft:gold_ingot") + .build()); + case "minecraft:diamond_tool_materials", "minecraft:repairs_diamond_armor" -> List.of(NbtMap.builder() + .putString("name", "minecraft:diamond") + .build()); + case "minecraft:netherite_tool_materials", "minecraft:repairs_netherite_armor" -> List.of(NbtMap.builder() + .putString("name", "minecraft:netherite_ingot") + .build()); + default -> List.of(NbtMap.builder() + .putString("tags", "q.all_tags('" + repairable.getLocation().asString() + "')") + .build()); + } : null; + + List repairItems = new ArrayList<>(); + if (baseItem != null) { + repairItems.add(NbtMap.builder() + .putList("items", NbtType.COMPOUND, NbtMap.builder() + .putString("name", baseItem) + .build(), NbtMap.builder() + .putString("tags", "q.all_tags('geyser:" + baseItem + "')") + .build()) + .putCompound("repair_amount", NbtMap.builder() + .putString("expression", "math.min(q.remaining_durability + c.other->q.remaining_durability + math.floor(q.max_durability * 0.05), q.max_durability)") + .putShort("version", (short) 12) + .build()) + .build()); } - List items = identifiers.stream() - .map(identifier -> NbtMap.builder() - .putString("name", identifier.toString()) - .build()).toList(); - - componentBuilder.putCompound("minecraft:repairable", NbtMap.builder() - .putList("repair_items", NbtType.COMPOUND, NbtMap.builder() + if (items != null) { + repairItems.add(NbtMap.builder() .putList("items", NbtType.COMPOUND, items) - .putFloat("repair_amount", 0.0F) - .build()) + .putCompound("repair_amount", NbtMap.builder() + .putString("expression", "q.max_durability * 0.25") + .putShort("version", (short) 12) + .build()) + .build()); + } + componentBuilder.putCompound("minecraft:repairable", NbtMap.builder() + .putList("repair_items", NbtType.COMPOUND, repairItems) .build()); } @@ -744,15 +783,15 @@ private static boolean isUnbreakableItem(CustomItemDefinition definition) { } @SuppressWarnings("unchecked") - private static void addItemTag(NbtMapBuilder builder, Identifier tag) { + private static void addItemTag(NbtMapBuilder builder, String tag) { List tagList = (List) builder.get("item_tags"); if (tagList == null) { - builder.putList("item_tags", NbtType.STRING, tag.toString()); + builder.putList("item_tags", NbtType.STRING, tag); } else { // NbtList is immutable - if (!tagList.contains(tag.toString())) { + if (!tagList.contains(tag)) { tagList = new ArrayList<>(tagList); - tagList.add(tag.toString()); + tagList.add(tag); builder.putList("item_tags", NbtType.STRING, tagList); } } From 7bf88abe69d2b25fc70ecd27b8c37e8441961543 Mon Sep 17 00:00:00 2001 From: Valaphee Date: Sat, 4 Jul 2026 18:44:19 +0200 Subject: [PATCH 2/3] Handle it server-side --- .../CustomItemRegistryPopulator.java | 63 +++---------------- 1 file changed, 7 insertions(+), 56 deletions(-) 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 d76af9dad4e..a19f7fbe086 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 @@ -254,9 +254,6 @@ private static NbtMapBuilder createComponentNbt(Key itemIdentifier, CustomItemCo setupBasicItemInfo(context.definition(), context.components(), itemProperties, componentBuilder); - // Add a base item tag to the item, as in Java Edition they are still the same items and that we can behave the same in Bedrock - context.vanillaMapping().ifPresent(mapping -> addItemTag(componentBuilder, "geyser:" + mapping.getBedrockIdentifier())); - computeToolProperties(itemProperties, componentBuilder); Integer attackDamage = context.definition().components().get(GeyserItemDataComponents.ATTACK_DAMAGE); if (attackDamage != null) { @@ -272,7 +269,7 @@ private static NbtMapBuilder createComponentNbt(Key itemIdentifier, CustomItemCo HolderSet repairable = context.components().get(DataComponentTypes.REPAIRABLE); if (repairable != null) { - computeRepairableProperties(repairable, componentBuilder, context.vanillaMapping().map(GeyserMappingItem::getBedrockIdentifier).orElse(null)); + computeRepairableProperties(componentBuilder); } Equippable equippable = context.components().get(DataComponentTypes.EQUIPPABLE); @@ -518,60 +515,14 @@ private static void computeCreativeDestroyProperties(boolean canDestroyInCreativ .build()); } - /** - * This method passes the Java identifiers straight to bedrock - which isn't perfect. Also doesn't work with holder sets that use a tag. - */ - private static void computeRepairableProperties(HolderSet repairable, NbtMapBuilder componentBuilder, String baseItem) { - // TODO also map individual items and do not hardcode tag mappings - List items = repairable.getLocation() != null ? switch (repairable.getLocation().asString()) { - case "minecraft:wooden_tool_materials" -> List.of(NbtMap.builder() - .putString("tags", "q.all_tags('minecraft:planks')") - .build()); - case "minecraft:repairs_leather_armor" -> List.of(NbtMap.builder() - .putString("name", "minecraft:leather") - .build()); - case "minecraft:iron_tool_materials", "minecraft:repairs_iron_armor" -> List.of(NbtMap.builder() - .putString("name", "minecraft:iron_ingot") - .build()); - case "minecraft:gold_tool_materials", "minecraft:repairs_gold_armor" -> List.of(NbtMap.builder() - .putString("name", "minecraft:gold_ingot") - .build()); - case "minecraft:diamond_tool_materials", "minecraft:repairs_diamond_armor" -> List.of(NbtMap.builder() - .putString("name", "minecraft:diamond") - .build()); - case "minecraft:netherite_tool_materials", "minecraft:repairs_netherite_armor" -> List.of(NbtMap.builder() - .putString("name", "minecraft:netherite_ingot") - .build()); - default -> List.of(NbtMap.builder() - .putString("tags", "q.all_tags('" + repairable.getLocation().asString() + "')") - .build()); - } : null; - - List repairItems = new ArrayList<>(); - if (baseItem != null) { - repairItems.add(NbtMap.builder() + private static void computeRepairableProperties(NbtMapBuilder componentBuilder) { + componentBuilder.putCompound("minecraft:repairable", NbtMap.builder() + .putList("repair_items", NbtType.COMPOUND, NbtMap.builder() .putList("items", NbtType.COMPOUND, NbtMap.builder() - .putString("name", baseItem) - .build(), NbtMap.builder() - .putString("tags", "q.all_tags('geyser:" + baseItem + "')") - .build()) - .putCompound("repair_amount", NbtMap.builder() - .putString("expression", "math.min(q.remaining_durability + c.other->q.remaining_durability + math.floor(q.max_durability * 0.05), q.max_durability)") - .putShort("version", (short) 12) - .build()) - .build()); - } - if (items != null) { - repairItems.add(NbtMap.builder() - .putList("items", NbtType.COMPOUND, items) - .putCompound("repair_amount", NbtMap.builder() - .putString("expression", "q.max_durability * 0.25") - .putShort("version", (short) 12) + .putString("tags", "1") .build()) - .build()); - } - componentBuilder.putCompound("minecraft:repairable", NbtMap.builder() - .putList("repair_items", NbtType.COMPOUND, repairItems) + .putFloat("repair_amount", 1.0F) + .build()) .build()); } From 77e3ee67e7ff0d84556651a1b54c4b75dbcaa8f5 Mon Sep 17 00:00:00 2001 From: Valaphee Date: Fri, 10 Jul 2026 19:05:27 +0200 Subject: [PATCH 3/3] Add TODO for repairable properties restriction Added a TODO comment to restrict repairs to the same Java item and its repair materials. --- .../geyser/registry/populator/CustomItemRegistryPopulator.java | 1 + 1 file changed, 1 insertion(+) 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 a19f7fbe086..219fb4e58ec 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 @@ -516,6 +516,7 @@ private static void computeCreativeDestroyProperties(boolean canDestroyInCreativ } private static void computeRepairableProperties(NbtMapBuilder componentBuilder) { + // TODO: only allow repair by the same Java item (not Bedrock item) and repair materials (needs mapping item tags). componentBuilder.putCompound("minecraft:repairable", NbtMap.builder() .putList("repair_items", NbtType.COMPOUND, NbtMap.builder() .putList("items", NbtType.COMPOUND, NbtMap.builder()