diff --git a/pom.xml b/pom.xml index f2c68b7..c6baa7e 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,7 @@ -LOCAL - 1.17.0 + 1.17.1 BentoBoxWorld_addon-invSwitcher bentobox-world @@ -238,6 +238,7 @@ 3.13.0 ${java.version} + true diff --git a/src/main/java/com/wasteofplastic/invswitcher/Store.java b/src/main/java/com/wasteofplastic/invswitcher/Store.java index 7fc4feb..5ef1022 100644 --- a/src/main/java/com/wasteofplastic/invswitcher/Store.java +++ b/src/main/java/com/wasteofplastic/invswitcher/Store.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -64,6 +65,7 @@ public class Store { private static final CharSequence THE_END = "_the_end"; private static final CharSequence NETHER = "_nether"; + static final String DEFAULT_WORLD_KEY = "default"; private final Database database; private final Map cache; private final Map currentKey; @@ -108,6 +110,9 @@ public String getStorageKey(Player player, World world, Island island) { * @return storage key */ String getStorageKey(Player player, World world, Location location, Island island) { + if (!addon.getWorlds().contains(world)) { + return DEFAULT_WORLD_KEY; + } String overworldName = getOverworldName(world); if (!addon.getSettings().isIslandsActive()) { @@ -159,9 +164,27 @@ String getStorageKey(Player player, World world, Location location, Island islan * @return overworld name */ private String getOverworldName(World world) { + if (!addon.getWorlds().contains(world)) { + return DEFAULT_WORLD_KEY; + } return (world.getName().replace(THE_END, "")).replace(NETHER, ""); } + private String findOldNonBentoBoxKey(InventoryStorage store) { + if (store.getInventory() == null) { + return null; + } + Set bentoboxOverworlds = addon.getWorlds().stream() + .map(w -> (w.getName().replace(THE_END, "")).replace(NETHER, "")) + .collect(Collectors.toSet()); + return store.getInventory().keySet().stream() + .filter(k -> !k.contains("/")) + .filter(k -> !bentoboxOverworlds.contains(k)) + .filter(k -> !DEFAULT_WORLD_KEY.equals(k)) + .findFirst() + .orElse(null); + } + /** * Get the current storage key for a player. * @param player - player @@ -209,10 +232,20 @@ public void getInventory(Player player, World world, Island island) { // Always track the island-level key so future saves and island detection work correctly currentKey.put(player.getUniqueId(), islandKey); + // Migration: non-BentoBox worlds previously stored data under individual world names. + // Now they share DEFAULT_WORLD_KEY. Find and migrate old data on first access. + String islandLoadKey = islandKey; + if (DEFAULT_WORLD_KEY.equals(islandKey) && !store.isInventory(islandKey)) { + String oldKey = findOldNonBentoBoxKey(store); + if (oldKey != null) { + islandLoadKey = oldKey; + store.clearWorldData(oldKey); + } + } + // Backward compat: if island-specific key has no data, migrate from world-only key. // This only happens once — the world-only data is cleared after migration so that // other islands don't also inherit a duplicate copy. - String islandLoadKey = islandKey; if (islandKey.contains("/") && !store.isInventory(islandKey)) { if (store.isInventory(worldKey)) { islandLoadKey = worldKey; diff --git a/src/test/java/com/wasteofplastic/invswitcher/StoreTest.java b/src/test/java/com/wasteofplastic/invswitcher/StoreTest.java index ac4feba..dfe5419 100644 --- a/src/test/java/com/wasteofplastic/invswitcher/StoreTest.java +++ b/src/test/java/com/wasteofplastic/invswitcher/StoreTest.java @@ -21,7 +21,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Comparator; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; import java.util.UUID; import java.util.logging.Logger; @@ -78,6 +80,8 @@ public class StoreTest { private Settings sets; + private Set bentoboxWorlds; + private UUID playerUUID; @Mock @@ -131,6 +135,11 @@ public void setUp() throws Exception { DatabaseType mockDbt = mock(DatabaseType.class); when(bbSettings.getDatabaseType()).thenReturn(mockDbt); + // Register world as a BentoBox world + bentoboxWorlds = new HashSet<>(); + bentoboxWorlds.add(world); + when(addon.getWorlds()).thenReturn(bentoboxWorlds); + // Disable island switching by default for existing tests sets.setIslandsActive(false); @@ -378,6 +387,7 @@ public void testGetStorageKeyGenericNether() { World netherWorld = mock(World.class); when(netherWorld.getName()).thenReturn("world_nether"); when(netherWorld.getEnvironment()).thenReturn(Environment.NETHER); + bentoboxWorlds.add(netherWorld); // Set up overworld for Util.getWorld World overworld = mock(World.class); @@ -512,4 +522,83 @@ public void testFullScenarioSingleToMultipleIslands() { } } + // --- Non-BentoBox world tests --- + + @Test + public void testGetStorageKeyNonBentoBoxWorld() { + World otherWorld = mock(World.class); + when(otherWorld.getName()).thenReturn("em_adventurers_guild"); + // otherWorld is NOT in bentoboxWorlds + String key = s.getStorageKey(player, otherWorld); + assertEquals(Store.DEFAULT_WORLD_KEY, key); + } + + @Test + public void testAllNonBentoBoxWorldsShareKey() { + World world1 = mock(World.class); + when(world1.getName()).thenReturn("world"); + World world2 = mock(World.class); + when(world2.getName()).thenReturn("em_adventurers_guild"); + World world3 = mock(World.class); + when(world3.getName()).thenReturn("em_diamond_arena"); + // None are in bentoboxWorlds + assertEquals(Store.DEFAULT_WORLD_KEY, s.getStorageKey(player, world1)); + assertEquals(Store.DEFAULT_WORLD_KEY, s.getStorageKey(player, world2)); + assertEquals(Store.DEFAULT_WORLD_KEY, s.getStorageKey(player, world3)); + } + + @Test + public void testBentoBoxToNonBentoBoxRestoresInventory() { + sets.setStatistics(false); + sets.setAdvancements(false); + + World nonBBWorld = mock(World.class); + when(nonBBWorld.getName()).thenReturn("em_adventurers_guild"); + + try (MockedStatic mockedBukkit = mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS)) { + // Step 1: Enter BentoBox world from non-BB world — saves "outside" inventory + s.storeInventory(player, nonBBWorld); + s.getInventory(player, world); + assertEquals("world", s.getCurrentKey(player)); + + // Step 2: Leave BentoBox world to a DIFFERENT non-BB world + s.storeInventory(player, world); + s.getInventory(player, nonBBWorld); + // Should load from DEFAULT_WORLD_KEY (where step 1 saved) + assertEquals(Store.DEFAULT_WORLD_KEY, s.getCurrentKey(player)); + + // Verify inventory was loaded (setContents called) + verify(player.getInventory(), atLeastOnce()).setContents(any(ItemStack[].class)); + } + } + + @Test + public void testMigrationFromOldWorldKey() { + sets.setStatistics(false); + sets.setAdvancements(false); + + // Simulate old data: inventory was saved under "overworld" (old behavior) + World overworld = mock(World.class); + when(overworld.getName()).thenReturn("overworld"); + // Temporarily add overworld to BentoBox worlds so storeAndSave uses "overworld" key + bentoboxWorlds.add(overworld); + + try (MockedStatic mockedBukkit = mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS)) { + s.storeInventory(player, overworld); + assertTrue(s.isWorldStored(player, overworld)); + } + + // Now remove overworld from BentoBox worlds (simulating the fix being applied) + bentoboxWorlds.remove(overworld); + + // Loading for a non-BB world should migrate from the old "overworld" key + World otherWorld = mock(World.class); + when(otherWorld.getName()).thenReturn("em_adventurers_guild"); + s.getInventory(player, otherWorld); + assertEquals(Store.DEFAULT_WORLD_KEY, s.getCurrentKey(player)); + + // Verify inventory was loaded (migration found the old "overworld" data) + verify(player.getInventory(), atLeastOnce()).setContents(any(ItemStack[].class)); + } + }