From 5796f810f1b918c3acd405e723d53373ffe2be19 Mon Sep 17 00:00:00 2001 From: XxFran10xX <318299142+XxFran10xX@users.noreply.github.com> Date: Sun, 27 Sep 2026 09:26:11 +0200 Subject: [PATCH 1/2] fix: name water-only sauces properly and stop the plate NPE getMergedColour returned the stored colour without its "#" when a mix had zero or one colour, so a water-only sauce was named "000000Mixed Sauce" (milk-only: "ffffffMixed Sauce"). Ladling it onto a plate found no hex in the name and threw an NPE in getSauceItemPath, so no sauce visual appeared. Always return "#rrggbb", name uncoloured (#000000) mixes white instead of black, and fall back to the water visual when a name has no colour, which also covers sauces already made with the old name. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../cooking/cache/CategoryDictionary.java | 2 + .../cooking/cooking/CookingReference.java | 2 +- .../cooking/utils/DisplayUtils.java | 14 +++- .../cooking/cache/SauceColourTest.java | 76 +++++++++++++++++++ 4 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 src/test/java/net/tfminecraft/cooking/cache/SauceColourTest.java diff --git a/src/main/java/net/tfminecraft/cooking/cache/CategoryDictionary.java b/src/main/java/net/tfminecraft/cooking/cache/CategoryDictionary.java index 3c5189e..dd506ad 100644 --- a/src/main/java/net/tfminecraft/cooking/cache/CategoryDictionary.java +++ b/src/main/java/net/tfminecraft/cooking/cache/CategoryDictionary.java @@ -25,6 +25,8 @@ public static String getName(String key) { } public static String getSauceItemPath(String colour, int index) { + // Uncoloured names (water-only mixes, or older "000000Mixed Sauce" items) have no hex to read. + if (colour == null) return ItemCache.liquidFallback; int rTotal = 0, gTotal = 0, bTotal = 0, count = 0; String hex = colour.replace("#", ""); if (hex.equalsIgnoreCase("000000")) return ItemCache.liquidFallback; diff --git a/src/main/java/net/tfminecraft/cooking/cooking/CookingReference.java b/src/main/java/net/tfminecraft/cooking/cooking/CookingReference.java index 4d37724..224f0ad 100644 --- a/src/main/java/net/tfminecraft/cooking/cooking/CookingReference.java +++ b/src/main/java/net/tfminecraft/cooking/cooking/CookingReference.java @@ -310,7 +310,7 @@ protected String applyNameTemplate(FoodItem product, String colour, String typeL if (fillers.isEmpty()) { fillers = "Mixed "; } - displayName = displayName.replace("{colour}", colour == null ? "" : colour); + displayName = displayName.replace("{colour}", DisplayUtils.getNameColour(colour)); displayName = displayName.replace("{prefixes}", NameComposer.formatPrefixes(product)); displayName = displayName.replace("{fillers}", fillers); displayName = displayName.replace("{ingredients}", getName(typeLabel)); diff --git a/src/main/java/net/tfminecraft/cooking/utils/DisplayUtils.java b/src/main/java/net/tfminecraft/cooking/utils/DisplayUtils.java index 130c48f..35fa7f8 100644 --- a/src/main/java/net/tfminecraft/cooking/utils/DisplayUtils.java +++ b/src/main/java/net/tfminecraft/cooking/utils/DisplayUtils.java @@ -56,9 +56,10 @@ public static String getDisplayString(String name, double foodMultiplier, double return name + " §8(" + extra.toString() + "§8)"; } + /** Average of the stored {@code rrggbb} colours, always as {@code #rrggbb}. */ public static String getMergedColour(List colours) { - if (colours.size() == 0) return "000000"; - if (colours.size() == 1) return colours.get(0); + if (colours.size() == 0) return "#000000"; + if (colours.size() == 1) return "#" + colours.get(0).replace("#", ""); int r = 0, g = 0, b = 0; @@ -78,6 +79,15 @@ public static String getMergedColour(List colours) { return String.format("#%02x%02x%02x", r, g, b); } + /** + * Colour code for a sauce or soup name. A mix of only uncoloured liquids (water) merges to + * {@code #000000}, which would print black, so it uses white instead. + */ + public static String getNameColour(String mergedColour) { + if (mergedColour == null || mergedColour.replace("#", "").equalsIgnoreCase("000000")) return "&f"; + return mergedColour; + } + public static String getSauceStatString(double food, double nutrition) { StringBuilder sb = new StringBuilder(); diff --git a/src/test/java/net/tfminecraft/cooking/cache/SauceColourTest.java b/src/test/java/net/tfminecraft/cooking/cache/SauceColourTest.java new file mode 100644 index 0000000..7133851 --- /dev/null +++ b/src/test/java/net/tfminecraft/cooking/cache/SauceColourTest.java @@ -0,0 +1,76 @@ +package net.tfminecraft.cooking.cache; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.HashMap; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import net.tfminecraft.cooking.utils.DisplayUtils; +import net.tfminecraft.tlibs.objects.api.subapi.StringFormatter; + +class SauceColourTest { + private static final String WATER = "ia.test:water"; + private static final String MILK_PLATED = "ia.test:milk_plated"; + + private String previousFallback; + private HashMap previousDict; + + @BeforeEach + void setUp() { + previousFallback = ItemCache.liquidFallback; + previousDict = CategoryDictionary.sauceDict; + ItemCache.liquidFallback = WATER; + CategoryDictionary.sauceDict = new HashMap<>(); + CategoryDictionary.sauceDict.put("ffffff", "ia.test:milk|" + MILK_PLATED); + } + + @AfterEach + void tearDown() { + ItemCache.liquidFallback = previousFallback; + CategoryDictionary.sauceDict = previousDict; + } + + @Test + void mergedColourAlwaysHasAHash() { + assertEquals("#000000", DisplayUtils.getMergedColour(List.of())); + assertEquals("#000000", DisplayUtils.getMergedColour(List.of("000000"))); + assertEquals("#ffffff", DisplayUtils.getMergedColour(List.of("ffffff"))); + assertEquals("#abcdef", DisplayUtils.getMergedColour(List.of("#abcdef"))); + assertEquals("#185d15", DisplayUtils.getMergedColour(List.of("000000", "30ba2b"))); + } + + @Test + void uncolouredMixesAreNamedWhite() { + assertEquals("&f", DisplayUtils.getNameColour("#000000")); + assertEquals("&f", DisplayUtils.getNameColour(null)); + assertEquals("#185d15", DisplayUtils.getNameColour("#185d15")); + } + + @Test + void waterOnlySauceNameShowsTheWaterVisual() { + String name = StringFormatter.formatHex( + DisplayUtils.getNameColour(DisplayUtils.getMergedColour(List.of("000000"))) + "Mixed Sauce"); + + assertEquals("§fMixed Sauce", name); + assertNull(StringFormatter.extractHexColor(name)); + assertEquals(WATER, CategoryDictionary.getSauceItemPath(StringFormatter.extractHexColor(name), 1)); + } + + @Test + void olderUncolouredSauceNameShowsTheWaterVisual() { + assertEquals(WATER, CategoryDictionary.getSauceItemPath(StringFormatter.extractHexColor("000000Mixed Sauce"), 1)); + } + + @Test + void milkOnlySauceNameShowsTheMilkVisual() { + String name = StringFormatter.formatHex( + DisplayUtils.getNameColour(DisplayUtils.getMergedColour(List.of("ffffff"))) + "Mixed Sauce"); + + assertEquals(MILK_PLATED, CategoryDictionary.getSauceItemPath(StringFormatter.extractHexColor(name), 1)); + } +} From 908839347d30c14c8ef3fcf78b4d0fa32e3c6fd3 Mon Sep 17 00:00:00 2001 From: XxFran10xX <318299142+XxFran10xX@users.noreply.github.com> Date: Sun, 27 Sep 2026 09:33:26 +0200 Subject: [PATCH 2/2] fix: store a sauce's colour on the ladle for the plate visual Paper keeps a #ffffff display name as named white, so reading the colour back from a milk-only sauce's name gives no hex and the plate showed the water visual. Store the merged colour in the ladle's PDC when scooping and use it on the plate, falling back to the name for older ladles. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../cooking/cooking/SauceReference.java | 9 ++++++++- .../tfminecraft/cooking/manager/PlateManager.java | 11 +++++++++-- .../java/net/tfminecraft/cooking/utils/Keys.java | 3 +++ .../tfminecraft/cooking/cache/SauceColourTest.java | 8 +++----- .../cooking/manager/PlateManagerTest.java | 14 ++++++++++++++ 5 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/tfminecraft/cooking/cooking/SauceReference.java b/src/main/java/net/tfminecraft/cooking/cooking/SauceReference.java index 3f38cbb..55182ac 100644 --- a/src/main/java/net/tfminecraft/cooking/cooking/SauceReference.java +++ b/src/main/java/net/tfminecraft/cooking/cooking/SauceReference.java @@ -20,6 +20,8 @@ import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; + import net.tfminecraft.tlibs.TLibs; @@ -58,6 +60,8 @@ import net.tfminecraft.cooking.utils.ItemBuilder; +import net.tfminecraft.cooking.utils.Keys; + import net.tfminecraft.cooking.utils.StationAddonRules; import net.tfminecraft.interactiblefurniture.events.FurnitureInteractEvent; @@ -306,11 +310,14 @@ public void scoop(Player p, ItemStack ladle) { - String displayName = applyNameTemplate(sauce, DisplayUtils.getMergedColour(colours), "Sauce"); + String colour = DisplayUtils.getMergedColour(colours); + String displayName = applyNameTemplate(sauce, colour, "Sauce"); ItemMeta m = output.getItemMeta(); m.setDisplayName(StringFormatter.formatHex(displayName)); + // The name can't carry every colour (Paper stores #ffffff as named white), so keep it here too. + m.getPersistentDataContainer().set(Keys.SAUCE_COLOUR, PersistentDataType.STRING, colour); output.setItemMeta(m); diff --git a/src/main/java/net/tfminecraft/cooking/manager/PlateManager.java b/src/main/java/net/tfminecraft/cooking/manager/PlateManager.java index 4ff66a5..63a2907 100644 --- a/src/main/java/net/tfminecraft/cooking/manager/PlateManager.java +++ b/src/main/java/net/tfminecraft/cooking/manager/PlateManager.java @@ -181,12 +181,19 @@ public void addSauce(Player p, Furniture f, FoodItem sauce, ItemStack base) { if (f.getType() == null || f.getType().getSlot("sauce") == null) return; if(f.hasActiveSlot("sauce")) return; - String saucePath = CategoryDictionary.getSauceItemPath( - StringFormatter.extractHexColor(base.getItemMeta().getDisplayName()), 1); + ItemMeta baseMeta = base.getItemMeta(); + String saucePath = CategoryDictionary.getSauceItemPath(sauceColour( + baseMeta.getPersistentDataContainer().get(Keys.SAUCE_COLOUR, PersistentDataType.STRING), + baseMeta.getDisplayName()), 1); f.getOrCreatePlacedSlot("sauce").forceModel(TLibs.getItemAPI().getCreator().getItemFromPath(saucePath)); f.getLoc().getWorld().playSound(f.getLoc(), Sound.ITEM_BUCKET_FILL, 1f, 1f); //TODO SOUND } + /** Sauce colour stored at scoop time; ladles scooped before it existed fall back to the name's colour. */ + static String sauceColour(String stored, String displayName) { + return stored != null ? stored : StringFormatter.extractHexColor(displayName); + } + public void addSoup(Player p, Furniture f, FoodItem soup, ItemStack base) { p.getInventory().setItemInMainHand( TLibs.getItemAPI().getCreator().getItemFromPath(ItemCache.ladle) diff --git a/src/main/java/net/tfminecraft/cooking/utils/Keys.java b/src/main/java/net/tfminecraft/cooking/utils/Keys.java index 4432e5c..ca5fc93 100644 --- a/src/main/java/net/tfminecraft/cooking/utils/Keys.java +++ b/src/main/java/net/tfminecraft/cooking/utils/Keys.java @@ -41,6 +41,9 @@ public class Keys { new NamespacedKey(Cooking.plugin, "sauce"); public static final NamespacedKey SAUCE_NAME = new NamespacedKey(Cooking.plugin, "sauce_name"); + /** Merged {@code #rrggbb} colour of a scooped sauce; plates pick their sauce visual from it. */ + public static final NamespacedKey SAUCE_COLOUR = + new NamespacedKey(Cooking.plugin, "sauce_colour"); public static final NamespacedKey CARVE_SEQUENCE = new NamespacedKey(Cooking.plugin, "carve_sequence"); public static final NamespacedKey CARVE_NEXT_INDEX = diff --git a/src/test/java/net/tfminecraft/cooking/cache/SauceColourTest.java b/src/test/java/net/tfminecraft/cooking/cache/SauceColourTest.java index 7133851..d6c32f7 100644 --- a/src/test/java/net/tfminecraft/cooking/cache/SauceColourTest.java +++ b/src/test/java/net/tfminecraft/cooking/cache/SauceColourTest.java @@ -67,10 +67,8 @@ void olderUncolouredSauceNameShowsTheWaterVisual() { } @Test - void milkOnlySauceNameShowsTheMilkVisual() { - String name = StringFormatter.formatHex( - DisplayUtils.getNameColour(DisplayUtils.getMergedColour(List.of("ffffff"))) + "Mixed Sauce"); - - assertEquals(MILK_PLATED, CategoryDictionary.getSauceItemPath(StringFormatter.extractHexColor(name), 1)); + void milkOnlySauceColourShowsTheMilkVisual() { + // Scooping stores this merged colour on the ladle; the name alone can't carry it (Paper keeps #ffffff as named white). + assertEquals(MILK_PLATED, CategoryDictionary.getSauceItemPath(DisplayUtils.getMergedColour(List.of("ffffff")), 1)); } } diff --git a/src/test/java/net/tfminecraft/cooking/manager/PlateManagerTest.java b/src/test/java/net/tfminecraft/cooking/manager/PlateManagerTest.java index 92723f8..9d7a132 100644 --- a/src/test/java/net/tfminecraft/cooking/manager/PlateManagerTest.java +++ b/src/test/java/net/tfminecraft/cooking/manager/PlateManagerTest.java @@ -1,7 +1,9 @@ package net.tfminecraft.cooking.manager; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -14,6 +16,7 @@ import net.tfminecraft.interactiblefurniture.furniture.Furniture; import net.tfminecraft.interactiblefurniture.furniture.PlacedSlot; +import net.tfminecraft.tlibs.objects.api.subapi.StringFormatter; class PlateManagerTest { @@ -175,6 +178,17 @@ public boolean hasItemMeta() { assertFalse(plate.hasActiveSlot("sauce")); } + @Test + void storedSauceColourWinsOverTheName() { + assertEquals("#ffffff", PlateManager.sauceColour("#ffffff", "§fMixed Sauce")); + } + + @Test + void ladlesWithoutAStoredColourFallBackToTheName() { + assertEquals("#185d15", PlateManager.sauceColour(null, StringFormatter.formatHex("#185d15Basil Sauce"))); + assertNull(PlateManager.sauceColour(null, "000000Mixed Sauce")); + } + @Test void displaySlotsDoNotCountAsFood() { Furniture plate = plateWithSauceVisual();