From 4518478ea1b212cc18e16a1e26c1a222a2a8c02d Mon Sep 17 00:00:00 2001 From: Dylan Garvis Date: Mon, 24 Aug 2026 10:06:25 -0400 Subject: [PATCH] feat(pocket-base): allow return portal relocation --- README.md | 2 +- design/log.md | 7 ++ ...s-016-build-and-use-pocket-base-portals.md | 7 +- .../dmg/spigotbase/PocketBaseController.java | 35 +++++- .../dmg/spigotbase/PocketBaseManager.java | 17 ++- .../games/dmg/spigotbase/PocketBaseState.java | 41 +++++-- .../spigotbase/PocketBaseWorldService.java | 6 +- .../spigotbase/YamlPocketBaseRepository.java | 6 +- .../spigotbase/PocketBaseControllerTest.java | 109 ++++++++++++++++++ .../dmg/spigotbase/PocketBaseManagerTest.java | 42 +++++++ .../PocketBaseWorldServiceTest.java | 29 +++++ .../YamlPocketBaseRepositoryTest.java | 9 +- 12 files changed, 290 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index e41c5a1..aa118ff 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ After 250 Survival-mode block placements anywhere by default, the spawnable over `/base` has a stationary warm-up. Looking around is allowed, while movement between blocks, damage, teleportation, world changes, death, logout, and conflicting teleport commands cancel it without consuming the cooldown. -Base IV owners can purchase and expand a persistent Pocket Base with `/basesettings pocket upgrade`. Activating a complete diamond-block portal frame inside the normal base with flint and steel opens one public entrance to the owner's grass platform in a private void world. Natural hostile and passive mob spawning is disabled by default and can be controlled by the owner with `/basesettings pocket mobs `. +Base IV owners can purchase and expand a persistent Pocket Base with `/basesettings pocket upgrade`. Activating a complete diamond-block portal frame inside the normal base with flint and steel opens one public entrance to the owner's grass platform in a private void world. The owner can move the return portal by building and igniting another complete frame inside the unlocked Pocket Base boundary; only the newly activated return portal remains functional. Natural hostile and passive mob spawning is disabled by default and can be controlled by the owner with `/basesettings pocket mobs `. ## Administration diff --git a/design/log.md b/design/log.md index 160a286..372fe11 100644 --- a/design/log.md +++ b/design/log.md @@ -98,3 +98,10 @@ description: Chronological record of material decisions affecting the Spigot Bas - Added the persisted owner setting `/basesettings pocket mobs enable|disable`, contextual autocomplete, and status reporting. - Reapplied saved spawn flags when Pocket Base worlds load and restored the prior world setting when persistence fails. - Verified the feature with `./gradlew clean check jar`. + +## 2026-08-24 — Relocatable Pocket Base return portals + +- Allowed Pocket Base owners to activate a complete return portal frame with flint and steel anywhere inside their unlocked pocket boundary. +- Persisted one active return portal location while retaining the generated portal as the default for existing Pocket Bases. +- Made only the selected intact frame functional without removing old frame blocks, preserving the void-return safety fallback. +- Verified the feature with `./gradlew clean check jar`. diff --git a/design/user-stories/us-016-build-and-use-pocket-base-portals.md b/design/user-stories/us-016-build-and-use-pocket-base-portals.md index 9d4d4d7..48c541e 100644 --- a/design/user-stories/us-016-build-and-use-pocket-base-portals.md +++ b/design/user-stories/us-016-build-and-use-pocket-base-portals.md @@ -21,7 +21,12 @@ As a **Pocket Base owner**, I want to connect my normal base to my Pocket Base w - [x] Any player can use an active entrance without a separate visitor setting, warm-up, or cooldown. - [x] Visitors can build, break blocks, open containers, and otherwise interact normally inside the Pocket Base. - [x] The generated return portal sends players safely to the owner's active entrance. -- [x] Return travel falls back to the server spawn when the owner has no valid active entrance. +- [x] Only the owner can activate a complete portal frame inside their own Pocket Base with flint and steel. +- [x] Every required return-frame block must be within the owner's unlocked Pocket Base boundary. +- [x] Each Pocket Base has at most one active return portal, and activating another valid frame moves the active return location without removing the old frame. +- [x] Relocated return portals persist through restarts, while existing Pocket Bases use the generated portal as their default. +- [x] Breaking the active return frame disables return travel through it. +- [x] Return travel falls back to the server spawn when the owner has no valid normal-world entrance. - [x] Falling below Y -64 performs the same safe return without void damage. - [x] Players who disconnect inside a Pocket Base remain there when they reconnect. - [x] Moving the normal base deactivates its entrance while preserving the Pocket Base world and its contents. diff --git a/src/main/java/games/dmg/spigotbase/PocketBaseController.java b/src/main/java/games/dmg/spigotbase/PocketBaseController.java index cb7f51f..6949c84 100644 --- a/src/main/java/games/dmg/spigotbase/PocketBaseController.java +++ b/src/main/java/games/dmg/spigotbase/PocketBaseController.java @@ -79,10 +79,6 @@ final class PocketBaseController implements Listener, Runnable { if (pocket.level() < 1) { return; } - PlayerState owner = baseStates.player(player.getUniqueId(), player.getName()); - if (owner.baseLevel() < 4 || owner.base().isEmpty()) { - return; - } Block clicked = event.getClickedBlock(); World world = clicked.getWorld(); Material frameMaterial = Material.valueOf( @@ -98,6 +94,37 @@ final class PocketBaseController implements Listener, Runnable { if (portal.isEmpty()) { return; } + Optional pocketOwner = pocketBases.ownerForPocketWorld(world.getUID()); + if (pocketOwner.isPresent()) { + if (!pocketOwner.orElseThrow().equals(player.getUniqueId())) { + return; + } + if (PocketPortalGeometry.frameBlocks(portal.orElseThrow()).stream() + .anyMatch(candidate -> !pocketBases.policy().contains( + pocket.level(), candidate.x(), candidate.z() + ))) { + player.sendMessage(ChatColor.RED + + "The complete return portal must be inside your Pocket Base boundary."); + event.setCancelled(true); + return; + } + try { + pocketBases.activateReturnPortal(player.getUniqueId(), portal.orElseThrow()); + } catch (IOException | RuntimeException exception) { + logger.log(Level.SEVERE, "Could not activate Pocket Base return portal", exception); + player.sendMessage(ChatColor.RED + + "The Pocket Base return portal could not be activated."); + event.setCancelled(true); + return; + } + event.setCancelled(true); + player.sendMessage(ChatColor.GREEN + "Pocket Base return portal activated."); + return; + } + PlayerState owner = baseStates.player(player.getUniqueId(), player.getName()); + if (owner.baseLevel() < 4 || owner.base().isEmpty()) { + return; + } BaseArea area = baseBounds.area(owner); if (PocketPortalGeometry.frameBlocks(portal.orElseThrow()).stream().anyMatch(candidate -> !area.contains(world.getUID(), candidate.x(), candidate.y(), candidate.z()))) { diff --git a/src/main/java/games/dmg/spigotbase/PocketBaseManager.java b/src/main/java/games/dmg/spigotbase/PocketBaseManager.java index f254145..047b203 100644 --- a/src/main/java/games/dmg/spigotbase/PocketBaseManager.java +++ b/src/main/java/games/dmg/spigotbase/PocketBaseManager.java @@ -60,6 +60,17 @@ final class PocketBaseManager { } } + PocketBaseState activateReturnPortal( + UUID ownerId, + PocketPortalLocation portal + ) throws IOException { + PocketBaseState current = states.state(ownerId); + if (current.level() < 1) { + throw new IllegalStateException("Pocket Base I is still locked"); + } + return states.updateAndSave(ownerId, state -> state.withReturnPortal(portal)); + } + PocketBaseState setMobSpawning(UUID ownerId, boolean enabled) throws IOException { PocketBaseState current = states.state(ownerId); if (current.level() < 1) { @@ -90,11 +101,13 @@ final class PocketBaseManager { } PocketPortalLocation returnPortal(UUID ownerId) { - return worlds.returnPortal(ownerId); + return states.state(ownerId).returnPortal().orElseGet( + () -> worlds.returnPortal(ownerId) + ); } boolean returnPortalIsIntact(UUID ownerId) { - return worlds.returnPortalIsIntact(ownerId); + return worlds.returnPortalIsIntact(ownerId, returnPortal(ownerId)); } World pocketWorld(UUID ownerId) { diff --git a/src/main/java/games/dmg/spigotbase/PocketBaseState.java b/src/main/java/games/dmg/spigotbase/PocketBaseState.java index 59d93ff..8cfad6c 100644 --- a/src/main/java/games/dmg/spigotbase/PocketBaseState.java +++ b/src/main/java/games/dmg/spigotbase/PocketBaseState.java @@ -7,6 +7,7 @@ public record PocketBaseState( UUID ownerId, int level, Optional entrance, + Optional returnPortal, boolean mobSpawningEnabled ) { public PocketBaseState { @@ -17,8 +18,9 @@ public record PocketBaseState( throw new IllegalArgumentException("Pocket Base level must not be negative"); } entrance = entrance == null ? Optional.empty() : entrance; - if (level == 0 && entrance.isPresent()) { - throw new IllegalArgumentException("a locked Pocket Base cannot have an entrance"); + returnPortal = returnPortal == null ? Optional.empty() : returnPortal; + if (level == 0 && (entrance.isPresent() || returnPortal.isPresent())) { + throw new IllegalArgumentException("a locked Pocket Base cannot have a portal"); } } @@ -27,26 +29,49 @@ public record PocketBaseState( int level, Optional entrance ) { - this(ownerId, level, entrance, false); + this(ownerId, level, entrance, Optional.empty(), false); + } + + public PocketBaseState( + UUID ownerId, + int level, + Optional entrance, + boolean mobSpawningEnabled + ) { + this(ownerId, level, entrance, Optional.empty(), mobSpawningEnabled); } public static PocketBaseState locked(UUID ownerId) { - return new PocketBaseState(ownerId, 0, Optional.empty(), false); + return new PocketBaseState( + ownerId, 0, Optional.empty(), Optional.empty(), false + ); } public PocketBaseState withLevel(int newLevel) { - return new PocketBaseState(ownerId, newLevel, entrance, mobSpawningEnabled); + return new PocketBaseState( + ownerId, newLevel, entrance, returnPortal, mobSpawningEnabled + ); } public PocketBaseState withEntrance(PocketPortalLocation portal) { - return new PocketBaseState(ownerId, level, Optional.of(portal), mobSpawningEnabled); + return new PocketBaseState( + ownerId, level, Optional.of(portal), returnPortal, mobSpawningEnabled + ); } public PocketBaseState withoutEntrance() { - return new PocketBaseState(ownerId, level, Optional.empty(), mobSpawningEnabled); + return new PocketBaseState( + ownerId, level, Optional.empty(), returnPortal, mobSpawningEnabled + ); + } + + public PocketBaseState withReturnPortal(PocketPortalLocation portal) { + return new PocketBaseState( + ownerId, level, entrance, Optional.of(portal), mobSpawningEnabled + ); } public PocketBaseState withMobSpawningEnabled(boolean enabled) { - return new PocketBaseState(ownerId, level, entrance, enabled); + return new PocketBaseState(ownerId, level, entrance, returnPortal, enabled); } } diff --git a/src/main/java/games/dmg/spigotbase/PocketBaseWorldService.java b/src/main/java/games/dmg/spigotbase/PocketBaseWorldService.java index 58e4ef5..0433c43 100644 --- a/src/main/java/games/dmg/spigotbase/PocketBaseWorldService.java +++ b/src/main/java/games/dmg/spigotbase/PocketBaseWorldService.java @@ -81,9 +81,11 @@ final class PocketBaseWorldService { ); } - boolean returnPortalIsIntact(UUID ownerId) { - PocketPortalLocation portal = returnPortal(ownerId); + boolean returnPortalIsIntact(UUID ownerId, PocketPortalLocation portal) { World world = ensureWorld(ownerId); + if (!portal.worldId().equals(world.getUID())) { + return false; + } Material frame = Material.valueOf(settings.current().pocketBasePortalFrameMaterial()); return PocketPortalGeometry.frameBlocks(portal).stream() .allMatch(position -> world.getBlockAt( diff --git a/src/main/java/games/dmg/spigotbase/YamlPocketBaseRepository.java b/src/main/java/games/dmg/spigotbase/YamlPocketBaseRepository.java index 54c0ace..cfcd3e4 100644 --- a/src/main/java/games/dmg/spigotbase/YamlPocketBaseRepository.java +++ b/src/main/java/games/dmg/spigotbase/YamlPocketBaseRepository.java @@ -43,6 +43,7 @@ public final class YamlPocketBaseRepository { ownerId, level, loadPortal(yaml, path + ".entrance"), + loadPortal(yaml, path + ".return-portal"), yaml.getBoolean(path + ".mob-spawning-enabled", false) ); states.put(ownerId, state); @@ -64,6 +65,9 @@ public final class YamlPocketBaseRepository { yaml.set(path + ".level", state.level()); yaml.set(path + ".mob-spawning-enabled", state.mobSpawningEnabled()); state.entrance().ifPresent(portal -> savePortal(yaml, path + ".entrance", portal)); + state.returnPortal().ifPresent(portal -> + savePortal(yaml, path + ".return-portal", portal) + ); } Path temporary = Files.createTempFile(parent, "spigot-base-pocket-", ".yml"); try { @@ -96,7 +100,7 @@ public final class YamlPocketBaseRepository { if (worldId == null || worldName == null || axis == null || !yaml.isInt(path + ".x") || !yaml.isInt(path + ".y") || !yaml.isInt(path + ".z")) { - throw new IllegalArgumentException("invalid Pocket Base entrance"); + throw new IllegalArgumentException("invalid Pocket Base portal"); } return Optional.of(new PocketPortalLocation( UUID.fromString(worldId), diff --git a/src/test/java/games/dmg/spigotbase/PocketBaseControllerTest.java b/src/test/java/games/dmg/spigotbase/PocketBaseControllerTest.java index 72bb4cf..7b2091d 100644 --- a/src/test/java/games/dmg/spigotbase/PocketBaseControllerTest.java +++ b/src/test/java/games/dmg/spigotbase/PocketBaseControllerTest.java @@ -2,6 +2,7 @@ package games.dmg.spigotbase; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -85,6 +86,114 @@ final class PocketBaseControllerTest { verify(event).setCancelled(true); } + @Test + void ownerCanActivateCompleteReturnFrameInsideOwnPocketBase() throws Exception { + UUID ownerId = UUID.randomUUID(); + UUID worldId = UUID.randomUUID(); + PocketPortalLocation portal = new PocketPortalLocation( + worldId, "pocket", 8, 65, 8, PocketPortalAxis.X + ); + Set frame = new HashSet<>(PocketPortalGeometry.frameBlocks(portal)); + Player owner = mock(Player.class); + World world = mock(World.class); + Block clicked = block(world, 8, 67, 8, Material.DIAMOND_BLOCK); + PlayerInteractEvent event = mock(PlayerInteractEvent.class); + PocketBaseManager pocketBases = mock(PocketBaseManager.class); + PluginSettings settings = PluginSettings.from(Map.of()); + + when(owner.getUniqueId()).thenReturn(ownerId); + when(event.getPlayer()).thenReturn(owner); + when(event.getAction()).thenReturn(Action.RIGHT_CLICK_BLOCK); + when(event.getClickedBlock()).thenReturn(clicked); + when(event.getItem()).thenReturn(new ItemStack(Material.FLINT_AND_STEEL)); + when(world.getUID()).thenReturn(worldId); + when(world.getName()).thenReturn("pocket"); + when(world.getBlockAt(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> { + BlockPosition position = new BlockPosition( + invocation.getArgument(0), invocation.getArgument(1), invocation.getArgument(2) + ); + return block( + world, + position.x(), + position.y(), + position.z(), + frame.contains(position) ? Material.DIAMOND_BLOCK : Material.AIR + ); + }); + when(pocketBases.ownerForPocketWorld(worldId)).thenReturn(Optional.of(ownerId)); + when(pocketBases.state(ownerId)).thenReturn( + new PocketBaseState(ownerId, 1, Optional.empty()) + ); + when(pocketBases.policy()).thenReturn(new PocketBasePolicy(settings)); + PocketBaseController controller = new PocketBaseController( + mock(Server.class), + mock(BaseStateManager.class), + new BaseBoundsService(settings), + pocketBases, + new PluginSettingsProvider(settings), + Logger.getAnonymousLogger() + ); + + controller.onActivate(event); + + verify(pocketBases).activateReturnPortal(ownerId, portal); + verify(event).setCancelled(true); + } + + @Test + void returnFrameOutsideUnlockedPocketBoundaryIsRejected() throws Exception { + UUID ownerId = UUID.randomUUID(); + UUID worldId = UUID.randomUUID(); + PocketPortalLocation portal = new PocketPortalLocation( + worldId, "pocket", 30, 65, 8, PocketPortalAxis.X + ); + Set frame = new HashSet<>(PocketPortalGeometry.frameBlocks(portal)); + Player owner = mock(Player.class); + World world = mock(World.class); + Block clicked = block(world, 30, 67, 8, Material.DIAMOND_BLOCK); + PlayerInteractEvent event = mock(PlayerInteractEvent.class); + PocketBaseManager pocketBases = mock(PocketBaseManager.class); + PluginSettings settings = PluginSettings.from(Map.of()); + + when(owner.getUniqueId()).thenReturn(ownerId); + when(event.getPlayer()).thenReturn(owner); + when(event.getAction()).thenReturn(Action.RIGHT_CLICK_BLOCK); + when(event.getClickedBlock()).thenReturn(clicked); + when(event.getItem()).thenReturn(new ItemStack(Material.FLINT_AND_STEEL)); + when(world.getUID()).thenReturn(worldId); + when(world.getName()).thenReturn("pocket"); + when(world.getBlockAt(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> { + BlockPosition position = new BlockPosition( + invocation.getArgument(0), invocation.getArgument(1), invocation.getArgument(2) + ); + return block( + world, + position.x(), + position.y(), + position.z(), + frame.contains(position) ? Material.DIAMOND_BLOCK : Material.AIR + ); + }); + when(pocketBases.ownerForPocketWorld(worldId)).thenReturn(Optional.of(ownerId)); + when(pocketBases.state(ownerId)).thenReturn( + new PocketBaseState(ownerId, 1, Optional.empty()) + ); + when(pocketBases.policy()).thenReturn(new PocketBasePolicy(settings)); + PocketBaseController controller = new PocketBaseController( + mock(Server.class), + mock(BaseStateManager.class), + new BaseBoundsService(settings), + pocketBases, + new PluginSettingsProvider(settings), + Logger.getAnonymousLogger() + ); + + controller.onActivate(event); + + verify(pocketBases, never()).activateReturnPortal(ownerId, portal); + verify(event).setCancelled(true); + } + @Test void placementOutsideUnlockedPocketBoundaryIsCancelled() { UUID ownerId = UUID.randomUUID(); diff --git a/src/test/java/games/dmg/spigotbase/PocketBaseManagerTest.java b/src/test/java/games/dmg/spigotbase/PocketBaseManagerTest.java index ef60b0d..8bb3be5 100644 --- a/src/test/java/games/dmg/spigotbase/PocketBaseManagerTest.java +++ b/src/test/java/games/dmg/spigotbase/PocketBaseManagerTest.java @@ -1,5 +1,6 @@ package games.dmg.spigotbase; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -15,6 +16,47 @@ import org.junit.jupiter.api.Test; import org.mockito.InOrder; final class PocketBaseManagerTest { + @Test + void relocatedReturnPortalOverridesGeneratedDefault() { + UUID ownerId = UUID.randomUUID(); + PocketPortalLocation relocated = new PocketPortalLocation( + UUID.randomUUID(), "pocket", 20, 65, 12, PocketPortalAxis.Z + ); + PocketBaseStateManager states = mock(PocketBaseStateManager.class); + PocketBaseWorldService worlds = mock(PocketBaseWorldService.class); + when(states.state(ownerId)).thenReturn(new PocketBaseState( + ownerId, 1, Optional.empty(), Optional.of(relocated), false + )); + PocketBaseManager manager = new PocketBaseManager( + states, + worlds, + new PluginSettingsProvider(PluginSettings.from(Map.of())) + ); + + assertEquals(relocated, manager.returnPortal(ownerId)); + } + + @Test + void existingStateWithoutRelocationUsesGeneratedReturnPortal() { + UUID ownerId = UUID.randomUUID(); + PocketPortalLocation generated = new PocketPortalLocation( + UUID.randomUUID(), "pocket", -2, 65, 0, PocketPortalAxis.X + ); + PocketBaseStateManager states = mock(PocketBaseStateManager.class); + PocketBaseWorldService worlds = mock(PocketBaseWorldService.class); + when(states.state(ownerId)).thenReturn( + new PocketBaseState(ownerId, 1, Optional.empty()) + ); + when(worlds.returnPortal(ownerId)).thenReturn(generated); + PocketBaseManager manager = new PocketBaseManager( + states, + worlds, + new PluginSettingsProvider(PluginSettings.from(Map.of())) + ); + + assertEquals(generated, manager.returnPortal(ownerId)); + } + @Test void persistsAndAppliesMobSpawningPreference() throws Exception { UUID ownerId = UUID.randomUUID(); diff --git a/src/test/java/games/dmg/spigotbase/PocketBaseWorldServiceTest.java b/src/test/java/games/dmg/spigotbase/PocketBaseWorldServiceTest.java index e0d7f9f..ee2768f 100644 --- a/src/test/java/games/dmg/spigotbase/PocketBaseWorldServiceTest.java +++ b/src/test/java/games/dmg/spigotbase/PocketBaseWorldServiceTest.java @@ -1,6 +1,8 @@ package games.dmg.spigotbase; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -46,6 +48,33 @@ final class PocketBaseWorldServiceTest { assertEquals(ownerId, service.ownerForWorld(worldId).orElseThrow()); } + @Test + void checksRelocatedReturnPortalFrameAtItsSavedLocation() { + UUID ownerId = UUID.randomUUID(); + UUID worldId = UUID.randomUUID(); + Server server = mock(Server.class); + World world = mock(World.class); + Map blocks = blocks(world); + when(server.getWorld(org.mockito.ArgumentMatchers.anyString())).thenReturn(world); + when(world.getUID()).thenReturn(worldId); + PocketPortalLocation portal = new PocketPortalLocation( + worldId, "pocket", 20, 65, 12, PocketPortalAxis.Z + ); + for (BlockPosition position : PocketPortalGeometry.frameBlocks(portal)) { + when(world.getBlockAt(position.x(), position.y(), position.z()).getType()) + .thenReturn(Material.DIAMOND_BLOCK); + } + PocketBaseWorldService service = new PocketBaseWorldService( + server, + new PluginSettingsProvider(PluginSettings.from(Map.of())) + ); + + assertTrue(service.returnPortalIsIntact(ownerId, portal)); + + when(world.getBlockAt(20, 65, 12).getType()).thenReturn(Material.AIR); + assertFalse(service.returnPortalIsIntact(ownerId, portal)); + } + @Test void reappliesSavedMobSpawningPreferenceWhenWorldLoads() { UUID ownerId = UUID.randomUUID(); diff --git a/src/test/java/games/dmg/spigotbase/YamlPocketBaseRepositoryTest.java b/src/test/java/games/dmg/spigotbase/YamlPocketBaseRepositoryTest.java index e2efc37..470240d 100644 --- a/src/test/java/games/dmg/spigotbase/YamlPocketBaseRepositoryTest.java +++ b/src/test/java/games/dmg/spigotbase/YamlPocketBaseRepositoryTest.java @@ -2,6 +2,7 @@ package games.dmg.spigotbase; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.nio.file.Files; import java.nio.file.Path; @@ -25,6 +26,9 @@ final class YamlPocketBaseRepositoryTest { Optional.of(new PocketPortalLocation( worldId, "world", 10, 65, -4, PocketPortalAxis.X )), + Optional.of(new PocketPortalLocation( + worldId, "pocket", 20, 65, 12, PocketPortalAxis.Z + )), true ); YamlPocketBaseRepository repository = new YamlPocketBaseRepository( @@ -43,6 +47,9 @@ final class YamlPocketBaseRepositoryTest { Files.writeString(stateFile, "owners:\n " + ownerId + ":\n level: 1\n"); YamlPocketBaseRepository repository = new YamlPocketBaseRepository(stateFile); - assertFalse(repository.load().get(ownerId).mobSpawningEnabled()); + PocketBaseState loaded = repository.load().get(ownerId); + + assertFalse(loaded.mobSpawningEnabled()); + assertTrue(loaded.returnPortal().isEmpty()); } }