From 49f31c96615805a592fc938ba04ab17c702c743f Mon Sep 17 00:00:00 2001
From: Eclipse <eclipse@eclipseisoffline.xyz>
Date: Fri, 6 Dec 2024 10:17:48 +0000
Subject: [PATCH] Work on range dispatch predicate

---
 .../{data => }/ConditionPredicate.java        |  5 +-
 .../v2/predicate/CustomItemPredicate.java     |  2 +-
 ...cate.java => CustomModelDataProperty.java} |  4 +-
 .../{data/match => }/MatchPredicate.java      |  4 +-
 .../v2/predicate/RangeDispatchPredicate.java  | 37 +++++++++++++++
 .../{data => }/match/ChargeType.java          |  2 +-
 .../match/MatchPredicateProperty.java         |  8 ++--
 .../org/geysermc/geyser/item/type/Item.java   |  2 +-
 .../geysermc/geyser/item/type/PotionItem.java |  2 +-
 .../geyser/item/type/ShulkerBoxItem.java      |  2 +-
 .../mappings/versions/MappingsReader_v2.java  | 14 +++---
 .../translator/item/CustomItemTranslator.java | 47 +++++++++++++++----
 .../translator/item/ItemTranslator.java       |  8 ++--
 .../player/input/BedrockBlockActions.java     |  2 +-
 14 files changed, 102 insertions(+), 37 deletions(-)
 rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/{data => }/ConditionPredicate.java (86%)
 rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/{data/CustomModelDataPredicate.java => CustomModelDataProperty.java} (90%)
 rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/{data/match => }/MatchPredicate.java (90%)
 create mode 100644 api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java
 rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/{data => }/match/ChargeType.java (94%)
 rename api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/{data => }/match/MatchPredicateProperty.java (88%)

diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/ConditionPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java
similarity index 86%
rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/ConditionPredicate.java
rename to api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java
index 7ea71e3a8..8295777ea 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/ConditionPredicate.java
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/ConditionPredicate.java
@@ -23,11 +23,8 @@
  * @link https://github.com/GeyserMC/Geyser
  */
 
-package org.geysermc.geyser.api.item.custom.v2.predicate.data;
+package org.geysermc.geyser.api.item.custom.v2.predicate;
 
