diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CustomItemTranslator.java index 29aa2f748..82a8c9de1 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CustomItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/CustomItemTranslator.java @@ -51,8 +51,9 @@ final class CustomItemTranslator { } int customModelData = nbt.get("CustomModelData") instanceof IntTag customModelDataTag ? customModelDataTag.getValue() : 0; - int damage = nbt.get("Damage") instanceof IntTag damageTag ? damageTag.getValue() : 0; - boolean unbreakable = !isDamaged(mapping, nbt, damage); + boolean checkDamage = mapping.getMaxDamage() > 0; + int damage = !checkDamage ? 0 : nbt.get("Damage") instanceof IntTag damageTag ? damageTag.getValue() : 0; + boolean unbreakable = checkDamage && !isDamaged(nbt, damage); for (ObjectIntPair mappingTypes : customMappings) { CustomItemOptions options = mappingTypes.key(); @@ -67,8 +68,21 @@ final class CustomItemTranslator { // The same behavior exists for Damage (in fraction form instead of whole numbers), // and Damaged/Unbreakable handles no damage as 0f and damaged as 1f. - if (unbreakable && options.unbreakable() != TriState.TRUE) { - continue; + if (checkDamage) { + if (unbreakable && options.unbreakable() == TriState.FALSE) { + continue; + } + + OptionalInt damagePredicate = options.damagePredicate(); + if (damagePredicate.isPresent() && damage < damagePredicate.getAsInt()) { + continue; + } + } else { + if (options.unbreakable() != TriState.NOT_SET || options.damagePredicate().isPresent()) { + // These will never match on this item. 1.19.2 behavior + // Maybe move this to CustomItemRegistryPopulator since it'll be the same for every item? If so, add a test. + continue; + } } OptionalInt customModelDataOption = options.customModelData(); @@ -76,11 +90,6 @@ final class CustomItemTranslator { continue; } - OptionalInt damagePredicate = options.damagePredicate(); - if (damagePredicate.isPresent() && damage < damagePredicate.getAsInt()) { - continue; - } - return mappingTypes.valueInt(); } return -1; @@ -88,16 +97,15 @@ final class CustomItemTranslator { /* These two functions are based off their Mojmap equivalents from 1.19.2 */ - private static boolean isDamaged(ItemMapping mapping, CompoundTag nbt, int damage) { - return isDamagableItem(mapping, nbt) && damage > 0; + private static boolean isDamaged(CompoundTag nbt, int damage) { + return isDamagableItem(nbt) && damage > 0; } - private static boolean isDamagableItem(ItemMapping mapping, CompoundTag nbt) { - if (mapping.getMaxDamage() > 0) { - Tag unbreakableTag = nbt.get("Unbreakable"); - return unbreakableTag != null && unbreakableTag.getValue() instanceof Number number && number.byteValue() == 0; - } - return false; + private static boolean isDamagableItem(CompoundTag nbt) { + // mapping.getMaxDamage > 0 should also be checked (return false if not true) but we already check prior to this function + Tag unbreakableTag = nbt.get("Unbreakable"); + // Tag must either not be present or be set to false + return unbreakableTag == null || !(unbreakableTag.getValue() instanceof Number number) || number.byteValue() == 0; } private CustomItemTranslator() { diff --git a/core/src/test/java/org/geysermc/geyser/translator/inventory/item/CustomItemsTest.java b/core/src/test/java/org/geysermc/geyser/translator/inventory/item/CustomItemsTest.java index 900feef72..6870558d0 100644 --- a/core/src/test/java/org/geysermc/geyser/translator/inventory/item/CustomItemsTest.java +++ b/core/src/test/java/org/geysermc/geyser/translator/inventory/item/CustomItemsTest.java @@ -40,11 +40,14 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import java.util.List; import java.util.OptionalInt; public class CustomItemsTest { private ItemMapping testMappingWithDamage; private Object2IntMap tagToCustomItemWithDamage; + private ItemMapping testMappingWithNoDamage; + private Object2IntMap tagToCustomItemWithNoDamage; @Before public void setup() { @@ -54,9 +57,11 @@ public class CustomItemsTest { CustomItemOptions d = new GeyserCustomItemOptions(TriState.TRUE, OptionalInt.empty(), OptionalInt.of(8)); CustomItemOptions e = new GeyserCustomItemOptions(TriState.FALSE, OptionalInt.empty(), OptionalInt.of(12)); CustomItemOptions f = new GeyserCustomItemOptions(TriState.FALSE, OptionalInt.of(8), OptionalInt.of(6)); + CustomItemOptions g = new GeyserCustomItemOptions(TriState.NOT_SET, OptionalInt.of(20), OptionalInt.empty()); Object2IntMap optionsToId = new Object2IntArrayMap<>(); // Order here is important, hence why we're using an array map + optionsToId.put(g, 7); optionsToId.put(f, 6); optionsToId.put(e, 5); optionsToId.put(d, 4); @@ -72,38 +77,70 @@ public class CustomItemsTest { tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a)); tag = new CompoundTag(""); - tag.put(new IntTag("CustomModelData", 3)); - tag.put(new ByteTag("Unbreakable", (byte) 1)); + addCustomModelData(20, tag); + // Test that an unbreakable item isn't tested for Damaged if there is no damaged predicate + tagToCustomItemWithDamage.put(tag, optionsToId.getInt(g)); + + tag = new CompoundTag(""); + addCustomModelData(3, tag); + setUnbreakable(true, tag); tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a)); tag = new CompoundTag(""); - tag.put(new IntTag("Damage", 16)); - tag.put(new ByteTag("Unbreakable", (byte) 0)); + addDamage(16, tag); + setUnbreakable(false, tag); tagToCustomItemWithDamage.put(tag, optionsToId.getInt(e)); tag = new CompoundTag(""); - tag.put(new IntTag("CustomModelData", 7)); - tag.put(new IntTag("Damage", 6)); - tag.put(new ByteTag("Unbreakable", (byte) 0)); + addCustomModelData(7, tag); + addDamage(6, tag); + setUnbreakable(false, tag); tagToCustomItemWithDamage.put(tag, optionsToId.getInt(c)); tag = new CompoundTag(""); - tag.put(new IntTag("CustomModelData", 8)); - tag.put(new IntTag("Damage", 6)); - tag.put(new ByteTag("Unbreakable", (byte) 1)); + addCustomModelData(9, tag); + addDamage(6, tag); + setUnbreakable(true, tag); tagToCustomItemWithDamage.put(tag, optionsToId.getInt(a)); tag = new CompoundTag(""); - tag.put(new IntTag("CustomModelData", 9)); - tag.put(new IntTag("Damage", 6)); - tag.put(new ByteTag("Unbreakable", (byte) 0)); + addCustomModelData(9, tag); + addDamage(6, tag); + setUnbreakable(false, tag); tagToCustomItemWithDamage.put(tag, optionsToId.getInt(f)); + List> customItemOptions = optionsToId.object2IntEntrySet().stream().map(entry -> ObjectIntPair.of(entry.getKey(), entry.getIntValue())).toList(); + testMappingWithDamage = ItemMapping.builder() - .customItemOptions(optionsToId.object2IntEntrySet().stream().map(entry -> ObjectIntPair.of(entry.getKey(), entry.getIntValue())).toList()) + .customItemOptions(customItemOptions) .maxDamage(100) .build(); - // Later, possibly add a condition with a mapping with no damage + + // Test differences with items with no max damage + + tagToCustomItemWithNoDamage = new Object2IntOpenHashMap<>(); + + tag = new CompoundTag(""); + tag.put(new IntTag("CustomModelData", 2)); + // Damage predicates existing mean an item will never match if the item mapping has no max damage + tagToCustomItemWithNoDamage.put(tag, -1); + + testMappingWithNoDamage = ItemMapping.builder() + .customItemOptions(customItemOptions) + .maxDamage(0) + .build(); + } + + private void addCustomModelData(int value, CompoundTag tag) { + tag.put(new IntTag("CustomModelData", value)); + } + + private void addDamage(int value, CompoundTag tag) { + tag.put(new IntTag("Damage", value)); + } + + private void setUnbreakable(boolean value, CompoundTag tag) { + tag.put(new ByteTag("Unbreakable", (byte) (value ? 1 : 0))); } @Test @@ -112,5 +149,10 @@ public class CustomItemsTest { int id = CustomItemTranslator.getCustomItem(entry.getKey(), this.testMappingWithDamage); Assert.assertEquals(entry.getKey() + " did not produce the correct custom item", entry.getIntValue(), id); } + + for (Object2IntMap.Entry entry : this.tagToCustomItemWithNoDamage.object2IntEntrySet()) { + int id = CustomItemTranslator.getCustomItem(entry.getKey(), this.testMappingWithNoDamage); + Assert.assertEquals(entry.getKey() + " did not produce the correct custom item", entry.getIntValue(), id); + } } }