feat(pocket-base): unlock flight by completing raids

This commit is contained in:
dmg
2026-08-29 00:04:46 -04:00
parent 02c6698268
commit f4511f9286
19 changed files with 474 additions and 19 deletions
@@ -12,11 +12,14 @@ import static org.mockito.Mockito.when;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.logging.Logger;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.raid.RaidFinishEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.junit.jupiter.api.BeforeEach;
@@ -47,6 +50,7 @@ final class BaseFlightControllerTest {
PlayerInventory inventory = mock(PlayerInventory.class);
doReturn(List.of(player)).when(server).getOnlinePlayers();
when(server.getPlayer(playerId)).thenReturn(player);
when(player.getUniqueId()).thenReturn(playerId);
when(player.getName()).thenReturn("Alex");
when(player.getGameMode()).thenReturn(org.bukkit.GameMode.SURVIVAL);
@@ -62,6 +66,219 @@ final class BaseFlightControllerTest {
when(player.getAllowFlight()).thenReturn(true);
}
@Test
void ownerWinningRaidInOwnPocketBaseUnlocksFlight() throws Exception {
UUID pocketWorldId = UUID.randomUUID();
World pocketWorld = mock(World.class);
Player winner = mock(Player.class);
RaidFinishEvent event = mock(RaidFinishEvent.class);
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
PocketBaseState locked = new PocketBaseState(playerId, 1, Optional.empty());
PocketBaseState unlocked = locked.withFlightUnlocked();
BaseFlightController pocketController = new BaseFlightController(
server,
stateManager,
new SecondaryProgressionService(PluginSettings.from(Map.of())),
new BaseBoundsService(PluginSettings.from(Map.of())),
new PluginSettingsProvider(PluginSettings.from(Map.of())),
pocketBases,
Logger.getAnonymousLogger()
);
when(event.getWorld()).thenReturn(pocketWorld);
when(event.getWinners()).thenReturn(List.of(winner));
when(pocketWorld.getUID()).thenReturn(pocketWorldId);
when(winner.getUniqueId()).thenReturn(playerId);
when(pocketBases.ownerForPocketWorld(pocketWorldId)).thenReturn(Optional.of(playerId));
when(pocketBases.state(playerId)).thenReturn(locked);
when(pocketBases.unlockFlight(playerId)).thenReturn(unlocked);
pocketController.onRaidFinish(event);
verify(pocketBases).unlockFlight(playerId);
verify(winner).sendTitle(anyString(), anyString(), anyInt(), anyInt(), anyInt());
}
@Test
void raidDoesNotUnlockFlightWhenPocketOwnerIsNotAWinner() throws Exception {
UUID pocketWorldId = UUID.randomUUID();
UUID ownerId = UUID.randomUUID();
World pocketWorld = mock(World.class);
Player visitor = mock(Player.class);
RaidFinishEvent event = mock(RaidFinishEvent.class);
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
BaseFlightController pocketController = new BaseFlightController(
server,
stateManager,
new SecondaryProgressionService(PluginSettings.from(Map.of())),
new BaseBoundsService(PluginSettings.from(Map.of())),
new PluginSettingsProvider(PluginSettings.from(Map.of())),
pocketBases,
Logger.getAnonymousLogger()
);
when(event.getWorld()).thenReturn(pocketWorld);
when(event.getWinners()).thenReturn(List.of(visitor));
when(pocketWorld.getUID()).thenReturn(pocketWorldId);
when(visitor.getUniqueId()).thenReturn(UUID.randomUUID());
when(pocketBases.ownerForPocketWorld(pocketWorldId)).thenReturn(Optional.of(ownerId));
pocketController.onRaidFinish(event);
verify(pocketBases, never()).unlockFlight(ownerId);
}
@Test
void unlockedOwnerReceivesFlightInsidePocketBaseBuffer() {
UUID pocketWorldId = UUID.randomUUID();
World pocketWorld = mock(World.class);
Location location = mock(Location.class);
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
PocketBaseState pocket = new PocketBaseState(playerId, 1, Optional.empty())
.withFlightUnlocked();
PlayerState noNormalFlight = PlayerState.newPlayer(playerId, "Alex");
BaseFlightController pocketController = new BaseFlightController(
server,
stateManager,
new SecondaryProgressionService(PluginSettings.from(Map.of())),
new BaseBoundsService(PluginSettings.from(Map.of())),
new PluginSettingsProvider(PluginSettings.from(Map.of())),
pocketBases,
Logger.getAnonymousLogger()
);
when(stateManager.player(playerId, "Alex")).thenReturn(noNormalFlight);
when(player.getWorld()).thenReturn(pocketWorld);
when(player.getLocation()).thenReturn(location);
when(player.getAllowFlight()).thenReturn(false);
when(pocketWorld.getUID()).thenReturn(pocketWorldId);
when(location.getX()).thenReturn(40.5);
when(location.getZ()).thenReturn(0.5);
when(location.getBlockX()).thenReturn(40);
when(location.getBlockZ()).thenReturn(0);
when(pocketBases.ownerForPocketWorld(pocketWorldId)).thenReturn(Optional.of(playerId));
when(pocketBases.state(playerId)).thenReturn(pocket);
when(pocketBases.policy()).thenReturn(
new PocketBasePolicy(PluginSettings.from(Map.of()))
);
pocketController.run();
verify(player).setAllowFlight(true);
}
@Test
void flyingInPocketBaseBufferShowsLeavingWarning() {
UUID pocketWorldId = UUID.randomUUID();
World pocketWorld = mock(World.class);
Location location = mock(Location.class);
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
PocketBaseState pocket = new PocketBaseState(playerId, 1, Optional.empty())
.withFlightUnlocked();
BaseFlightController pocketController = new BaseFlightController(
server,
stateManager,
new SecondaryProgressionService(PluginSettings.from(Map.of())),
new BaseBoundsService(PluginSettings.from(Map.of())),
new PluginSettingsProvider(PluginSettings.from(Map.of())),
pocketBases,
Logger.getAnonymousLogger()
);
when(stateManager.player(playerId, "Alex")).thenReturn(
PlayerState.newPlayer(playerId, "Alex")
);
when(player.getWorld()).thenReturn(pocketWorld);
when(player.getLocation()).thenReturn(location);
when(player.isFlying()).thenReturn(true);
when(pocketWorld.getUID()).thenReturn(pocketWorldId);
when(location.getBlockX()).thenReturn(40);
when(location.getBlockZ()).thenReturn(0);
when(pocketBases.ownerForPocketWorld(pocketWorldId)).thenReturn(Optional.of(playerId));
when(pocketBases.state(playerId)).thenReturn(pocket);
when(pocketBases.policy()).thenReturn(
new PocketBasePolicy(PluginSettings.from(Map.of()))
);
pocketController.run();
verify(player).sendTitle(anyString(), anyString(), anyInt(), anyInt(), anyInt());
}
@Test
void pocketBaseFlightEndsBeyondSixteenBlockBuffer() {
UUID pocketWorldId = UUID.randomUUID();
World pocketWorld = mock(World.class);
Location location = mock(Location.class);
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
PocketBaseState pocket = new PocketBaseState(playerId, 1, Optional.empty())
.withFlightUnlocked();
BaseFlightController pocketController = new BaseFlightController(
server,
stateManager,
new SecondaryProgressionService(PluginSettings.from(Map.of())),
new BaseBoundsService(PluginSettings.from(Map.of())),
new PluginSettingsProvider(PluginSettings.from(Map.of())),
pocketBases,
Logger.getAnonymousLogger()
);
when(stateManager.player(playerId, "Alex")).thenReturn(
PlayerState.newPlayer(playerId, "Alex")
);
when(player.getWorld()).thenReturn(pocketWorld);
when(player.getLocation()).thenReturn(location);
when(player.getAllowFlight()).thenReturn(false);
when(pocketWorld.getUID()).thenReturn(pocketWorldId);
when(location.getBlockX()).thenReturn(40, 48);
when(location.getBlockZ()).thenReturn(0);
when(pocketBases.ownerForPocketWorld(pocketWorldId)).thenReturn(Optional.of(playerId));
when(pocketBases.state(playerId)).thenReturn(pocket);
when(pocketBases.policy()).thenReturn(
new PocketBasePolicy(PluginSettings.from(Map.of()))
);
pocketController.run();
pocketController.run();
verify(player).setFlying(false);
verify(player).setAllowFlight(false);
}
@Test
void visitorDoesNotReceiveUnlockedOwnersPocketBaseFlight() {
UUID ownerId = UUID.randomUUID();
UUID pocketWorldId = UUID.randomUUID();
World pocketWorld = mock(World.class);
Location location = mock(Location.class);
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
PocketBaseState pocket = new PocketBaseState(ownerId, 1, Optional.empty())
.withFlightUnlocked();
BaseFlightController pocketController = new BaseFlightController(
server,
stateManager,
new SecondaryProgressionService(PluginSettings.from(Map.of())),
new BaseBoundsService(PluginSettings.from(Map.of())),
new PluginSettingsProvider(PluginSettings.from(Map.of())),
pocketBases,
Logger.getAnonymousLogger()
);
when(stateManager.player(playerId, "Alex")).thenReturn(
PlayerState.newPlayer(playerId, "Alex")
);
when(player.getWorld()).thenReturn(pocketWorld);
when(player.getLocation()).thenReturn(location);
when(player.getAllowFlight()).thenReturn(false);
when(pocketWorld.getUID()).thenReturn(pocketWorldId);
when(pocketBases.ownerForPocketWorld(pocketWorldId)).thenReturn(Optional.of(ownerId));
when(pocketBases.state(ownerId)).thenReturn(pocket);
pocketController.run();
verify(player, never()).setAllowFlight(true);
}
@Test
void walkingInWarningBufferDoesNotShowLeavingBaseWarning() {
when(player.isFlying()).thenReturn(false);
@@ -489,6 +489,7 @@ final class BaseSettingsCommandTest {
&& message.contains("type=void/the_void")
&& message.contains("hostile spawning=enabled")
&& message.contains("passive spawning=disabled")
&& message.contains("flight=complete a raid to unlock")
));
}
@@ -59,6 +59,13 @@ final class BaseSettingsDialogFactoryTest {
specification
);
assertTrue(root.content().message().contains("Base 4/4"));
BaseSettingsDialogFactory.ListSpec pocketSettings = assertInstanceOf(
BaseSettingsDialogFactory.ListSpec.class,
root.dialogs().get(3)
);
assertTrue(pocketSettings.content().message().contains(
"Flight: Complete a raid to unlock"
));
List<String> commands = commands(root);
assertTrue(commands.contains("/basesettings status"));
assertTrue(commands.contains("/basesettings visitors blocked"));
@@ -2,6 +2,7 @@ package games.dmg.spigotbase;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -58,6 +59,32 @@ final class PocketBaseManagerTest {
assertEquals(generated, manager.returnPortal(ownerId));
}
@Test
void persistsPocketBaseFlightUnlock() throws Exception {
UUID ownerId = UUID.randomUUID();
PocketBaseState current = new PocketBaseState(ownerId, 1, Optional.empty());
PocketBaseStateManager states = mock(PocketBaseStateManager.class);
when(states.state(ownerId)).thenReturn(current);
when(states.updateAndSave(org.mockito.ArgumentMatchers.eq(ownerId),
org.mockito.ArgumentMatchers.any())).thenAnswer(invocation -> {
@SuppressWarnings("unchecked")
UnaryOperator<PocketBaseState> operation = invocation.getArgument(1);
return operation.apply(current);
});
PocketBaseManager manager = new PocketBaseManager(
states,
mock(PocketBaseWorldService.class),
new PluginSettingsProvider(PluginSettings.from(Map.of()))
);
PocketBaseState updated = manager.unlockFlight(ownerId);
assertTrue(updated.flightUnlocked());
verify(states).updateAndSave(
org.mockito.ArgumentMatchers.eq(ownerId), org.mockito.ArgumentMatchers.any()
);
}
@Test
void persistsAndAppliesHostileMobSpawningWithoutChangingPassive() throws Exception {
UUID ownerId = UUID.randomUUID();
@@ -31,7 +31,8 @@ final class YamlPocketBaseRepositoryTest {
)),
true,
false,
PocketBaseBiome.CRIMSON_FOREST
PocketBaseBiome.CRIMSON_FOREST,
true
);
YamlPocketBaseRepository repository = new YamlPocketBaseRepository(
temporaryDirectory.resolve("pocket-bases.yml")
@@ -55,6 +56,7 @@ final class YamlPocketBaseRepositoryTest {
assertFalse(loaded.passiveMobSpawningEnabled());
assertTrue(loaded.returnPortal().isEmpty());
assertEquals(PocketBaseBiome.THE_VOID, loaded.biome());
assertFalse(loaded.flightUnlocked());
}
@Test