-import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate;
-
-// TODO maybe type should be a generic class with data, but this works for now
 public record ConditionPredicate(ConditionProperty property, boolean expected, int index) implements CustomItemPredicate {
 
     // TODO maybe we can extend this
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java
index eab23e449..abb61efbe 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomItemPredicate.java
@@ -25,5 +25,5 @@
 
 package org.geysermc.geyser.api.item.custom.v2.predicate;
 
-public interface CustomItemPredicate { // TODO this probably needs to be different since people can implement this
+public sealed interface CustomItemPredicate permits ConditionPredicate, MatchPredicate, RangeDispatchPredicate { // TODO maybe we need to move the predicate classes out of API
 }
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/CustomModelDataPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomModelDataProperty.java
similarity index 90%
rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/CustomModelDataPredicate.java
rename to api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomModelDataProperty.java
index 04646cc50..799a0d91f 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/CustomModelDataPredicate.java
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/CustomModelDataProperty.java
@@ -23,7 +23,7 @@
  * @link https://github.com/GeyserMC/Geyser
  */
 
-package org.geysermc.geyser.api.item.custom.v2.predicate.data;
+package org.geysermc.geyser.api.item.custom.v2.predicate;
 
-public record CustomModelDataPredicate<T>(T data, int index) {
+public record CustomModelDataProperty<T>(T data, int index) {
 }
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/MatchPredicate.java
similarity index 90%
rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicate.java
rename to api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/MatchPredicate.java
index cbecc88f7..eca29d2e4 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicate.java
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/MatchPredicate.java
@@ -23,9 +23,9 @@
  * @link https://github.com/GeyserMC/Geyser
  */
 
-package org.geysermc.geyser.api.item.custom.v2.predicate.data.match;
+package org.geysermc.geyser.api.item.custom.v2.predicate;
 
-import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate;
+import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty;
 
 public record MatchPredicate<T>(MatchPredicateProperty<T> property, T data) implements CustomItemPredicate {
 }
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java
new file mode 100644
index 000000000..1ecb4f618
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/RangeDispatchPredicate.java
@@ -0,0 +1,37 @@
+/*
+ * 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.api.item.custom.v2.predicate;
+
+public record RangeDispatchPredicate(RangeDispatchProperty property, double threshold, double scale, boolean normalizeIfPossible, int index) implements CustomItemPredicate {
+
+    // TODO check if we can change items while bedrock is using them, and if bedrock will continue to use them
+    public enum RangeDispatchProperty {
+        BUNDLE_FULLNESS,
+        DAMAGE,
+        COUNT,
+        CUSTOM_MODEL_DATA
+    }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/ChargeType.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/ChargeType.java
similarity index 94%
rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/ChargeType.java
rename to api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/ChargeType.java
index 438cee772..950ee128a 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/ChargeType.java
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/ChargeType.java
@@ -23,7 +23,7 @@
  * @link https://github.com/GeyserMC/Geyser
  */
 
-package org.geysermc.geyser.api.item.custom.v2.predicate.data.match;
+package org.geysermc.geyser.api.item.custom.v2.predicate.match;
 
 public enum ChargeType {
     NONE,
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateProperty.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java
similarity index 88%
rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateProperty.java
rename to api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java
index a512ff3f7..b9f0ee3de 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/data/match/MatchPredicateProperty.java
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/predicate/match/MatchPredicateProperty.java
@@ -23,10 +23,10 @@
  * @link https://github.com/GeyserMC/Geyser
  */
 
-package org.geysermc.geyser.api.item.custom.v2.predicate.data.match;
+package org.geysermc.geyser.api.item.custom.v2.predicate.match;
 
 import net.kyori.adventure.key.Key;
-import org.geysermc.geyser.api.item.custom.v2.predicate.data.CustomModelDataPredicate;
+import org.geysermc.geyser.api.item.custom.v2.predicate.CustomModelDataProperty;
 
 // TODO can we do more?
 public class MatchPredicateProperty<T> {
@@ -34,7 +34,9 @@ public class MatchPredicateProperty<T> {
     public static final MatchPredicateProperty<ChargeType> CHARGE_TYPE = create();
     public static final MatchPredicateProperty<Key> TRIM_MATERIAL = create();
     public static final MatchPredicateProperty<Key> CONTEXT_DIMENSION = create();
-    public static final MatchPredicateProperty<CustomModelDataPredicate<String>> CUSTOM_MODEL_DATA = create();
+    public static final MatchPredicateProperty<CustomModelDataProperty<String>> CUSTOM_MODEL_DATA = create();
+
+    private MatchPredicateProperty() {}
 
     private static <T> MatchPredicateProperty<T> create() {
         return new MatchPredicateProperty<>();
diff --git a/core/src/main/java/org/geysermc/geyser/item/type/Item.java b/core/src/main/java/org/geysermc/geyser/item/type/Item.java
index 881012b98..d666f8818 100644
--- a/core/src/main/java/org/geysermc/geyser/item/type/Item.java
+++ b/core/src/main/java/org/geysermc/geyser/item/type/Item.java
@@ -125,7 +125,7 @@ public class Item {
                 .damage(mapping.getBedrockData())
                 .count(count);
 
-        ItemTranslator.translateCustomItem(session, components, builder, mapping);
+        ItemTranslator.translateCustomItem(session, count, components, builder, mapping);
 
         return builder;
     }
diff --git a/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java b/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java
index 1cfbdd68d..03508262b 100644
--- a/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java
+++ b/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java
@@ -49,7 +49,7 @@ public class PotionItem extends Item {
         if (components == null) return super.translateToBedrock(session, count, components, mapping, mappings);
         PotionContents potionContents = components.get(DataComponentType.POTION_CONTENTS);
         if (potionContents != null) {
-            ItemDefinition customItemDefinition = CustomItemTranslator.getCustomItem(session, components, mapping);
+            ItemDefinition customItemDefinition = CustomItemTranslator.getCustomItem(session, count, components, mapping);
             if (customItemDefinition == null) {
                 Potion potion = Potion.getByJavaId(potionContents.getPotionId());
                 if (potion != null) {
diff --git a/core/src/main/java/org/geysermc/geyser/item/type/ShulkerBoxItem.java b/core/src/main/java/org/geysermc/geyser/item/type/ShulkerBoxItem.java
index f57733e71..2fca0fdca 100644
--- a/core/src/main/java/org/geysermc/geyser/item/type/ShulkerBoxItem.java
+++ b/core/src/main/java/org/geysermc/geyser/item/type/ShulkerBoxItem.java
@@ -74,7 +74,7 @@ public class ShulkerBoxItem extends BlockItem {
 
             if (boxComponents != null) {
                 // Check for custom items
-                ItemDefinition customItemDefinition = CustomItemTranslator.getCustomItem(session, boxComponents, boxMapping);
+                ItemDefinition customItemDefinition = CustomItemTranslator.getCustomItem(session, item.getAmount(), boxComponents, boxMapping);
                 if (customItemDefinition != null) {
                     bedrockIdentifier = customItemDefinition.getIdentifier();
                     bedrockData = 0;
diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java
index a90114273..40a711884 100644
--- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java
+++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v2.java
@@ -35,11 +35,11 @@ import org.geysermc.geyser.GeyserImpl;
 import org.geysermc.geyser.api.item.custom.v2.BedrockCreativeTab;
 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.predicate.data.ConditionPredicate;
-import org.geysermc.geyser.api.item.custom.v2.predicate.data.CustomModelDataPredicate;
-import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.ChargeType;
-import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicate;
-import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicateProperty;
+import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionPredicate;
+import org.geysermc.geyser.api.item.custom.v2.predicate.CustomModelDataProperty;
+import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType;
+import org.geysermc.geyser.api.item.custom.v2.predicate.MatchPredicate;
+import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty;
 import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException;
 import org.geysermc.geyser.registry.mappings.components.DataComponentReaders;
 import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping;
@@ -270,9 +270,9 @@ public class MappingsReader_v2 extends MappingsReader {
                 case "custom_model_data" -> {
                     JsonNode index = node.get("index");
                     if (index == null || !index.isIntegralNumber()) {
-                        throw new InvalidCustomMappingsFileException("Predicate missing index key");
+                        throw new InvalidCustomMappingsFileException("Predicate missing index key"); // TODO default to 0
                     }
-                    builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CUSTOM_MODEL_DATA, new CustomModelDataPredicate<>(value.asText(), index.asInt())));
+                    builder.predicate(new MatchPredicate<>(MatchPredicateProperty.CUSTOM_MODEL_DATA, new CustomModelDataProperty<>(value.asText(), index.asInt())));
                 }
                 default -> throw new InvalidCustomMappingsFileException("Unknown property " + property);
             }
diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java
index 87ea8f5b8..0e3d629c1 100644
--- a/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java
+++ b/core/src/main/java/org/geysermc/geyser/translator/item/CustomItemTranslator.java
@@ -30,10 +30,11 @@ import net.kyori.adventure.key.Key;
 import org.cloudburstmc.protocol.bedrock.data.TrimMaterial;
 import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition;
 import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate;
-import org.geysermc.geyser.api.item.custom.v2.predicate.data.ConditionPredicate;
-import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.ChargeType;
-import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicate;
-import org.geysermc.geyser.api.item.custom.v2.predicate.data.match.MatchPredicateProperty;
+import org.geysermc.geyser.api.item.custom.v2.predicate.ConditionPredicate;
+import org.geysermc.geyser.api.item.custom.v2.predicate.RangeDispatchPredicate;
+import org.geysermc.geyser.api.item.custom.v2.predicate.match.ChargeType;
+import org.geysermc.geyser.api.item.custom.v2.predicate.MatchPredicate;
+import org.geysermc.geyser.api.item.custom.v2.predicate.match.MatchPredicateProperty;
 import org.geysermc.geyser.item.Items;
 import org.geysermc.geyser.level.JavaDimension;
 import org.geysermc.geyser.session.GeyserSession;
@@ -57,7 +58,7 @@ import java.util.List;
 public final class CustomItemTranslator {
 
     @Nullable
-    public static ItemDefinition getCustomItem(GeyserSession session, DataComponents components, ItemMapping mapping) {
+    public static ItemDefinition getCustomItem(GeyserSession session, int stackSize, DataComponents components, ItemMapping mapping) {
         if (components == null) {
             return null;
         }
@@ -78,7 +79,7 @@ public final class CustomItemTranslator {
             if (customModel.first().model().equals(itemModel)) {
                 boolean allMatch = true;
                 for (CustomItemPredicate predicate : customModel.first().predicates()) {
-                    if (!predicateMatches(session, predicate, components)) {
+                    if (!predicateMatches(session, predicate, stackSize, components)) {
                         allMatch = false;
                         break;
                     }
@@ -91,7 +92,7 @@ public final class CustomItemTranslator {
         return null;
     }
 
-    private static boolean predicateMatches(GeyserSession session, CustomItemPredicate predicate, DataComponents components) {
+    private static boolean predicateMatches(GeyserSession session, CustomItemPredicate predicate, int stackSize, DataComponents components) {
         if (predicate instanceof ConditionPredicate condition) {
             return switch (condition.property()) {
                 case BROKEN -> nextDamageWillBreak(components);
@@ -124,18 +125,46 @@ public final class CustomItemTranslator {
             } else if (match.property() == MatchPredicateProperty.CONTEXT_DIMENSION) {
                 Key dimension = (Key) match.data();
                 RegistryEntryData<JavaDimension> registered = session.getRegistryCache().dimensions().entryByValue(session.getDimensionType());
-                return registered != null && dimension.equals(registered.key()); // TODO check if this works
+                return registered != null && dimension.equals(registered.key());
             } else if (match.property() == MatchPredicateProperty.CUSTOM_MODEL_DATA) {
                 // TODO 1.21.4
                 return false;
             }
+        } else if (predicate instanceof RangeDispatchPredicate rangeDispatch) {
+            double propertyValue = switch (rangeDispatch.property()) {
+                case BUNDLE_FULLNESS -> {
+                    List<ItemStack> stacks = components.get(DataComponentType.BUNDLE_CONTENTS);
+                    if (stacks == null) {
+                        yield 0;
+                    }
+                    int bundleWeight = 0;
+                    for (ItemStack stack : stacks) {
+                        bundleWeight += stack.getAmount();
+                    }
+                    yield bundleWeight;
+                }
+                case DAMAGE -> tryNormalize(rangeDispatch, components.get(DataComponentType.DAMAGE), components.get(DataComponentType.MAX_DAMAGE));
+                case COUNT -> tryNormalize(rangeDispatch, stackSize, components.get(DataComponentType.MAX_STACK_SIZE));
+                case CUSTOM_MODEL_DATA -> 0.0; // TODO 1.21.4
+            } * rangeDispatch.scale();
+            return propertyValue >= rangeDispatch.threshold();
         }
 
         throw new IllegalStateException("Unimplemented predicate type");
     }
 
-    /* These three functions are based off their Mojmap equivalents from 1.21.3 */
+    private static double tryNormalize(RangeDispatchPredicate predicate, @Nullable Integer value, @Nullable Integer max) {
+        if (value == null) {
+            return 0.0;
+        } else if (max == null) {
+            return value;
+        } else if (!predicate.normalizeIfPossible()) {
+            return Math.min(value, max);
+        }
+        return Math.max(0.0, Math.min(1.0, (double) value / max));
+    }
 
+    /* These three functions are based off their Mojmap equivalents from 1.21.3 */
     private static boolean nextDamageWillBreak(DataComponents components) {
         return isDamageableItem(components) && components.getOrDefault(DataComponentType.DAMAGE, 0) >= components.getOrDefault(DataComponentType.MAX_DAMAGE, 0) - 1;
     }
diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java
index 5951eea2f..a927fedf3 100644
--- a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java
+++ b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java
@@ -215,7 +215,7 @@ public final class ItemTranslator {
             translatePlayerHead(session, components, builder);
         }
 
-        translateCustomItem(session, components, builder, bedrockItem);
+        translateCustomItem(session, count, components, builder, bedrockItem);
 
         if (components != null) {
             // Translate the canDestroy and canPlaceOn Java components
@@ -428,7 +428,7 @@ public final class ItemTranslator {
             }
         }
 
-        ItemDefinition definition = CustomItemTranslator.getCustomItem(session, itemStack.getComponents(), mapping);
+        ItemDefinition definition = CustomItemTranslator.getCustomItem(session, itemStack.getAmount(), itemStack.getComponents(), mapping);
         if (definition == null) {
             // No custom item
             return itemDefinition;
@@ -469,8 +469,8 @@ public final class ItemTranslator {
     /**
      * Translates the custom model data of an item
      */
-    public static void translateCustomItem(GeyserSession session, DataComponents components, ItemData.Builder builder, ItemMapping mapping) {
-        ItemDefinition definition = CustomItemTranslator.getCustomItem(session, components, mapping);
+    public static void translateCustomItem(GeyserSession session, int stackSize, DataComponents components, ItemData.Builder builder, ItemMapping mapping) {
+        ItemDefinition definition = CustomItemTranslator.getCustomItem(session, stackSize, components, mapping);
         if (definition != null) {
             builder.definition(definition);
             builder.blockDefinition(null);
diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockBlockActions.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockBlockActions.java
index 2d593dc09..e35679f80 100644
--- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockBlockActions.java
+++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockBlockActions.java
@@ -93,7 +93,7 @@ final class BedrockBlockActions {
                 // If the block is custom or the breaking item is custom, we must keep track of break time ourselves
                 GeyserItemStack item = session.getPlayerInventory().getItemInHand();
                 ItemMapping mapping = item.getMapping(session);
-                ItemDefinition customItem = mapping.isTool() ? CustomItemTranslator.getCustomItem(session, item.getComponents(), mapping) : null;
+                ItemDefinition customItem = mapping.isTool() ? CustomItemTranslator.getCustomItem(session, item.getAmount(), item.getComponents(), mapping) : null;
                 CustomBlockState blockStateOverride = BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get(blockState);
                 SkullCache.Skull skull = session.getSkullCache().getSkulls().get(vector);