Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>1.17.0</build.version>
<build.version>1.17.1</build.version>
<!-- Sonar Cloud -->
<sonar.projectKey>BentoBoxWorld_addon-invSwitcher</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
Expand Down Expand Up @@ -238,6 +238,7 @@
<version>3.13.0</version>
<configuration>
<release>${java.version}</release>
<forceJavacCompilerUse>true</forceJavacCompilerUse>
</configuration>
</plugin>
<plugin>
Expand Down
35 changes: 34 additions & 1 deletion src/main/java/com/wasteofplastic/invswitcher/Store.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<InventoryStorage> database;
private final Map<UUID, InventoryStorage> cache;
private final Map<UUID, String> currentKey;
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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<String> 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
Expand Down Expand Up @@ -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;
Expand Down
89 changes: 89 additions & 0 deletions src/test/java/com/wasteofplastic/invswitcher/StoreTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -78,6 +80,8 @@ public class StoreTest {

private Settings sets;

private Set<World> bentoboxWorlds;

private UUID playerUUID;

@Mock
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<Bukkit> 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<Bukkit> 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));
}

}
Loading