feat(pocket-base): allow mobs through portals
This commit is contained in:
@@ -48,7 +48,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` 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. 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. Owners can control each category independently with `/basesettings pocket mobs <hostile|passive> <enable|disable>`.
|
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. Players and non-player living mobs can travel through the entrance and active return portal; items, projectiles, and vehicles are not transported. 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. Owners can control each category independently with `/basesettings pocket mobs <hostile|passive> <enable|disable>`.
|
||||||
|
|
||||||
Owners can change biome metadata without altering Pocket Base blocks, entities, inventories, or portals by using `/basesettings pocket type <void|nether|overworld> <subtype>`. Void uses `the_void`; Nether and Overworld accept their compatible vanilla biome names, such as `crimson_forest` or `plains`. Each change defaults to 16 netherite blocks, is configurable, and affects applicable natural mob selection when spawning is enabled. The Pocket Base remains a void-generated normal-environment world; dimension-specific mechanics do not change.
|
Owners can change biome metadata without altering Pocket Base blocks, entities, inventories, or portals by using `/basesettings pocket type <void|nether|overworld> <subtype>`. Void uses `the_void`; Nether and Overworld accept their compatible vanilla biome names, such as `crimson_forest` or `plains`. Each change defaults to 16 netherite blocks, is configurable, and affects applicable natural mob selection when spawning is enabled. The Pocket Base remains a void-generated normal-environment world; dimension-specific mechanics do not change.
|
||||||
|
|
||||||
|
|||||||
@@ -129,3 +129,10 @@ description: Chronological record of material decisions affecting the Spigot Bas
|
|||||||
- Compiled and tested against the exact Purpur 26.2 build 2618 API with Java 25, and updated both CI workflows to use Java 25.
|
- Compiled and tested against the exact Purpur 26.2 build 2618 API with Java 25, and updated both CI workflows to use Java 25.
|
||||||
- Preserved unit-test coverage through server-independent dialog specifications and adapted Bukkit test fixtures to Purpur's registry-aware API behavior.
|
- Preserved unit-test coverage through server-independent dialog specifications and adapted Bukkit test fixtures to Purpur's registry-aware API behavior.
|
||||||
- Verified 128 tests and the Java 25 plugin artifact with `./gradlew clean check jar`.
|
- Verified 128 tests and the Java 25 plugin artifact with `./gradlew clean check jar`.
|
||||||
|
|
||||||
|
## 2026-08-24 — Pocket Base mob portal travel
|
||||||
|
|
||||||
|
- Added five-tick collision scanning that transports non-player living mobs through active Pocket Base entrance and return portals.
|
||||||
|
- Reused safe player destinations and server-spawn fallback behavior while excluding items, projectiles, vehicles, and other non-living entities.
|
||||||
|
- Applied the existing two-second portal cooldown to mobs to prevent immediate repeated transport.
|
||||||
|
- Verified the feature and Java 25 plugin artifact with `./gradlew clean check jar`.
|
||||||
|
|||||||
@@ -19,8 +19,13 @@ As a **Pocket Base owner**, I want to connect my normal base to my Pocket Base w
|
|||||||
- [x] Activating another valid entrance deactivates the previous entrance without removing its frame blocks.
|
- [x] Activating another valid entrance deactivates the previous entrance without removing its frame blocks.
|
||||||
- [x] Breaking any required frame block immediately deactivates the entrance.
|
- [x] Breaking any required frame block immediately deactivates the entrance.
|
||||||
- [x] Any player can use an active entrance without a separate visitor setting, warm-up, or cooldown.
|
- [x] Any player can use an active entrance without a separate visitor setting, warm-up, or cooldown.
|
||||||
|
- [x] Non-player living mobs entering an active entrance portal are teleported into that Pocket Base.
|
||||||
|
- [x] Portal cooldown handling prevents transported mobs from immediately bouncing back.
|
||||||
|
- [x] Items, projectiles, vehicles, and other non-living entities are not transported.
|
||||||
- [x] Visitors can build, break blocks, open containers, and otherwise interact normally inside the Pocket Base.
|
- [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] The generated return portal sends players safely to the owner's active entrance.
|
||||||
|
- [x] Non-player living mobs entering the active, intact return portal are teleported safely outside the normal-world entrance.
|
||||||
|
- [x] Mob return travel falls back safely to the server spawn when the owner has no valid normal-world entrance.
|
||||||
- [x] Only the owner can activate a complete portal frame inside their own Pocket Base with flint and steel.
|
- [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] 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] 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.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import org.bukkit.ChatColor;
|
import org.bukkit.ChatColor;
|
||||||
@@ -15,6 +16,7 @@ import org.bukkit.Server;
|
|||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.Mob;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.EventPriority;
|
import org.bukkit.event.EventPriority;
|
||||||
@@ -26,6 +28,7 @@ import org.bukkit.event.entity.EntityDamageEvent;
|
|||||||
import org.bukkit.event.player.PlayerInteractEvent;
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
import org.bukkit.event.player.PlayerMoveEvent;
|
import org.bukkit.event.player.PlayerMoveEvent;
|
||||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||||
|
import org.bukkit.util.BoundingBox;
|
||||||
|
|
||||||
final class PocketBaseController implements Listener, Runnable {
|
final class PocketBaseController implements Listener, Runnable {
|
||||||
private static final int VOID_RETURN_Y = -64;
|
private static final int VOID_RETURN_Y = -64;
|
||||||
@@ -63,6 +66,7 @@ final class PocketBaseController implements Listener, Runnable {
|
|||||||
showPortal(pocketBases.returnPortal(state.ownerId()));
|
showPortal(pocketBases.returnPortal(state.ownerId()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
scanMobPortals();
|
||||||
long now = System.nanoTime();
|
long now = System.nanoTime();
|
||||||
cooldownUntil.entrySet().removeIf(entry -> entry.getValue() <= now);
|
cooldownUntil.entrySet().removeIf(entry -> entry.getValue() <= now);
|
||||||
}
|
}
|
||||||
@@ -235,23 +239,61 @@ final class PocketBaseController implements Listener, Runnable {
|
|||||||
deactivate(ownerId);
|
deactivate(ownerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void leavePocket(Player player, UUID ownerId) {
|
void scanMobPortals() {
|
||||||
|
for (PocketBaseState state : pocketBases.knownStates().values()) {
|
||||||
|
if (state.entrance().isPresent()) {
|
||||||
|
PocketPortalLocation entrance = state.entrance().orElseThrow();
|
||||||
|
if (isIntact(entrance)) {
|
||||||
|
transportMobs(
|
||||||
|
entrance,
|
||||||
|
mob -> teleport(mob, pocketBases.pocketArrival(state.ownerId()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state.level() > 0 && pocketBases.returnPortalIsIntact(state.ownerId())) {
|
||||||
|
transportMobs(
|
||||||
|
pocketBases.returnPortal(state.ownerId()),
|
||||||
|
mob -> leavePocket(mob, state.ownerId())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void transportMobs(
|
||||||
|
PocketPortalLocation portal,
|
||||||
|
Consumer<Mob> transport
|
||||||
|
) {
|
||||||
|
World world = server.getWorld(portal.worldId());
|
||||||
|
if (world == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (Entity entity : world.getNearbyEntities(
|
||||||
|
portalBounds(portal),
|
||||||
|
candidate -> candidate instanceof Mob
|
||||||
|
)) {
|
||||||
|
if (entity instanceof Mob mob && !onCooldown(mob)) {
|
||||||
|
transport.accept(mob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void leavePocket(Entity entity, UUID ownerId) {
|
||||||
PocketBaseState state = pocketBases.state(ownerId);
|
PocketBaseState state = pocketBases.state(ownerId);
|
||||||
if (state.entrance().isPresent() && isIntact(state.entrance().orElseThrow())) {
|
if (state.entrance().isPresent() && isIntact(state.entrance().orElseThrow())) {
|
||||||
teleport(player, outsidePortal(state.entrance().orElseThrow()));
|
teleport(entity, outsidePortal(state.entrance().orElseThrow()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
World fallbackWorld = server.getWorlds().get(0);
|
World fallbackWorld = server.getWorlds().get(0);
|
||||||
teleport(player, fallbackWorld.getSpawnLocation());
|
teleport(entity, fallbackWorld.getSpawnLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void teleport(Player player, Location destination) {
|
private void teleport(Entity entity, Location destination) {
|
||||||
cooldownUntil.put(player.getUniqueId(), System.nanoTime() + PORTAL_COOLDOWN_NANOS);
|
cooldownUntil.put(entity.getUniqueId(), System.nanoTime() + PORTAL_COOLDOWN_NANOS);
|
||||||
player.teleport(destination, PlayerTeleportEvent.TeleportCause.PLUGIN);
|
entity.teleport(destination, PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean onCooldown(Player player) {
|
private boolean onCooldown(Entity entity) {
|
||||||
return cooldownUntil.getOrDefault(player.getUniqueId(), 0L) > System.nanoTime();
|
return cooldownUntil.getOrDefault(entity.getUniqueId(), 0L) > System.nanoTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateEntrances() {
|
private void validateEntrances() {
|
||||||
@@ -320,6 +362,26 @@ final class PocketBaseController implements Listener, Runnable {
|
|||||||
: new Location(world, portal.x() + 1.5, portal.y() + 1.0, portal.z() + 2.0);
|
: new Location(world, portal.x() + 1.5, portal.y() + 1.0, portal.z() + 2.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static BoundingBox portalBounds(PocketPortalLocation portal) {
|
||||||
|
return portal.axis() == PocketPortalAxis.X
|
||||||
|
? new BoundingBox(
|
||||||
|
portal.x() + 1.0,
|
||||||
|
portal.y() + 1.0,
|
||||||
|
portal.z(),
|
||||||
|
portal.x() + 3.0,
|
||||||
|
portal.y() + 4.0,
|
||||||
|
portal.z() + 1.0
|
||||||
|
)
|
||||||
|
: new BoundingBox(
|
||||||
|
portal.x(),
|
||||||
|
portal.y() + 1.0,
|
||||||
|
portal.z() + 1.0,
|
||||||
|
portal.x() + 1.0,
|
||||||
|
portal.y() + 4.0,
|
||||||
|
portal.z() + 3.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isAir(Material material) {
|
private static boolean isAir(Material material) {
|
||||||
return material == Material.AIR || material == Material.CAVE_AIR
|
return material == Material.AIR || material == Material.CAVE_AIR
|
||||||
|| material == Material.VOID_AIR;
|
|| material == Material.VOID_AIR;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package games.dmg.spigotbase;
|
package games.dmg.spigotbase;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
@@ -8,20 +9,26 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.Mob;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.block.Action;
|
import org.bukkit.event.block.Action;
|
||||||
import org.bukkit.event.block.BlockPlaceEvent;
|
import org.bukkit.event.block.BlockPlaceEvent;
|
||||||
import org.bukkit.event.player.PlayerInteractEvent;
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
|
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.util.BoundingBox;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
final class PocketBaseControllerTest {
|
final class PocketBaseControllerTest {
|
||||||
@@ -230,6 +237,151 @@ final class PocketBaseControllerTest {
|
|||||||
verify(event).setCancelled(true);
|
verify(event).setCancelled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void activeEntranceTransportsMobsOnceButNotOtherEntities() {
|
||||||
|
UUID ownerId = UUID.randomUUID();
|
||||||
|
UUID worldId = UUID.randomUUID();
|
||||||
|
PocketPortalLocation portal = new PocketPortalLocation(
|
||||||
|
worldId, "world", -2, 64, 0, PocketPortalAxis.X
|
||||||
|
);
|
||||||
|
PocketBaseState state = new PocketBaseState(ownerId, 1, Optional.of(portal));
|
||||||
|
Server server = mock(Server.class);
|
||||||
|
World world = intactPortalWorld(worldId, portal);
|
||||||
|
Mob mob = mock(Mob.class);
|
||||||
|
Entity item = mock(Entity.class);
|
||||||
|
Location arrival = new Location(mock(World.class), 0.5, 65.0, 5.5);
|
||||||
|
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
|
||||||
|
|
||||||
|
when(mob.getUniqueId()).thenReturn(UUID.randomUUID());
|
||||||
|
when(item.getUniqueId()).thenReturn(UUID.randomUUID());
|
||||||
|
when(world.getNearbyEntities(any(BoundingBox.class), any()))
|
||||||
|
.thenReturn(List.of(mob, item));
|
||||||
|
when(server.getWorld(worldId)).thenReturn(world);
|
||||||
|
when(pocketBases.knownStates()).thenReturn(Map.of(ownerId, state));
|
||||||
|
when(pocketBases.pocketArrival(ownerId)).thenReturn(arrival);
|
||||||
|
PocketBaseController controller = controller(server, pocketBases);
|
||||||
|
|
||||||
|
controller.scanMobPortals();
|
||||||
|
controller.scanMobPortals();
|
||||||
|
|
||||||
|
verify(mob).teleport(arrival, PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||||
|
verify(item, never()).teleport(
|
||||||
|
any(Location.class),
|
||||||
|
any(PlayerTeleportEvent.TeleportCause.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void activeReturnPortalTransportsMobOutsideIntactEntrance() {
|
||||||
|
UUID ownerId = UUID.randomUUID();
|
||||||
|
UUID normalWorldId = UUID.randomUUID();
|
||||||
|
UUID pocketWorldId = UUID.randomUUID();
|
||||||
|
PocketPortalLocation entrance = new PocketPortalLocation(
|
||||||
|
normalWorldId, "world", -2, 64, 0, PocketPortalAxis.X
|
||||||
|
);
|
||||||
|
PocketPortalLocation returnPortal = new PocketPortalLocation(
|
||||||
|
pocketWorldId, "pocket", 8, 65, 8, PocketPortalAxis.Z
|
||||||
|
);
|
||||||
|
PocketBaseState state = new PocketBaseState(ownerId, 1, Optional.of(entrance));
|
||||||
|
Server server = mock(Server.class);
|
||||||
|
World normalWorld = intactPortalWorld(normalWorldId, entrance);
|
||||||
|
World pocketWorld = intactPortalWorld(pocketWorldId, returnPortal);
|
||||||
|
Mob mob = mock(Mob.class);
|
||||||
|
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
|
||||||
|
|
||||||
|
when(mob.getUniqueId()).thenReturn(UUID.randomUUID());
|
||||||
|
when(normalWorld.getNearbyEntities(any(BoundingBox.class), any()))
|
||||||
|
.thenReturn(List.of());
|
||||||
|
when(pocketWorld.getNearbyEntities(any(BoundingBox.class), any()))
|
||||||
|
.thenReturn(List.of(mob));
|
||||||
|
when(server.getWorld(normalWorldId)).thenReturn(normalWorld);
|
||||||
|
when(server.getWorld(pocketWorldId)).thenReturn(pocketWorld);
|
||||||
|
when(pocketBases.knownStates()).thenReturn(Map.of(ownerId, state));
|
||||||
|
when(pocketBases.state(ownerId)).thenReturn(state);
|
||||||
|
when(pocketBases.returnPortalIsIntact(ownerId)).thenReturn(true);
|
||||||
|
when(pocketBases.returnPortal(ownerId)).thenReturn(returnPortal);
|
||||||
|
PocketBaseController controller = controller(server, pocketBases);
|
||||||
|
|
||||||
|
controller.scanMobPortals();
|
||||||
|
|
||||||
|
verify(mob).teleport(
|
||||||
|
new Location(normalWorld, 0.0, 65.0, 1.5),
|
||||||
|
PlayerTeleportEvent.TeleportCause.PLUGIN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mobReturnFallsBackToServerSpawnWithoutValidEntrance() {
|
||||||
|
UUID ownerId = UUID.randomUUID();
|
||||||
|
UUID pocketWorldId = UUID.randomUUID();
|
||||||
|
PocketPortalLocation returnPortal = new PocketPortalLocation(
|
||||||
|
pocketWorldId, "pocket", 8, 65, 8, PocketPortalAxis.Z
|
||||||
|
);
|
||||||
|
PocketBaseState state = new PocketBaseState(ownerId, 1, Optional.empty());
|
||||||
|
Server server = mock(Server.class);
|
||||||
|
World pocketWorld = intactPortalWorld(pocketWorldId, returnPortal);
|
||||||
|
World fallbackWorld = mock(World.class);
|
||||||
|
Location spawn = new Location(fallbackWorld, 10.5, 70.0, 10.5);
|
||||||
|
Mob mob = mock(Mob.class);
|
||||||
|
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
|
||||||
|
|
||||||
|
when(mob.getUniqueId()).thenReturn(UUID.randomUUID());
|
||||||
|
when(pocketWorld.getNearbyEntities(any(BoundingBox.class), any()))
|
||||||
|
.thenReturn(List.of(mob));
|
||||||
|
when(server.getWorld(pocketWorldId)).thenReturn(pocketWorld);
|
||||||
|
when(server.getWorlds()).thenReturn(List.of(fallbackWorld));
|
||||||
|
when(fallbackWorld.getSpawnLocation()).thenReturn(spawn);
|
||||||
|
when(pocketBases.knownStates()).thenReturn(Map.of(ownerId, state));
|
||||||
|
when(pocketBases.state(ownerId)).thenReturn(state);
|
||||||
|
when(pocketBases.returnPortalIsIntact(ownerId)).thenReturn(true);
|
||||||
|
when(pocketBases.returnPortal(ownerId)).thenReturn(returnPortal);
|
||||||
|
PocketBaseController controller = controller(server, pocketBases);
|
||||||
|
|
||||||
|
controller.scanMobPortals();
|
||||||
|
|
||||||
|
verify(mob).teleport(
|
||||||
|
spawn,
|
||||||
|
PlayerTeleportEvent.TeleportCause.PLUGIN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PocketBaseController controller(
|
||||||
|
Server server,
|
||||||
|
PocketBaseManager pocketBases
|
||||||
|
) {
|
||||||
|
PluginSettings settings = PluginSettings.from(Map.of());
|
||||||
|
return new PocketBaseController(
|
||||||
|
server,
|
||||||
|
mock(BaseStateManager.class),
|
||||||
|
new BaseBoundsService(settings),
|
||||||
|
pocketBases,
|
||||||
|
new PluginSettingsProvider(settings),
|
||||||
|
Logger.getAnonymousLogger()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static World intactPortalWorld(
|
||||||
|
UUID worldId,
|
||||||
|
PocketPortalLocation portal
|
||||||
|
) {
|
||||||
|
World world = mock(World.class);
|
||||||
|
Set<BlockPosition> frame = new HashSet<>(PocketPortalGeometry.frameBlocks(portal));
|
||||||
|
when(world.getUID()).thenReturn(worldId);
|
||||||
|
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
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return world;
|
||||||
|
}
|
||||||
|
|
||||||
private static ItemStack item(Material material) {
|
private static ItemStack item(Material material) {
|
||||||
ItemStack item = mock(ItemStack.class);
|
ItemStack item = mock(ItemStack.class);
|
||||||
when(item.getType()).thenReturn(material);
|
when(item.getType()).thenReturn(material);
|
||||||
|
|||||||
Reference in New Issue
Block a user