diff --git a/src/main/java/net/tfminecraft/cooking/manager/PlateManager.java b/src/main/java/net/tfminecraft/cooking/manager/PlateManager.java index 15f08a8..4ff66a5 100644 --- a/src/main/java/net/tfminecraft/cooking/manager/PlateManager.java +++ b/src/main/java/net/tfminecraft/cooking/manager/PlateManager.java @@ -10,6 +10,7 @@ import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; @@ -31,6 +32,7 @@ import net.tfminecraft.interactiblefurniture.events.FurnitureBreakEvent; import net.tfminecraft.interactiblefurniture.events.FurnitureInteractEvent; import net.tfminecraft.interactiblefurniture.events.FurnitureSlotItemAddEvent; +import net.tfminecraft.interactiblefurniture.events.FurnitureSlotItemTakeEvent; import net.tfminecraft.interactiblefurniture.furniture.Furniture; import net.tfminecraft.interactiblefurniture.furniture.PlacedSlot; import net.tfminecraft.interactiblefurniture.furniture.data.DisplayData; @@ -56,6 +58,11 @@ public void run() { } public void update(Furniture f) { + // Also clears plates saved with leftover sauce before the take handler existed. + if(hasLeftoverSauce(f, null)) { + f.removeActiveSlot("sauce"); + InteractibleFurniture.getInstance().getFurnitureManager().persistFurniture(f); + } for(PlacedSlot slot : f.getActiveSlots().values()) { if(slot.getId().contains("display")) continue; ItemStack item = slot.getCurrentItem(); @@ -99,10 +106,51 @@ public boolean hasSauce(Furniture f) { return false; } + /** + * True when the plate shows a sauce visual but no food would remain once {@code leavingSlot} + * is gone. A leftover sauce visual makes hasSauce reject the next dish on the plate. + */ + boolean hasLeftoverSauce(Furniture f, String leavingSlot) { + if(!f.hasActiveSlot("sauce")) return false; + return !hasFood(f, leavingSlot); + } + + // An active food slot counts even without a loaded item: restore can leave the item unset + // while the display still shows the food. + private boolean hasFood(Furniture f, String ignoredSlot) { + for(String id : f.getActiveSlots().keySet()) { + if(id.equals("sauce") || id.contains("display") || id.equals(ignoredSlot)) continue; + return true; + } + return false; + } + + // Food addSauce can actually apply the sauce to. + private boolean hasSauceableFood(Furniture f) { + for(PlacedSlot slot : f.getActiveSlots().values()) { + if(slot.getId().equals("sauce") || slot.getId().contains("display")) continue; + ItemStack item = slot.getCurrentItem(); + if(item != null && FoodItem.fromItem(item) != null) return true; + } + return false; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void takeItem(FurnitureSlotItemTakeEvent e) { + Furniture f = e.getFurniture(); + if(!FurnitureCache.isPlate(f)) return; + // The taken slot is still active here; InteractibleFurniture removes it and saves the plate next. + if(hasLeftoverSauce(f, e.getSlot().getId())) { + f.removeActiveSlot("sauce"); + } + } + // Keep the existing legacy text representation, formatting, and exact-string comparisons. @SuppressWarnings("deprecation") public void addSauce(Player p, Furniture f, FoodItem sauce, ItemStack base) { if (hasSauce(f)) return; + // Sauce with no food to take it would never reach food added later, so keep the ladle full. + if (!hasSauceableFood(f)) return; // Replace player ladle with empty ladle p.getInventory().setItemInMainHand( diff --git a/src/test/java/net/tfminecraft/cooking/manager/PlateManagerTest.java b/src/test/java/net/tfminecraft/cooking/manager/PlateManagerTest.java index 1522813..92723f8 100644 --- a/src/test/java/net/tfminecraft/cooking/manager/PlateManagerTest.java +++ b/src/test/java/net/tfminecraft/cooking/manager/PlateManagerTest.java @@ -116,4 +116,79 @@ public boolean hasItemMeta() { assertFalse(new PlateManager().hasSauce(plate)); } + + @Test + void sauceIsLeftoverOnceTheLastFoodLeaves() { + Furniture plate = plateWithSauceVisual(); + plate.getActiveSlots().put("item_1", new PlacedSlot(plate, "item_1")); + + assertFalse(new PlateManager().hasLeftoverSauce(plate, null)); + assertTrue(new PlateManager().hasLeftoverSauce(plate, "item_1")); + } + + @Test + void sauceStaysWhileOtherFoodRemains() { + Furniture plate = plateWithSauceVisual(); + plate.getActiveSlots().put("item_1", new PlacedSlot(plate, "item_1")); + plate.getActiveSlots().put("item_2", new PlacedSlot(plate, "item_2")); + + assertFalse(new PlateManager().hasLeftoverSauce(plate, "item_1")); + } + + @Test + void sauceAloneOnAPlateIsLeftover() { + assertTrue(new PlateManager().hasLeftoverSauce(plateWithSauceVisual(), null)); + } + + @Test + void plateWithoutSauceHasNoLeftover() { + Furniture plate = new Furniture("plate", null, UUID.randomUUID()); + plate.getActiveSlots().put("item_1", new PlacedSlot(plate, "item_1")); + + assertFalse(new PlateManager().hasLeftoverSauce(plate, null)); + assertFalse(new PlateManager().hasLeftoverSauce(plate, "item_1")); + } + + @Test + void sauceOnAnEmptyPlateReturnsBeforeTouchingThePlayerOrLadle() { + Furniture plate = new Furniture("plate", null, UUID.randomUUID()); + + // Null interaction arguments ensure the empty-plate attempt exits before using them. + assertDoesNotThrow(() -> new PlateManager().addSauce(null, plate, null, null)); + assertTrue(plate.getActiveSlots().isEmpty()); + } + + @Test + void sauceOnAPlateWithoutLoadedFoodReturnsBeforeTouchingThePlayerOrLadle() { + Furniture plate = new Furniture("plate", null, UUID.randomUUID()); + plate.getActiveSlots().put("item_1", new PlacedSlot(plate, "item_1")); + PlacedSlot unrecognized = new PlacedSlot(plate, "item_2"); + unrecognized.setModel(new ItemStack() { + @Override + public boolean hasItemMeta() { + return false; + } + }); + plate.getActiveSlots().put("item_2", unrecognized); + + assertDoesNotThrow(() -> new PlateManager().addSauce(null, plate, null, null)); + assertFalse(plate.hasActiveSlot("sauce")); + } + + @Test + void displaySlotsDoNotCountAsFood() { + Furniture plate = plateWithSauceVisual(); + plate.getActiveSlots().put("display_1", new PlacedSlot(plate, "display_1")); + + assertTrue(new PlateManager().hasLeftoverSauce(plate, null)); + } + + @Test + void sauceOnADisplayOnlyPlateReturnsBeforeTouchingThePlayerOrLadle() { + Furniture plate = new Furniture("plate", null, UUID.randomUUID()); + plate.getActiveSlots().put("display_1", new PlacedSlot(plate, "display_1")); + + assertDoesNotThrow(() -> new PlateManager().addSauce(null, plate, null, null)); + assertFalse(plate.hasActiveSlot("sauce")); + } }