feat(pocket-base): add reusable keystone travel
This commit is contained in:
@@ -0,0 +1,321 @@
|
||||
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.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;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
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.inventory.PrepareItemCraftEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import org.bukkit.inventory.CraftingInventory;
|
||||
import org.bukkit.inventory.InventoryView;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.ShapedRecipe;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class PocketBaseKeystoneServiceTest {
|
||||
@Test
|
||||
void recipeUsesNetheriteOnCardinalsAndObsidianInCorners() {
|
||||
PocketBaseKeystoneService.RecipeSpec recipe =
|
||||
PocketBaseKeystoneService.recipeSpecification();
|
||||
|
||||
assertEquals("ONO", recipe.top());
|
||||
assertEquals("NEN", recipe.middle());
|
||||
assertEquals("ONO", recipe.bottom());
|
||||
assertEquals(Material.OBSIDIAN, recipe.obsidian());
|
||||
assertEquals(Material.NETHERITE_INGOT, recipe.netherite());
|
||||
assertEquals(Material.ENDER_PEARL, recipe.enderPearl());
|
||||
}
|
||||
|
||||
@Test
|
||||
void lockedPlayersCannotCompleteTheKeystoneRecipe() {
|
||||
NamespacedKey key = NamespacedKey.minecraft("pocket_base_keystone");
|
||||
UUID playerId = UUID.randomUUID();
|
||||
Player player = mock(Player.class);
|
||||
PrepareItemCraftEvent event = mock(PrepareItemCraftEvent.class);
|
||||
InventoryView view = mock(InventoryView.class);
|
||||
CraftingInventory inventory = mock(CraftingInventory.class);
|
||||
ShapedRecipe recipe = mock(ShapedRecipe.class);
|
||||
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
|
||||
PocketBaseKeystoneService service = new PocketBaseKeystoneService(
|
||||
mock(org.bukkit.Server.class), pocketBases, mock(KeystoneReturnStore.class),
|
||||
key, Logger.getAnonymousLogger()
|
||||
);
|
||||
|
||||
when(player.getUniqueId()).thenReturn(playerId);
|
||||
when(event.getView()).thenReturn(view);
|
||||
when(view.getPlayer()).thenReturn(player);
|
||||
when(event.getInventory()).thenReturn(inventory);
|
||||
when(event.getRecipe()).thenReturn(recipe);
|
||||
when(recipe.getKey()).thenReturn(key);
|
||||
when(pocketBases.state(playerId)).thenReturn(
|
||||
new PocketBaseState(playerId, 0, Optional.empty())
|
||||
);
|
||||
|
||||
service.onPrepareCraft(event);
|
||||
|
||||
verify(inventory).setResult(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void lockedPlayerCannotUseATransferredKeystone() throws Exception {
|
||||
NamespacedKey key = NamespacedKey.minecraft("pocket_base_keystone");
|
||||
UUID playerId = UUID.randomUUID();
|
||||
Player player = mock(Player.class);
|
||||
PlayerInteractEvent event = mock(PlayerInteractEvent.class);
|
||||
ItemStack keystone = markedKeystone(key);
|
||||
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
|
||||
KeystoneReturnStore returns = mock(KeystoneReturnStore.class);
|
||||
PocketBaseKeystoneService service = new PocketBaseKeystoneService(
|
||||
mock(Server.class), pocketBases, returns, key, Logger.getAnonymousLogger()
|
||||
);
|
||||
|
||||
when(player.getUniqueId()).thenReturn(playerId);
|
||||
when(event.getPlayer()).thenReturn(player);
|
||||
when(event.getAction()).thenReturn(Action.RIGHT_CLICK_AIR);
|
||||
when(event.getItem()).thenReturn(keystone);
|
||||
when(pocketBases.state(playerId)).thenReturn(
|
||||
new PocketBaseState(playerId, 0, Optional.empty())
|
||||
);
|
||||
|
||||
service.onUse(event);
|
||||
|
||||
verify(returns, never()).record(any(UUID.class), any(KeystoneReturnLocation.class));
|
||||
verify(player, never()).teleport(
|
||||
any(Location.class), any(PlayerTeleportEvent.TeleportCause.class)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unlockedPlayerEntersOwnPocketAndRecordsExactReturnDestination() throws Exception {
|
||||
NamespacedKey key = NamespacedKey.minecraft("pocket_base_keystone");
|
||||
UUID playerId = UUID.randomUUID();
|
||||
UUID worldId = UUID.randomUUID();
|
||||
Player player = mock(Player.class);
|
||||
World world = mock(World.class);
|
||||
Location origin = new Location(world, 12.25, 70.5, -4.75, 123.0F, -15.5F);
|
||||
Location arrival = new Location(mock(World.class), 0.5, 65.0, 0.5);
|
||||
PlayerInteractEvent event = mock(PlayerInteractEvent.class);
|
||||
ItemStack keystone = markedKeystone(key);
|
||||
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
|
||||
KeystoneReturnStore returns = mock(KeystoneReturnStore.class);
|
||||
PocketBaseKeystoneService service = new PocketBaseKeystoneService(
|
||||
mock(Server.class), pocketBases, returns, key, Logger.getAnonymousLogger()
|
||||
);
|
||||
|
||||
when(player.getUniqueId()).thenReturn(playerId);
|
||||
when(player.getLocation()).thenReturn(origin);
|
||||
when(player.getWorld()).thenReturn(world);
|
||||
when(world.getUID()).thenReturn(worldId);
|
||||
when(world.getName()).thenReturn("world");
|
||||
when(event.getPlayer()).thenReturn(player);
|
||||
when(event.getAction()).thenReturn(Action.RIGHT_CLICK_AIR);
|
||||
when(event.getItem()).thenReturn(keystone);
|
||||
when(pocketBases.state(playerId)).thenReturn(
|
||||
new PocketBaseState(playerId, 1, Optional.empty())
|
||||
);
|
||||
when(pocketBases.ownerForPocketWorld(worldId)).thenReturn(Optional.empty());
|
||||
when(pocketBases.pocketArrival(playerId)).thenReturn(arrival);
|
||||
when(player.teleport(arrival, PlayerTeleportEvent.TeleportCause.PLUGIN))
|
||||
.thenReturn(true);
|
||||
|
||||
service.onUse(event);
|
||||
|
||||
verify(returns).record(playerId, new KeystoneReturnLocation(
|
||||
worldId, "world", 12.25, 70.5, -4.75, 123.0F, -15.5F
|
||||
));
|
||||
verify(player).teleport(arrival, PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||
verify(player).setFallDistance(0.0F);
|
||||
verify(event).setCancelled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void playerReturnsFromOwnPocketToRecordedSafeDestination() {
|
||||
NamespacedKey key = NamespacedKey.minecraft("pocket_base_keystone");
|
||||
UUID playerId = UUID.randomUUID();
|
||||
UUID pocketWorldId = UUID.randomUUID();
|
||||
UUID returnWorldId = UUID.randomUUID();
|
||||
Player player = mock(Player.class);
|
||||
World pocketWorld = mock(World.class);
|
||||
World returnWorld = mock(World.class);
|
||||
Block ground = mock(Block.class);
|
||||
Block feet = mock(Block.class);
|
||||
Block head = mock(Block.class);
|
||||
PlayerInteractEvent event = mock(PlayerInteractEvent.class);
|
||||
ItemStack keystone = markedKeystone(key);
|
||||
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
|
||||
KeystoneReturnStore returns = mock(KeystoneReturnStore.class);
|
||||
Server server = mock(Server.class);
|
||||
KeystoneReturnLocation saved = new KeystoneReturnLocation(
|
||||
returnWorldId, "world", 12.25, 70.5, -4.75, 123.0F, -15.5F
|
||||
);
|
||||
PocketBaseKeystoneService service = new PocketBaseKeystoneService(
|
||||
server, pocketBases, returns, key, Logger.getAnonymousLogger()
|
||||
);
|
||||
|
||||
when(player.getUniqueId()).thenReturn(playerId);
|
||||
when(player.getWorld()).thenReturn(pocketWorld);
|
||||
when(pocketWorld.getUID()).thenReturn(pocketWorldId);
|
||||
when(event.getPlayer()).thenReturn(player);
|
||||
when(event.getAction()).thenReturn(Action.RIGHT_CLICK_AIR);
|
||||
when(event.getItem()).thenReturn(keystone);
|
||||
when(pocketBases.state(playerId)).thenReturn(
|
||||
new PocketBaseState(playerId, 1, Optional.empty())
|
||||
);
|
||||
when(pocketBases.ownerForPocketWorld(pocketWorldId))
|
||||
.thenReturn(Optional.of(playerId));
|
||||
when(returns.destination(playerId)).thenReturn(Optional.of(saved));
|
||||
when(server.getWorld(returnWorldId)).thenReturn(returnWorld);
|
||||
when(returnWorld.getMinHeight()).thenReturn(-64);
|
||||
when(returnWorld.getMaxHeight()).thenReturn(320);
|
||||
when(returnWorld.getBlockAt(12, 69, -5)).thenReturn(ground);
|
||||
when(returnWorld.getBlockAt(12, 70, -5)).thenReturn(feet);
|
||||
when(returnWorld.getBlockAt(12, 71, -5)).thenReturn(head);
|
||||
when(ground.getType()).thenReturn(Material.STONE);
|
||||
when(feet.getType()).thenReturn(Material.AIR);
|
||||
when(head.getType()).thenReturn(Material.AIR);
|
||||
when(feet.isPassable()).thenReturn(true);
|
||||
when(head.isPassable()).thenReturn(true);
|
||||
when(player.teleport(
|
||||
new Location(returnWorld, 12.25, 70.5, -4.75, 123.0F, -15.5F),
|
||||
PlayerTeleportEvent.TeleportCause.PLUGIN
|
||||
)).thenReturn(true);
|
||||
|
||||
service.onUse(event);
|
||||
|
||||
verify(player).teleport(
|
||||
new Location(returnWorld, 12.25, 70.5, -4.75, 123.0F, -15.5F),
|
||||
PlayerTeleportEvent.TeleportCause.PLUGIN
|
||||
);
|
||||
verify(player).setFallDistance(0.0F);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unavailableAndUnsafeReturnsDoNotTeleportThePlayer() {
|
||||
NamespacedKey key = NamespacedKey.minecraft("pocket_base_keystone");
|
||||
UUID missingPocketWorldId = UUID.randomUUID();
|
||||
UUID unsafePocketWorldId = UUID.randomUUID();
|
||||
UUID missingPlayerId = UUID.randomUUID();
|
||||
UUID unsafePlayerId = UUID.randomUUID();
|
||||
UUID missingWorldId = UUID.randomUUID();
|
||||
UUID unsafeWorldId = UUID.randomUUID();
|
||||
World missingPocketWorld = mock(World.class);
|
||||
World unsafePocketWorld = mock(World.class);
|
||||
World unsafeWorld = mock(World.class);
|
||||
Player missingPlayer = mock(Player.class);
|
||||
Player unsafePlayer = mock(Player.class);
|
||||
PlayerInteractEvent missingEvent = mock(PlayerInteractEvent.class);
|
||||
PlayerInteractEvent unsafeEvent = mock(PlayerInteractEvent.class);
|
||||
ItemStack missingKeystone = markedKeystone(key);
|
||||
ItemStack unsafeKeystone = markedKeystone(key);
|
||||
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
|
||||
KeystoneReturnStore returns = mock(KeystoneReturnStore.class);
|
||||
Server server = mock(Server.class);
|
||||
PocketBaseKeystoneService service = new PocketBaseKeystoneService(
|
||||
server, pocketBases, returns, key, Logger.getAnonymousLogger()
|
||||
);
|
||||
|
||||
when(missingPocketWorld.getUID()).thenReturn(missingPocketWorldId);
|
||||
when(unsafePocketWorld.getUID()).thenReturn(unsafePocketWorldId);
|
||||
when(missingPlayer.getUniqueId()).thenReturn(missingPlayerId);
|
||||
when(missingPlayer.getWorld()).thenReturn(missingPocketWorld);
|
||||
when(unsafePlayer.getUniqueId()).thenReturn(unsafePlayerId);
|
||||
when(unsafePlayer.getWorld()).thenReturn(unsafePocketWorld);
|
||||
when(missingEvent.getPlayer()).thenReturn(missingPlayer);
|
||||
when(missingEvent.getAction()).thenReturn(Action.RIGHT_CLICK_AIR);
|
||||
when(missingEvent.getItem()).thenReturn(missingKeystone);
|
||||
when(unsafeEvent.getPlayer()).thenReturn(unsafePlayer);
|
||||
when(unsafeEvent.getAction()).thenReturn(Action.RIGHT_CLICK_AIR);
|
||||
when(unsafeEvent.getItem()).thenReturn(unsafeKeystone);
|
||||
when(pocketBases.state(missingPlayerId)).thenReturn(
|
||||
new PocketBaseState(missingPlayerId, 1, Optional.empty())
|
||||
);
|
||||
when(pocketBases.state(unsafePlayerId)).thenReturn(
|
||||
new PocketBaseState(unsafePlayerId, 1, Optional.empty())
|
||||
);
|
||||
when(pocketBases.ownerForPocketWorld(missingPocketWorldId))
|
||||
.thenReturn(Optional.of(missingPlayerId));
|
||||
when(pocketBases.ownerForPocketWorld(unsafePocketWorldId))
|
||||
.thenReturn(Optional.of(unsafePlayerId));
|
||||
when(returns.destination(missingPlayerId)).thenReturn(Optional.of(
|
||||
new KeystoneReturnLocation(
|
||||
missingWorldId, "missing", 0.5, 65.0, 0.5, 0.0F, 0.0F
|
||||
)
|
||||
));
|
||||
when(returns.destination(unsafePlayerId)).thenReturn(Optional.of(
|
||||
new KeystoneReturnLocation(
|
||||
unsafeWorldId, "unsafe", 0.5, 65.0, 0.5, 0.0F, 0.0F
|
||||
)
|
||||
));
|
||||
when(server.getWorld(unsafeWorldId)).thenReturn(unsafeWorld);
|
||||
when(unsafeWorld.getMinHeight()).thenReturn(-64);
|
||||
when(unsafeWorld.getMaxHeight()).thenReturn(320);
|
||||
Block unsafeBlock = mock(Block.class);
|
||||
when(unsafeBlock.getType()).thenReturn(Material.AIR);
|
||||
when(unsafeWorld.getBlockAt(anyInt(), anyInt(), anyInt()))
|
||||
.thenReturn(unsafeBlock);
|
||||
|
||||
service.onUse(missingEvent);
|
||||
service.onUse(unsafeEvent);
|
||||
|
||||
verify(missingPlayer, never()).teleport(
|
||||
any(Location.class), any(PlayerTeleportEvent.TeleportCause.class)
|
||||
);
|
||||
verify(unsafePlayer, never()).teleport(
|
||||
any(Location.class), any(PlayerTeleportEvent.TeleportCause.class)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void authenticatesKeystonesByPersistentMetadataInsteadOfTheirName() {
|
||||
NamespacedKey key = NamespacedKey.minecraft("pocket_base_keystone");
|
||||
ItemStack marked = mock(ItemStack.class);
|
||||
ItemMeta markedMeta = mock(ItemMeta.class);
|
||||
PersistentDataContainer markedData = mock(PersistentDataContainer.class);
|
||||
ItemStack renamedOnly = mock(ItemStack.class);
|
||||
ItemMeta renamedMeta = mock(ItemMeta.class);
|
||||
PersistentDataContainer renamedData = mock(PersistentDataContainer.class);
|
||||
|
||||
when(marked.getType()).thenReturn(Material.RECOVERY_COMPASS);
|
||||
when(marked.getItemMeta()).thenReturn(markedMeta);
|
||||
when(markedMeta.getPersistentDataContainer()).thenReturn(markedData);
|
||||
when(markedData.get(key, PersistentDataType.BYTE)).thenReturn((byte) 1);
|
||||
when(renamedOnly.getType()).thenReturn(Material.RECOVERY_COMPASS);
|
||||
when(renamedOnly.getItemMeta()).thenReturn(renamedMeta);
|
||||
when(renamedMeta.getPersistentDataContainer()).thenReturn(renamedData);
|
||||
|
||||
assertTrue(PocketBaseKeystoneService.isKeystone(marked, key));
|
||||
assertFalse(PocketBaseKeystoneService.isKeystone(renamedOnly, key));
|
||||
}
|
||||
|
||||
private static ItemStack markedKeystone(NamespacedKey key) {
|
||||
ItemStack item = mock(ItemStack.class);
|
||||
ItemMeta metadata = mock(ItemMeta.class);
|
||||
PersistentDataContainer data = mock(PersistentDataContainer.class);
|
||||
when(item.getType()).thenReturn(Material.RECOVERY_COMPASS);
|
||||
when(item.getItemMeta()).thenReturn(metadata);
|
||||
when(metadata.getPersistentDataContainer()).thenReturn(data);
|
||||
when(data.get(key, PersistentDataType.BYTE)).thenReturn((byte) 1);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package games.dmg.spigotbase;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
final class YamlKeystoneReturnRepositoryTest {
|
||||
@TempDir
|
||||
Path temporaryDirectory;
|
||||
|
||||
@Test
|
||||
void roundTripsExactPlayerReturnDestinations() throws Exception {
|
||||
UUID playerId = UUID.randomUUID();
|
||||
KeystoneReturnLocation expected = new KeystoneReturnLocation(
|
||||
UUID.randomUUID(), "world", 12.25, 70.5, -4.75, 123.0F, -15.5F
|
||||
);
|
||||
YamlKeystoneReturnRepository repository = new YamlKeystoneReturnRepository(
|
||||
temporaryDirectory.resolve("keystone-returns.yml")
|
||||
);
|
||||
|
||||
repository.save(Map.of(playerId, expected));
|
||||
|
||||
assertEquals(expected, repository.load().get(playerId));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user