diff --git a/pom.xml b/pom.xml
index ff72b79..3c67c1c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,32 @@
2.1.0
+
+
+
+
+ org.junit
+ junit-bom
+ 5.10.2
+ pom
+ import
+
+
+
+
+
+ org.mockbukkit.mockbukkit
+ mockbukkit-v1.21
+ 4.95.0
+ test
+
+
+ org.mockito
+ mockito-core
+ 5.20.0
+ test
+
local
gson
diff --git a/src/main/java/net/tfminecraft/cooking/cooking/FryingReference.java b/src/main/java/net/tfminecraft/cooking/cooking/FryingReference.java
index 1816206..0d3e5d1 100644
--- a/src/main/java/net/tfminecraft/cooking/cooking/FryingReference.java
+++ b/src/main/java/net/tfminecraft/cooking/cooking/FryingReference.java
@@ -106,7 +106,8 @@ public void interact(FurnitureInteractEvent e) {
ItemStack converted = IngredientConverter.convertIfNeeded(p, item);
if (converted != item) {
p.getInventory().setItemInMainHand(converted);
- item = converted;
+ // setItemInMainHand stores a copy, so take from the hand itself or nothing is used up.
+ item = p.getInventory().getItemInMainHand();
}
FoodItem butterFi = FoodItem.fromItem(item);
diff --git a/src/main/java/net/tfminecraft/cooking/cup/DrinkConsumeListener.java b/src/main/java/net/tfminecraft/cooking/cup/DrinkConsumeListener.java
index 68bfbdf..742247a 100644
--- a/src/main/java/net/tfminecraft/cooking/cup/DrinkConsumeListener.java
+++ b/src/main/java/net/tfminecraft/cooking/cup/DrinkConsumeListener.java
@@ -1,5 +1,7 @@
package net.tfminecraft.cooking.cup;
+import java.util.function.Supplier;
+
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Material;
@@ -66,24 +68,35 @@ public void onDrink(PlayerItemConsumeEvent event) {
}
int amountBefore = consumed.getAmount();
- Bukkit.getScheduler().runTask(Cooking.plugin, () -> replaceWithEmptyCup(player, amountBefore));
+ EquipmentSlot hand = event.getHand() == EquipmentSlot.OFF_HAND ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND;
+ int slot = hand == EquipmentSlot.OFF_HAND ? -1 : player.getInventory().getHeldItemSlot();
+ Bukkit.getScheduler().runTask(Cooking.plugin, () -> replaceWithEmptyCup(player, amountBefore, slot));
+ }
+
+ /**
+ * Swaps the glass bottle vanilla leaves in the slot the cup was drunk from for an empty cup.
+ * Only that slot, and only if it still holds the bottle: switching slots within the tick
+ * must neither leave the bottle and add a cup, nor overwrite whatever is held instead. Cups
+ * are potions, so an empty slot means the bottle was moved away, or nothing was used up.
+ */
+ static void replaceWithEmptyCup(Player player, int amountBefore, int slot) {
+ replaceWithEmptyCup(player, amountBefore, slot, CupItems::emptyCup);
}
- private static void replaceWithEmptyCup(Player player, int amountBefore) {
+ static void replaceWithEmptyCup(Player player, int amountBefore, int slot, Supplier emptyCup) {
if (amountBefore > 1) {
return;
}
-
- ItemStack empty = CupItems.emptyCup();
- ItemStack main = player.getInventory().getItemInMainHand();
- ItemStack off = player.getInventory().getItemInOffHand();
-
- if (main.getType() == Material.GLASS_BOTTLE || main.getType() == Material.AIR) {
- player.getInventory().setItemInMainHand(empty);
- } else if (off.getType() == Material.GLASS_BOTTLE) {
- player.getInventory().setItemInOffHand(empty);
- } else if (!ItemCache.isCupOfWater(main) && !ItemCache.isCupOfMilk(main)) {
- player.getInventory().setItemInMainHand(empty);
+ PlayerInventory inv = player.getInventory();
+ ItemStack left = slot < 0 ? inv.getItemInOffHand() : inv.getItem(slot);
+ if (left == null || left.getType() != Material.GLASS_BOTTLE) {
+ return;
+ }
+ ItemStack empty = emptyCup.get();
+ if (slot < 0) {
+ inv.setItemInOffHand(empty);
+ } else {
+ inv.setItem(slot, empty);
}
}
diff --git a/src/main/java/net/tfminecraft/cooking/manager/ConversionManager.java b/src/main/java/net/tfminecraft/cooking/manager/ConversionManager.java
index aa846f5..f44688b 100644
--- a/src/main/java/net/tfminecraft/cooking/manager/ConversionManager.java
+++ b/src/main/java/net/tfminecraft/cooking/manager/ConversionManager.java
@@ -23,6 +23,7 @@
import net.tfminecraft.cooking.utils.FoodParser;
import net.tfminecraft.cooking.utils.InventoryAdder;
import net.tfminecraft.cooking.utils.ItemBuilder;
+import net.tfminecraft.cooking.utils.Keys;
public class ConversionManager implements Listener {
@@ -132,7 +133,7 @@ public void onOpen(InventoryOpenEvent e) {
FoodItem fb = FoodItem.fromItem(b);
if (fb == null) continue;
- if (InventoryAdder.equalsFood(fa, fb)) {
+ if (InventoryAdder.equalsFood(fa, fb) && sameApartFromAging(a, b)) {
ItemStack clone = b.clone();
clone.setAmount(a.getAmount());
setSlot(i, clone, top, bottom, topSize);
@@ -142,6 +143,32 @@ public void onOpen(InventoryOpenEvent e) {
}
}
+ /**
+ * True when two food stacks differ only in how far they have aged, so one can be copied over
+ * the other to let them stack. equalsFood matches by kind and tag step alone, so without this
+ * a carved roast, a sausage chain or a big catch would be replaced by a fresh copy of another.
+ */
+ public static boolean sameApartFromAging(ItemStack a, ItemStack b) {
+ return withoutAging(a).isSimilar(withoutAging(b));
+ }
+
+ private static ItemStack withoutAging(ItemStack item) {
+ ItemStack copy = item.clone();
+ copy.setAmount(1);
+ ItemMeta meta = copy.getItemMeta();
+ if (meta == null) return copy;
+ var pdc = meta.getPersistentDataContainer();
+ // The clock, the progress within each tag step, and the lore written from them. Food lore
+ // is always rebuilt whole from the item's data (ItemBuilder.stamp), which is compared.
+ pdc.remove(Keys.LAST_UPDATE);
+ pdc.remove(Keys.AGE_REMAINDER);
+ pdc.remove(Keys.TAGS);
+ pdc.remove(Keys.LORE_INDEX_MAP);
+ meta.lore(null);
+ copy.setItemMeta(meta);
+ return copy;
+ }
+
private ItemStack getSlot(int index, Inventory top, Inventory bottom, int topSize) {
return index < topSize ? top.getItem(index) : bottom.getItem(index - topSize);
}
diff --git a/src/main/java/net/tfminecraft/cooking/manager/CookingManager.java b/src/main/java/net/tfminecraft/cooking/manager/CookingManager.java
index 56403e8..f5901f0 100644
--- a/src/main/java/net/tfminecraft/cooking/manager/CookingManager.java
+++ b/src/main/java/net/tfminecraft/cooking/manager/CookingManager.java
@@ -13,6 +13,7 @@
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.world.ChunkLoadEvent;
+import org.bukkit.event.world.ChunkUnloadEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitRunnable;
@@ -27,6 +28,7 @@
import net.tfminecraft.cooking.cooking.SauceReference;
import net.tfminecraft.cooking.enums.Method;
import net.tfminecraft.cooking.item.FoodItem;
+import net.tfminecraft.cooking.utils.StationChunks;
import net.tfminecraft.interactiblefurniture.events.FurnitureBreakEvent;
import net.tfminecraft.interactiblefurniture.events.FurnitureInteractEvent;
import net.tfminecraft.interactiblefurniture.events.FurnitureSlotItemAddEvent;
@@ -76,6 +78,13 @@ private void resumeStationsInChunk(Chunk chunk) {
}
}
+ /** Drops references to furniture in an unloading chunk; loading it again builds fresh ones. */
+ @EventHandler
+ public void onChunkUnload(ChunkUnloadEvent event) {
+ Chunk chunk = event.getChunk();
+ stations.values().removeIf(ref -> StationChunks.isIn(ref.getFurniture(), chunk));
+ }
+
@EventHandler
public void onChunkLoad(ChunkLoadEvent event) {
Chunk chunk = event.getChunk();
@@ -97,7 +106,9 @@ private CookingReference createReference(Furniture furniture, Method method) {
private CookingReference getOrCreateReference(Furniture furniture) {
CookingReference existing = stations.get(furniture.getEntityId());
- if (existing != null) {
+ // A chunk reload replaces the furniture object under the same UUID. A reference still
+ // holding the old one would hand out items the live furniture also keeps, so rebuild it.
+ if (existing != null && existing.getFurniture() == furniture) {
return existing;
}
Method method = FurnitureCache.getByFurniture(furniture);
diff --git a/src/main/java/net/tfminecraft/cooking/manager/CraftingManager.java b/src/main/java/net/tfminecraft/cooking/manager/CraftingManager.java
index 2847d0c..2d1d965 100644
--- a/src/main/java/net/tfminecraft/cooking/manager/CraftingManager.java
+++ b/src/main/java/net/tfminecraft/cooking/manager/CraftingManager.java
@@ -16,6 +16,7 @@
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkLoadEvent;
+import org.bukkit.event.world.ChunkUnloadEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Transformation;
@@ -34,6 +35,7 @@
import net.tfminecraft.cooking.item.FoodItem;
import net.tfminecraft.cooking.item.data.CookData;
import net.tfminecraft.cooking.utils.ItemUpdater;
+import net.tfminecraft.cooking.utils.StationChunks;
import net.tfminecraft.cooking.loader.CraftingStationLoader;
import net.tfminecraft.interactiblefurniture.events.FurnitureBreakEvent;
import net.tfminecraft.interactiblefurniture.events.FurnitureInteractEvent;
@@ -92,7 +94,8 @@ private boolean isCraftingStationFurniture(Furniture f) {
public CraftingStation getOrCreateStation(Furniture f) {
if (f.getType() == null) return null;
CraftingStation existing = stations.get(f.getEntityId());
- if (existing != null) return existing;
+ // A chunk reload replaces the furniture object under the same UUID; rebuild from the live one.
+ if (existing != null && existing.getFurniture() == f) return existing;
for (CraftingStation template : CraftingStationLoader.get()) {
if (template.getBlockId().equalsIgnoreCase(f.getType().getId())) {
@@ -105,6 +108,13 @@ public CraftingStation getOrCreateStation(Furniture f) {
return null;
}
+ /** Drops stations in an unloading chunk; loading it again builds fresh ones. */
+ @EventHandler
+ public void onChunkUnload(ChunkUnloadEvent event) {
+ Chunk chunk = event.getChunk();
+ stations.values().removeIf(station -> StationChunks.isIn(station.getFurniture(), chunk));
+ }
+
@EventHandler
public void onChunkLoad(ChunkLoadEvent event) {
Chunk chunk = event.getChunk();
diff --git a/src/main/java/net/tfminecraft/cooking/mixing/MixingBowlHandler.java b/src/main/java/net/tfminecraft/cooking/mixing/MixingBowlHandler.java
index eb75656..9c94927 100644
--- a/src/main/java/net/tfminecraft/cooking/mixing/MixingBowlHandler.java
+++ b/src/main/java/net/tfminecraft/cooking/mixing/MixingBowlHandler.java
@@ -319,7 +319,8 @@ private void handleMixinAdd(Furniture furniture, Player player, FurnitureInterac
ItemStack converted = IngredientConverter.convertIfNeeded(player, hand);
if (converted != hand) {
player.getInventory().setItemInMainHand(converted);
- hand = converted;
+ // setItemInMainHand stores a copy, so take from the hand itself or nothing is used up.
+ hand = player.getInventory().getItemInMainHand();
}
FoodItem foodItem = FoodItem.fromItem(hand);
diff --git a/src/main/java/net/tfminecraft/cooking/sausagemaker/SausageMakerHandler.java b/src/main/java/net/tfminecraft/cooking/sausagemaker/SausageMakerHandler.java
index 6264502..a53768a 100644
--- a/src/main/java/net/tfminecraft/cooking/sausagemaker/SausageMakerHandler.java
+++ b/src/main/java/net/tfminecraft/cooking/sausagemaker/SausageMakerHandler.java
@@ -136,10 +136,18 @@ private void startCrank(Furniture furniture, Player player) {
}
private void finishCrank(Furniture furniture, Player player) {
+ if (!player.isOnline()) {
+ return;
+ }
List meats = collectMeats(furniture);
if (meats.size() < MEAT_SLOTS.length) {
return;
}
+ // The paper was checked when the crank started; it could have been put away since.
+ if (!hasPaper(player)) {
+ player.sendMessage("§cHold paper to casing.");
+ return;
+ }
ItemStack chain = SausageItems.fromMeats(player, meats);
if (chain == null) {
diff --git a/src/main/java/net/tfminecraft/cooking/utils/StationChunks.java b/src/main/java/net/tfminecraft/cooking/utils/StationChunks.java
new file mode 100644
index 0000000..55cf733
--- /dev/null
+++ b/src/main/java/net/tfminecraft/cooking/utils/StationChunks.java
@@ -0,0 +1,27 @@
+package net.tfminecraft.cooking.utils;
+
+import org.bukkit.Chunk;
+import org.bukkit.Location;
+
+import net.tfminecraft.interactiblefurniture.furniture.Furniture;
+
+/** Chunk checks for cached stations that never load a chunk to answer. */
+public final class StationChunks {
+
+ private StationChunks() {
+ }
+
+ /** True when the furniture sits in this chunk. A carried piece counts where it is now. */
+ public static boolean isIn(Furniture furniture, Chunk chunk) {
+ if (furniture == null || chunk == null) return false;
+ Location loc = furniture.getLoc();
+ return isIn(loc, chunk);
+ }
+
+ static boolean isIn(Location loc, Chunk chunk) {
+ if (loc == null || loc.getWorld() == null || chunk.getWorld() == null) return false;
+ return loc.getWorld().getUID().equals(chunk.getWorld().getUID())
+ && (loc.getBlockX() >> 4) == chunk.getX()
+ && (loc.getBlockZ() >> 4) == chunk.getZ();
+ }
+}
diff --git a/src/test/java/net/tfminecraft/cooking/cup/EmptyCupReturnTest.java b/src/test/java/net/tfminecraft/cooking/cup/EmptyCupReturnTest.java
new file mode 100644
index 0000000..8e668ba
--- /dev/null
+++ b/src/test/java/net/tfminecraft/cooking/cup/EmptyCupReturnTest.java
@@ -0,0 +1,80 @@
+package net.tfminecraft.cooking.cup;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import org.bukkit.Material;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.PlayerInventory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockbukkit.mockbukkit.MockBukkit;
+import org.mockbukkit.mockbukkit.entity.PlayerMock;
+
+/** A drunk cup leaves a glass bottle in its slot, which becomes an empty cup there and nowhere else. */
+class EmptyCupReturnTest {
+ private PlayerMock player;
+ private PlayerInventory inventory;
+
+ @BeforeEach
+ void setUp() {
+ player = MockBukkit.mock().addPlayer();
+ inventory = player.getInventory();
+ }
+
+ @AfterEach
+ void tearDown() {
+ MockBukkit.unmock();
+ }
+
+ private static ItemStack cup() {
+ return new ItemStack(Material.BOWL);
+ }
+
+ @Test
+ void theBottleInTheSlotDrunkFromBecomesACup() {
+ inventory.setItem(3, new ItemStack(Material.GLASS_BOTTLE));
+
+ DrinkConsumeListener.replaceWithEmptyCup(player, 1, 3, EmptyCupReturnTest::cup);
+
+ assertEquals(Material.BOWL, inventory.getItem(3).getType());
+ }
+
+ @Test
+ void switchingSlotsDoesNotOverwriteTheNewItem() {
+ inventory.setItem(3, new ItemStack(Material.GLASS_BOTTLE));
+ inventory.setItem(5, new ItemStack(Material.DIAMOND_SWORD));
+ inventory.setHeldItemSlot(5);
+
+ DrinkConsumeListener.replaceWithEmptyCup(player, 1, 3, EmptyCupReturnTest::cup);
+
+ assertEquals(Material.BOWL, inventory.getItem(3).getType());
+ assertEquals(Material.DIAMOND_SWORD, inventory.getItem(5).getType());
+ }
+
+ @Test
+ void aBottleMovedAwayGetsNoCupAsWell() {
+ inventory.setItem(3, new ItemStack(Material.BREAD));
+
+ DrinkConsumeListener.replaceWithEmptyCup(player, 1, 3, () -> fail("no cup should be made"));
+
+ assertEquals(Material.BREAD, inventory.getItem(3).getType());
+ }
+
+ @Test
+ void aSlotEmptiedOfItsBottleGetsNoCup() {
+ DrinkConsumeListener.replaceWithEmptyCup(player, 1, 3, () -> fail("no cup should be made"));
+
+ assertEquals(Material.AIR, inventory.getItem(3) == null ? Material.AIR : inventory.getItem(3).getType());
+ }
+
+ @Test
+ void theOffHandIsUsedWhenTheCupWasDrunkFromIt() {
+ inventory.setItemInOffHand(new ItemStack(Material.GLASS_BOTTLE));
+
+ DrinkConsumeListener.replaceWithEmptyCup(player, 1, -1, EmptyCupReturnTest::cup);
+
+ assertEquals(Material.BOWL, inventory.getItemInOffHand().getType());
+ }
+}
diff --git a/src/test/java/net/tfminecraft/cooking/manager/FoodStackMergeTest.java b/src/test/java/net/tfminecraft/cooking/manager/FoodStackMergeTest.java
new file mode 100644
index 0000000..d2aae9b
--- /dev/null
+++ b/src/test/java/net/tfminecraft/cooking/manager/FoodStackMergeTest.java
@@ -0,0 +1,95 @@
+package net.tfminecraft.cooking.manager;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+
+import org.bukkit.Material;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.bukkit.persistence.PersistentDataType;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockbukkit.mockbukkit.MockBukkit;
+
+import net.kyori.adventure.text.Component;
+import net.tfminecraft.cooking.Cooking;
+import net.tfminecraft.cooking.utils.Keys;
+
+/**
+ * Opening a chest copies one food over another equal one so the two stack. Only the aging may
+ * differ; anything else, such as how far a roast has been carved, must stay with its own item.
+ */
+class FoodStackMergeTest {
+ private static Cooking previous;
+
+ @BeforeAll
+ static void setUp() {
+ MockBukkit.mock();
+ previous = Cooking.plugin;
+ Cooking plugin = mock(Cooking.class);
+ when(plugin.namespace()).thenReturn("cooking");
+ when(plugin.getName()).thenReturn("Cooking");
+ Cooking.plugin = plugin;
+ }
+
+ @AfterAll
+ static void tearDown() {
+ Cooking.plugin = previous;
+ MockBukkit.unmock();
+ }
+
+ private static ItemStack roast(int amount, long lastUpdate, String tags, String lore) {
+ ItemStack item = new ItemStack(Material.COOKED_BEEF, amount);
+ ItemMeta meta = item.getItemMeta();
+ var pdc = meta.getPersistentDataContainer();
+ pdc.set(Keys.FOOD_ID, PersistentDataType.STRING, "beef_roast");
+ pdc.set(Keys.LAST_UPDATE, PersistentDataType.LONG, lastUpdate);
+ pdc.set(Keys.TAGS, PersistentDataType.STRING, tags);
+ meta.lore(List.of(Component.text(lore)));
+ item.setItemMeta(meta);
+ return item;
+ }
+
+ private static ItemStack with(ItemStack item, java.util.function.Consumer change) {
+ ItemStack copy = item.clone();
+ ItemMeta meta = copy.getItemMeta();
+ change.accept(meta);
+ copy.setItemMeta(meta);
+ return copy;
+ }
+
+ @Test
+ void foodsThatOnlyAgedDifferentlyCanMerge() {
+ ItemStack older = roast(3, 1_000L, "cooked.3", "Fresh");
+ ItemStack newer = roast(1, 9_000L, "cooked.7", "Fresh, 2 minutes");
+
+ assertTrue(ConversionManager.sameApartFromAging(older, newer));
+ }
+
+ @Test
+ void aCarvedRoastIsNotReplacedByAWholeOne() {
+ ItemStack whole = roast(1, 1_000L, "cooked.3", "Fresh");
+ ItemStack carved = with(whole, m -> m.getPersistentDataContainer()
+ .set(Keys.CARVE_REMAINING, PersistentDataType.INTEGER, 2));
+ ItemStack full = with(whole, m -> m.getPersistentDataContainer()
+ .set(Keys.CARVE_REMAINING, PersistentDataType.INTEGER, 8));
+
+ assertFalse(ConversionManager.sameApartFromAging(carved, full));
+ }
+
+ @Test
+ void anyOtherDifferenceKeepsTheItemsApart() {
+ ItemStack plain = roast(1, 1_000L, "cooked.3", "Fresh");
+
+ assertFalse(ConversionManager.sameApartFromAging(plain, with(plain, m -> m.getPersistentDataContainer()
+ .set(Keys.BASE_FOOD, PersistentDataType.DOUBLE, 12.0))));
+ assertFalse(ConversionManager.sameApartFromAging(plain, with(plain, m -> m.getPersistentDataContainer()
+ .set(Keys.CATCH_SIZE_CM, PersistentDataType.INTEGER, 90))));
+ assertFalse(ConversionManager.sameApartFromAging(plain, new ItemStack(Material.COOKED_PORKCHOP)));
+ }
+}
diff --git a/src/test/java/net/tfminecraft/cooking/utils/StationChunksTest.java b/src/test/java/net/tfminecraft/cooking/utils/StationChunksTest.java
new file mode 100644
index 0000000..ac3ecbc
--- /dev/null
+++ b/src/test/java/net/tfminecraft/cooking/utils/StationChunksTest.java
@@ -0,0 +1,41 @@
+package net.tfminecraft.cooking.utils;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.bukkit.Chunk;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockbukkit.mockbukkit.MockBukkit;
+import org.mockbukkit.mockbukkit.ServerMock;
+
+class StationChunksTest {
+ private ServerMock server;
+
+ @BeforeEach
+ void setUp() {
+ server = MockBukkit.mock();
+ }
+
+ @AfterEach
+ void tearDown() {
+ MockBukkit.unmock();
+ }
+
+ @Test
+ void findsFurnitureInTheUnloadingChunkOnly() {
+ World world = server.addSimpleWorld("world");
+ World other = server.addSimpleWorld("other");
+ Chunk chunk = world.getChunkAt(1, -2);
+
+ assertTrue(StationChunks.isIn(new Location(world, 16, 64, -32), chunk));
+ assertTrue(StationChunks.isIn(new Location(world, 31.9, 64, -17.5), chunk));
+ assertFalse(StationChunks.isIn(new Location(world, 32, 64, -32), chunk));
+ assertFalse(StationChunks.isIn(new Location(world, 16, 64, -33), chunk));
+ assertFalse(StationChunks.isIn(new Location(other, 16, 64, -32), chunk));
+ assertFalse(StationChunks.isIn((Location) null, chunk));
+ }
+}