feat(pocket-base): add persistent pocket dimensions
This commit is contained in:
@@ -10,6 +10,7 @@ import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -22,6 +23,7 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
final class BaseSettingsCommandTest {
|
||||
@Test
|
||||
@@ -89,6 +91,10 @@ final class BaseSettingsCommandTest {
|
||||
List.of("allowed"),
|
||||
command.onTabComplete(null, null, "basesettings", new String[] {"visitors", "a"})
|
||||
);
|
||||
assertEquals(
|
||||
List.of("upgrade"),
|
||||
command.onTabComplete(null, null, "basesettings", new String[] {"pocket", "u"})
|
||||
);
|
||||
for (String setting : List.of("navigation", "flight", "border", "spawnable", "bossbar")) {
|
||||
assertEquals(
|
||||
List.of("disable"),
|
||||
@@ -107,7 +113,7 @@ final class BaseSettingsCommandTest {
|
||||
|
||||
assertEquals(
|
||||
List.of(
|
||||
"status", "upgrade", "visitors", "navigation", "flight", "border",
|
||||
"status", "upgrade", "pocket", "visitors", "navigation", "flight", "border",
|
||||
"spawnable", "bossbar"
|
||||
),
|
||||
command.onTabComplete(null, null, "basesettings", new String[] {""})
|
||||
@@ -155,6 +161,81 @@ final class BaseSettingsCommandTest {
|
||||
assertTrue(updated.get().visitorsEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
void pocketUpgradePurchasesFirstLevelAfterBaseIv() throws Exception {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
Player player = mock(Player.class);
|
||||
when(player.getUniqueId()).thenReturn(playerId);
|
||||
when(player.getName()).thenReturn("Builder");
|
||||
PlayerInventory inventory = mock(PlayerInventory.class);
|
||||
when(player.getInventory()).thenReturn(inventory);
|
||||
when(inventory.getStorageContents()).thenReturn(new ItemStack[] {
|
||||
new ItemStack(Material.DIAMOND_BLOCK, 64)
|
||||
});
|
||||
PlayerState owner = PlayerState.newPlayer(playerId, "Builder")
|
||||
.withAdministrativeLevels(4, 0, 0, 0, 0, false, false, true)
|
||||
.withBase(
|
||||
new BaseLocation(UUID.randomUUID(), "world", 0, 64, 0, 0, 0),
|
||||
Instant.EPOCH
|
||||
);
|
||||
BaseStateManager stateManager = mock(BaseStateManager.class);
|
||||
when(stateManager.player(playerId, "Builder")).thenReturn(owner);
|
||||
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
|
||||
when(pocketBases.state(playerId)).thenReturn(PocketBaseState.locked(playerId));
|
||||
when(pocketBases.upgrade(playerId)).thenReturn(
|
||||
new PocketBaseState(playerId, 1, java.util.Optional.empty())
|
||||
);
|
||||
BaseSettingsCommand command = new BaseSettingsCommand(
|
||||
stateManager,
|
||||
new PluginSettingsProvider(PluginSettings.from(Map.of())),
|
||||
mock(BaseFlightController.class),
|
||||
pocketBases
|
||||
);
|
||||
|
||||
command.onCommand(player, null, "basesettings", new String[] {"pocket", "upgrade"});
|
||||
|
||||
verify(pocketBases).upgrade(playerId);
|
||||
verify(inventory).setStorageContents(any(ItemStack[].class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void failedPocketUpgradeRestoresPayment() throws Exception {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
Player player = mock(Player.class);
|
||||
when(player.getUniqueId()).thenReturn(playerId);
|
||||
when(player.getName()).thenReturn("Builder");
|
||||
PlayerInventory inventory = mock(PlayerInventory.class);
|
||||
when(player.getInventory()).thenReturn(inventory);
|
||||
when(inventory.getStorageContents()).thenReturn(new ItemStack[] {
|
||||
new ItemStack(Material.DIAMOND_BLOCK, 64)
|
||||
});
|
||||
PlayerState owner = PlayerState.newPlayer(playerId, "Builder")
|
||||
.withAdministrativeLevels(4, 0, 0, 0, 0, false, false, true)
|
||||
.withBase(
|
||||
new BaseLocation(UUID.randomUUID(), "world", 0, 64, 0, 0, 0),
|
||||
Instant.EPOCH
|
||||
);
|
||||
BaseStateManager stateManager = mock(BaseStateManager.class);
|
||||
when(stateManager.player(playerId, "Builder")).thenReturn(owner);
|
||||
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
|
||||
when(pocketBases.state(playerId)).thenReturn(PocketBaseState.locked(playerId));
|
||||
when(pocketBases.upgrade(playerId)).thenThrow(new IOException("save failed"));
|
||||
BaseSettingsCommand command = new BaseSettingsCommand(
|
||||
stateManager,
|
||||
new PluginSettingsProvider(PluginSettings.from(Map.of())),
|
||||
mock(BaseFlightController.class),
|
||||
pocketBases
|
||||
);
|
||||
|
||||
command.onCommand(player, null, "basesettings", new String[] {"pocket", "upgrade"});
|
||||
|
||||
ArgumentCaptor<ItemStack[]> contents = ArgumentCaptor.forClass(ItemStack[].class);
|
||||
verify(inventory, times(2)).setStorageContents(contents.capture());
|
||||
ItemStack[] restored = contents.getAllValues().get(1);
|
||||
assertEquals(Material.DIAMOND_BLOCK, restored[0].getType());
|
||||
assertEquals(64, restored[0].getAmount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bossbarEnableIsIdempotent() {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
|
||||
@@ -21,6 +21,25 @@ final class PluginSettingsTest {
|
||||
assertEquals(250, settings.spawnableOverlayUnlockPlacements());
|
||||
assertEquals(10_800L, settings.initialTeleportCooldownSeconds());
|
||||
assertEquals(128, settings.visitorUnlockDiamondCost());
|
||||
assertEquals(64, settings.pocketBaseUnlockCost());
|
||||
assertEquals(16, settings.pocketBaseUpgradeCost());
|
||||
assertEquals("DIAMOND_BLOCK", settings.pocketBaseCurrencyMaterial());
|
||||
assertEquals("DIAMOND_BLOCK", settings.pocketBasePortalFrameMaterial());
|
||||
}
|
||||
|
||||
@Test
|
||||
void pocketBaseCostsAndMaterialsAreConfigurable() {
|
||||
PluginSettings settings = PluginSettings.from(Map.of(
|
||||
"pocket-base-unlock-cost", 32,
|
||||
"pocket-base-upgrade-cost", 8,
|
||||
"pocket-base-currency-material", "EMERALD_BLOCK",
|
||||
"pocket-base-portal-frame-material", "GOLD_BLOCK"
|
||||
));
|
||||
|
||||
assertEquals(32, settings.pocketBaseUnlockCost());
|
||||
assertEquals(8, settings.pocketBaseUpgradeCost());
|
||||
assertEquals("EMERALD_BLOCK", settings.pocketBaseCurrencyMaterial());
|
||||
assertEquals("GOLD_BLOCK", settings.pocketBasePortalFrameMaterial());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package games.dmg.spigotbase;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class PocketBaseControllerTest {
|
||||
@Test
|
||||
void ownerCanActivateCompleteFrameInsideNormalBase() throws Exception {
|
||||
UUID ownerId = UUID.randomUUID();
|
||||
UUID worldId = UUID.randomUUID();
|
||||
PocketPortalLocation portal = new PocketPortalLocation(
|
||||
worldId, "world", -2, 64, 0, PocketPortalAxis.X
|
||||
);
|
||||
Set<BlockPosition> frame = new HashSet<>(PocketPortalGeometry.frameBlocks(portal));
|
||||
Player owner = mock(Player.class);
|
||||
World world = mock(World.class);
|
||||
Block clicked = block(world, -2, 66, 0, Material.DIAMOND_BLOCK);
|
||||
PlayerInteractEvent event = mock(PlayerInteractEvent.class);
|
||||
BaseStateManager baseStates = mock(BaseStateManager.class);
|
||||
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
|
||||
PluginSettings settings = PluginSettings.from(Map.of());
|
||||
PlayerState ownerState = PlayerState.newPlayer(ownerId, "Owner")
|
||||
.withAdministrativeLevels(4, 0, 0, 0, 0, false, false, true)
|
||||
.withBase(new BaseLocation(worldId, "world", 0, 64, 0, 0, 0), Instant.EPOCH);
|
||||
|
||||
when(owner.getUniqueId()).thenReturn(ownerId);
|
||||
when(owner.getName()).thenReturn("Owner");
|
||||
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("world");
|
||||
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(baseStates.player(ownerId, "Owner")).thenReturn(ownerState);
|
||||
when(pocketBases.state(ownerId)).thenReturn(
|
||||
new PocketBaseState(ownerId, 1, Optional.empty())
|
||||
);
|
||||
when(pocketBases.activateEntrance(ownerId, portal)).thenReturn(
|
||||
new PocketBaseState(ownerId, 1, Optional.of(portal))
|
||||
);
|
||||
PocketBaseController controller = new PocketBaseController(
|
||||
mock(Server.class),
|
||||
baseStates,
|
||||
new BaseBoundsService(settings),
|
||||
pocketBases,
|
||||
new PluginSettingsProvider(settings),
|
||||
Logger.getAnonymousLogger()
|
||||
);
|
||||
|
||||
controller.onActivate(event);
|
||||
|
||||
verify(pocketBases).activateEntrance(ownerId, portal);
|
||||
verify(event).setCancelled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void placementOutsideUnlockedPocketBoundaryIsCancelled() {
|
||||
UUID ownerId = UUID.randomUUID();
|
||||
UUID worldId = UUID.randomUUID();
|
||||
World world = mock(World.class);
|
||||
Block placed = block(world, 32, 65, 0, Material.STONE);
|
||||
Player player = mock(Player.class);
|
||||
BlockPlaceEvent event = mock(BlockPlaceEvent.class);
|
||||
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
|
||||
PluginSettings settings = PluginSettings.from(Map.of());
|
||||
|
||||
when(world.getUID()).thenReturn(worldId);
|
||||
when(event.getBlockPlaced()).thenReturn(placed);
|
||||
when(event.getPlayer()).thenReturn(player);
|
||||
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.onPlace(event);
|
||||
|
||||
verify(event).setCancelled(true);
|
||||
}
|
||||
|
||||
private static Block block(
|
||||
World world,
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
Material material
|
||||
) {
|
||||
Block block = mock(Block.class);
|
||||
when(block.getWorld()).thenReturn(world);
|
||||
when(block.getX()).thenReturn(x);
|
||||
when(block.getY()).thenReturn(y);
|
||||
when(block.getZ()).thenReturn(z);
|
||||
when(block.getType()).thenReturn(material);
|
||||
return block;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
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.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class PocketBasePolicyTest {
|
||||
private final PocketBasePolicy policy = new PocketBasePolicy(PluginSettings.from(Map.of()));
|
||||
|
||||
@Test
|
||||
void levelsExpandBySixteenBlocksOnEverySide() {
|
||||
assertEquals(64, policy.size(1));
|
||||
assertEquals(96, policy.size(2));
|
||||
assertEquals(128, policy.size(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
void unlockAndLaterLevelsUseTheirConfiguredPrices() {
|
||||
assertEquals(64, policy.price(PocketBaseState.locked(UUID.randomUUID())));
|
||||
assertEquals(16, policy.price(new PocketBaseState(UUID.randomUUID(), 1, java.util.Optional.empty())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildBoundaryMatchesUnlockedPlatform() {
|
||||
assertTrue(policy.contains(1, -32, -32));
|
||||
assertTrue(policy.contains(1, 31, 31));
|
||||
assertFalse(policy.contains(1, -33, 0));
|
||||
assertFalse(policy.contains(1, 32, 0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package games.dmg.spigotbase;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldCreator;
|
||||
import org.bukkit.block.Block;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class PocketBaseWorldServiceTest {
|
||||
@Test
|
||||
void firstLevelGeneratesGrassPlatformAndReturnPortal() {
|
||||
UUID ownerId = UUID.randomUUID();
|
||||
UUID worldId = UUID.randomUUID();
|
||||
Server server = mock(Server.class);
|
||||
World world = mock(World.class);
|
||||
Map<BlockPosition, Block> blocks = blocks(world);
|
||||
when(server.createWorld(any(WorldCreator.class))).thenReturn(world);
|
||||
when(world.getUID()).thenReturn(worldId);
|
||||
when(world.getName()).thenReturn("pocket");
|
||||
PocketBaseWorldService service = new PocketBaseWorldService(
|
||||
server,
|
||||
new PluginSettingsProvider(PluginSettings.from(Map.of()))
|
||||
);
|
||||
|
||||
service.expand(ownerId, 0, 1);
|
||||
|
||||
verify(blocks.get(new BlockPosition(-32, 64, -32)))
|
||||
.setType(Material.GRASS_BLOCK, false);
|
||||
verify(blocks.get(new BlockPosition(31, 61, 31))).setType(Material.DIRT, false);
|
||||
verify(blocks.get(new BlockPosition(-2, 65, 0)))
|
||||
.setType(Material.DIAMOND_BLOCK, false);
|
||||
verify(world).setSpawnLocation(0, 65, 5);
|
||||
assertEquals(ownerId, service.ownerForWorld(worldId).orElseThrow());
|
||||
}
|
||||
|
||||
@Test
|
||||
void laterLevelAddsRingWithoutOverwritingExistingTerrain() {
|
||||
UUID ownerId = UUID.randomUUID();
|
||||
Server server = mock(Server.class);
|
||||
World world = mock(World.class);
|
||||
Map<BlockPosition, Block> blocks = blocks(world);
|
||||
when(server.createWorld(any(WorldCreator.class))).thenReturn(world);
|
||||
when(world.getUID()).thenReturn(UUID.randomUUID());
|
||||
when(world.getName()).thenReturn("pocket");
|
||||
PocketBaseWorldService service = new PocketBaseWorldService(
|
||||
server,
|
||||
new PluginSettingsProvider(PluginSettings.from(Map.of()))
|
||||
);
|
||||
Block existingCenter = world.getBlockAt(0, 64, 0);
|
||||
|
||||
service.expand(ownerId, 1, 2);
|
||||
|
||||
verify(blocks.get(new BlockPosition(47, 64, 47)))
|
||||
.setType(Material.GRASS_BLOCK, false);
|
||||
verify(existingCenter, never())
|
||||
.setType(any(Material.class), org.mockito.ArgumentMatchers.anyBoolean());
|
||||
}
|
||||
|
||||
private static Map<BlockPosition, Block> blocks(World world) {
|
||||
Map<BlockPosition, Block> blocks = new HashMap<>();
|
||||
when(world.getBlockAt(
|
||||
org.mockito.ArgumentMatchers.anyInt(),
|
||||
org.mockito.ArgumentMatchers.anyInt(),
|
||||
org.mockito.ArgumentMatchers.anyInt()
|
||||
)).thenAnswer(invocation -> {
|
||||
BlockPosition position = new BlockPosition(
|
||||
invocation.getArgument(0), invocation.getArgument(1), invocation.getArgument(2)
|
||||
);
|
||||
return blocks.computeIfAbsent(position, ignored -> mock(Block.class));
|
||||
});
|
||||
return blocks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package games.dmg.spigotbase;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class PocketPortalGeometryTest {
|
||||
@Test
|
||||
void findsStandardFrameContainingIgnitedBlock() {
|
||||
UUID worldId = UUID.randomUUID();
|
||||
PocketPortalLocation expected = new PocketPortalLocation(
|
||||
worldId, "world", 10, 64, 20, PocketPortalAxis.X
|
||||
);
|
||||
Set<BlockPosition> frame = new HashSet<>(PocketPortalGeometry.frameBlocks(expected));
|
||||
|
||||
Optional<PocketPortalLocation> found = PocketPortalGeometry.findFrame(
|
||||
new BlockPosition(10, 66, 20),
|
||||
worldId,
|
||||
"world",
|
||||
frame::contains,
|
||||
ignored -> true
|
||||
);
|
||||
|
||||
assertEquals(expected, found.orElseThrow());
|
||||
}
|
||||
|
||||
@Test
|
||||
void identifiesPortalInteriorAndRequiredFrameBlocks() {
|
||||
PocketPortalLocation portal = new PocketPortalLocation(
|
||||
UUID.randomUUID(), "world", 0, 64, 0, PocketPortalAxis.Z
|
||||
);
|
||||
|
||||
assertTrue(PocketPortalGeometry.isInterior(portal, new BlockPosition(0, 65, 1)));
|
||||
assertTrue(PocketPortalGeometry.isFrame(portal, new BlockPosition(0, 68, 3)));
|
||||
assertEquals(14, PocketPortalGeometry.frameBlocks(portal).size());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package games.dmg.spigotbase;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
final class YamlPocketBaseRepositoryTest {
|
||||
@TempDir
|
||||
Path temporaryDirectory;
|
||||
|
||||
@Test
|
||||
void roundTripsLevelsAndActiveEntrances() throws Exception {
|
||||
UUID ownerId = UUID.randomUUID();
|
||||
UUID worldId = UUID.randomUUID();
|
||||
PocketBaseState expected = new PocketBaseState(
|
||||
ownerId,
|
||||
7,
|
||||
Optional.of(new PocketPortalLocation(
|
||||
worldId, "world", 10, 65, -4, PocketPortalAxis.X
|
||||
))
|
||||
);
|
||||
YamlPocketBaseRepository repository = new YamlPocketBaseRepository(
|
||||
temporaryDirectory.resolve("pocket-bases.yml")
|
||||
);
|
||||
|
||||
repository.save(Map.of(ownerId, expected));
|
||||
|
||||
assertEquals(expected, repository.load().get(ownerId));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user