feat(pocket-base): share flight through gold portals
Release / release (push) Successful in 2m50s
CI / build (push) Successful in 1m15s

This commit is contained in:
dmg
2026-08-29 00:20:40 -04:00
parent f4511f9286
commit e0b1a25c5e
21 changed files with 744 additions and 80 deletions
@@ -1,7 +1,9 @@
package games.dmg.spigotbase;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
@@ -14,6 +16,8 @@ import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.raid.RaidFinishEvent;
import org.bukkit.inventory.ItemStack;
@@ -28,6 +32,7 @@ final class BaseFlightController implements Runnable, Listener {
private final Logger logger;
private final Set<UUID> grantedFlight = new HashSet<>();
private final Set<UUID> warned = new HashSet<>();
private final Map<UUID, UUID> portalFlight = new HashMap<>();
BaseFlightController(
Server server,
@@ -91,6 +96,20 @@ final class BaseFlightController implements Runnable, Listener {
);
}
@EventHandler
public void onDeath(PlayerDeathEvent event) {
clearPortalFlight(event.getEntity());
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
clearPortalFlight(event.getPlayer());
}
void grantPocketPortalFlight(Player player, UUID ownerId) {
portalFlight.put(player.getUniqueId(), ownerId);
}
@Override
public void run() {
for (Player player : server.getOnlinePlayers()) {
@@ -102,6 +121,7 @@ final class BaseFlightController implements Runnable, Listener {
}
grantedFlight.removeIf(id -> server.getPlayer(id) == null);
warned.removeIf(id -> server.getPlayer(id) == null);
portalFlight.keySet().removeIf(id -> server.getPlayer(id) == null);
}
void removeGrantedFlight(Player player) {
@@ -124,6 +144,7 @@ final class BaseFlightController implements Runnable, Listener {
}
grantedFlight.clear();
warned.clear();
portalFlight.clear();
}
private PlayerState observeElytra(Player player, PlayerState state) {
@@ -148,6 +169,7 @@ final class BaseFlightController implements Runnable, Listener {
private void applyFlight(Player player, PlayerState state) {
if (player.getGameMode() != GameMode.SURVIVAL) {
portalFlight.remove(player.getUniqueId());
removeGrantedFlight(player);
return;
}
@@ -160,6 +182,7 @@ final class BaseFlightController implements Runnable, Listener {
return;
}
}
portalFlight.remove(player.getUniqueId());
if (!state.flightEnabled() || state.flightLevel() < 1 || state.base().isEmpty()) {
removeGrantedFlight(player);
return;
@@ -199,8 +222,10 @@ final class BaseFlightController implements Runnable, Listener {
private void applyPocketFlight(Player player, UUID ownerId) {
PocketBaseState pocket = pocketBases.state(ownerId);
if (!ownerId.equals(player.getUniqueId()) || !pocket.flightUnlocked()
|| pocket.level() < 1) {
boolean ownerFlight = ownerId.equals(player.getUniqueId())
&& pocket.flightUnlocked() && pocket.flightEnabled();
boolean temporaryFlight = ownerId.equals(portalFlight.get(player.getUniqueId()));
if ((!ownerFlight && !temporaryFlight) || pocket.level() < 1) {
removeGrantedFlight(player);
return;
}
@@ -234,6 +259,11 @@ final class BaseFlightController implements Runnable, Listener {
}
}
private void clearPortalFlight(Player player) {
portalFlight.remove(player.getUniqueId());
removeGrantedFlight(player);
}
private boolean withinVerticalRange(Player player, PlayerState state, BaseLocation base) {
int y = player.getLocation().getBlockY();
if (state.flightLevel() == 3) {
@@ -93,6 +93,10 @@ final class BaseSettingsCommand implements CommandExecutor, TabCompleter {
&& arguments[1].equalsIgnoreCase("upgrade")) {
return purchasePocketUpgrade(player, state);
}
if (arguments.length == 3 && arguments[0].equalsIgnoreCase("pocket")
&& arguments[1].equalsIgnoreCase("flight")) {
return updatePocketFlight(player, arguments[2]);
}
if (arguments.length == 4 && arguments[0].equalsIgnoreCase("pocket")
&& arguments[1].equalsIgnoreCase("mobs")) {
return updatePocketMobSpawning(player, arguments[2], arguments[3]);
@@ -264,6 +268,35 @@ final class BaseSettingsCommand implements CommandExecutor, TabCompleter {
return true;
}
private boolean updatePocketFlight(Player player, String mode) {
if (pocketBases == null) {
player.sendMessage(ChatColor.RED + "Pocket Bases are currently unavailable.");
return true;
}
PocketBaseState current = pocketBases.state(player.getUniqueId());
if (!current.flightUnlocked()) {
player.sendMessage(ChatColor.RED
+ "Win a raid in your Pocket Base before enabling automatic flight.");
return true;
}
Boolean enabled = enabledMode(mode);
if (enabled == null) {
sendUsage(player);
return true;
}
try {
current = pocketBases.setFlightEnabled(player.getUniqueId(), enabled);
} catch (IOException | RuntimeException exception) {
player.sendMessage(ChatColor.RED
+ "The Pocket Base flight setting could not be changed.");
return true;
}
player.sendMessage(ChatColor.YELLOW + "Pocket Base automatic flight is now "
+ (current.flightEnabled() ? ChatColor.GREEN + "enabled" : ChatColor.RED + "disabled")
+ ChatColor.YELLOW + ".");
return true;
}
private boolean updatePocketMobSpawning(
Player player,
String category,
@@ -567,7 +600,9 @@ final class BaseSettingsCommand implements CommandExecutor, TabCompleter {
+ "; passive spawning="
+ (pocket.passiveMobSpawningEnabled() ? "enabled" : "disabled")
+ "; flight="
+ (pocket.flightUnlocked() ? "unlocked" : "complete a raid to unlock"));
+ (pocket.flightUnlocked()
? "automatic " + (pocket.flightEnabled() ? "enabled" : "disabled")
: "complete a raid to unlock"));
}
private void showCooldownPath(Player player, PlayerState state) {
@@ -599,7 +634,7 @@ final class BaseSettingsCommand implements CommandExecutor, TabCompleter {
List<String> modes = arguments[0].equalsIgnoreCase("visitors")
? VISITOR_MODES
: switch (arguments[0].toLowerCase(Locale.ROOT)) {
case "pocket" -> List.of("upgrade", "mobs", "type");
case "pocket" -> List.of("upgrade", "flight", "mobs", "type");
case "navigation", "flight", "border", "spawnable", "bossbar" -> ENABLE_MODES;
default -> List.of();
};
@@ -608,6 +643,11 @@ final class BaseSettingsCommand implements CommandExecutor, TabCompleter {
}
if (arguments.length == 3 && arguments[0].equalsIgnoreCase("pocket")) {
String prefix = arguments[2].toLowerCase(Locale.ROOT);
if (arguments[1].equalsIgnoreCase("flight")) {
return ENABLE_MODES.stream()
.filter(mode -> mode.startsWith(prefix))
.toList();
}
if (arguments[1].equalsIgnoreCase("mobs")) {
return List.of("hostile", "passive").stream()
.filter(category -> category.startsWith(prefix))
@@ -701,7 +741,7 @@ final class BaseSettingsCommand implements CommandExecutor, TabCompleter {
private static void sendUsage(Player player) {
player.sendMessage(ChatColor.RED + "Usage: /basesettings "
+ "[ui|status|upgrade|pocket upgrade|pocket mobs "
+ "[ui|status|upgrade|pocket upgrade|pocket flight <enable|disable>|pocket mobs "
+ "<hostile|passive> <enable|disable>"
+ "|pocket type <void|nether|overworld> <subtype>"
+ "|visitors <allowed|blocked>|navigation <enable|disable>"
@@ -147,6 +147,7 @@ final class BaseSettingsDialogFactory {
private DialogSpec pocketSettings(PlayerState owner, PocketBaseState pocket) {
List<DialogSpec> dialogs = List.of(
pocketUpgrade(owner, pocket),
pocketFlightSettings(pocket),
pocketMobSettings(pocket),
pocketBiomeSettings(pocket)
);
@@ -161,7 +162,7 @@ final class BaseSettingsDialogFactory {
+ enabled(pocket.hostileMobSpawningEnabled()) + " • Passive mobs: "
+ enabled(pocket.passiveMobSpawningEnabled()) + "\nFlight: "
+ (pocket.flightUnlocked()
? "Unlocked"
? (pocket.flightEnabled() ? "Enabled" : "Disabled")
: "Complete a raid to unlock")
),
dialogs,
@@ -191,6 +192,32 @@ final class BaseSettingsDialogFactory {
);
}
private DialogSpec pocketFlightSettings(PocketBaseState pocket) {
if (!pocket.flightUnlocked()) {
return notice(
"Pocket Flight — Locked",
"Win a raid in your Pocket Base to unlock automatic flight."
);
}
return new MultiSpec(
content(
"Pocket Flight",
"Pocket Flight",
"Control automatic owner flight. Flight granted by the gold portal "
+ "is unaffected."
),
List.of(toggle(
"Automatic Flight",
true,
pocket.flightEnabled(),
"Enabled",
"Disabled",
"basesettings pocket flight " + mode(!pocket.flightEnabled())
)),
1
);
}
private DialogSpec pocketMobSettings(PocketBaseState pocket) {
if (pocket.level() < 1) {
return notice(
@@ -328,14 +355,19 @@ final class BaseSettingsDialogFactory {
}
private static Dialog render(DialogSpec specification) {
return render(specification, true);
}
private static Dialog render(DialogSpec specification, boolean root) {
DialogType type;
ActionButton back = root ? null : backButton();
if (specification instanceof ListSpec list) {
List<Dialog> dialogs = list.dialogs().stream()
.map(BaseSettingsDialogFactory::render)
.map(child -> render(child, false))
.toList();
type = DialogType.dialogList(
RegistrySet.valueSet(RegistryKey.DIALOG, dialogs),
null,
back,
list.columns(),
list.buttonWidth()
);
@@ -343,13 +375,15 @@ final class BaseSettingsDialogFactory {
List<ActionButton> actions = multi.actions().stream()
.map(BaseSettingsDialogFactory::render)
.toList();
type = DialogType.multiAction(actions, null, multi.columns());
type = DialogType.multiAction(actions, back, multi.columns());
} else if (specification instanceof ConfirmationSpec confirmation) {
ActionButton cancel = ActionButton.builder(Component.text("Cancel")).build();
type = DialogType.confirmation(render(confirmation.confirm()), cancel);
type = DialogType.confirmation(
render(confirmation.confirm()),
root ? ActionButton.builder(Component.text("Cancel")).build() : back
);
} else if (specification instanceof NoticeSpec notice) {
type = notice.action() == null
? DialogType.notice()
? (root ? DialogType.notice() : DialogType.notice(back))
: DialogType.notice(render(notice.action()));
} else {
throw new IllegalArgumentException("Unsupported dialog specification");
@@ -367,6 +401,14 @@ final class BaseSettingsDialogFactory {
return Dialog.create(factory -> factory.empty().base(base).type(type));
}
private static ActionButton backButton() {
return render(backButtonSpec());
}
static ButtonSpec backButtonSpec() {
return commandButton("Back", "basesettings ui");
}
private static ActionButton render(ButtonSpec button) {
ActionButton.Builder builder = ActionButton.builder(Component.text(button.label()))
.width(button.width())
@@ -39,6 +39,7 @@ final class PocketBaseController implements Listener, Runnable {
private final PocketBaseManager pocketBases;
private final PluginSettingsProvider settings;
private final Logger logger;
private final BaseFlightController flightController;
private final Map<UUID, Long> cooldownUntil = new HashMap<>();
PocketBaseController(
@@ -48,6 +49,18 @@ final class PocketBaseController implements Listener, Runnable {
PocketBaseManager pocketBases,
PluginSettingsProvider settings,
Logger logger
) {
this(server, baseStates, baseBounds, pocketBases, settings, logger, null);
}
PocketBaseController(
Server server,
BaseStateManager baseStates,
BaseBoundsService baseBounds,
PocketBaseManager pocketBases,
PluginSettingsProvider settings,
Logger logger,
BaseFlightController flightController
) {
this.server = server;
this.baseStates = baseStates;
@@ -55,6 +68,7 @@ final class PocketBaseController implements Listener, Runnable {
this.pocketBases = pocketBases;
this.settings = settings;
this.logger = logger;
this.flightController = flightController;
}
@Override
@@ -65,6 +79,8 @@ final class PocketBaseController implements Listener, Runnable {
if (state.level() > 0 && pocketBases.returnPortalIsIntact(state.ownerId())) {
showPortal(pocketBases.returnPortal(state.ownerId()));
}
state.flightPortal().filter(portal -> isIntact(portal, Material.GOLD_BLOCK))
.ifPresent(portal -> showPortal(portal, Material.GOLD_BLOCK));
}
scanMobPortals();
long now = System.nanoTime();
@@ -88,41 +104,57 @@ final class PocketBaseController implements Listener, Runnable {
Material frameMaterial = Material.valueOf(
settings.current().pocketBasePortalFrameMaterial()
);
Optional<PocketPortalLocation> portal = PocketPortalGeometry.findFrame(
position(clicked),
world.getUID(),
world.getName(),
candidate -> block(world, candidate).getType() == frameMaterial,
candidate -> isAir(block(world, candidate).getType())
);
if (portal.isEmpty()) {
return;
}
Optional<UUID> pocketOwner = pocketBases.ownerForPocketWorld(world.getUID());
if (pocketOwner.isPresent()) {
if (!pocketOwner.orElseThrow().equals(player.getUniqueId())) {
return;
}
Optional<PocketPortalLocation> flightPortal = pocket.flightUnlocked()
? findFrame(clicked, Material.GOLD_BLOCK)
: Optional.empty();
boolean activatingFlightPortal = flightPortal.isPresent();
Optional<PocketPortalLocation> portal = activatingFlightPortal
? flightPortal
: findFrame(clicked, frameMaterial);
if (portal.isEmpty()) {
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.");
player.sendMessage(ChatColor.RED + "The complete "
+ (activatingFlightPortal ? "flight" : "return")
+ " portal must be inside your Pocket Base boundary.");
event.setCancelled(true);
return;
}
try {
pocketBases.activateReturnPortal(player.getUniqueId(), portal.orElseThrow());
if (activatingFlightPortal) {
pocketBases.activateFlightPortal(
player.getUniqueId(), portal.orElseThrow()
);
} else {
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.");
logger.log(Level.SEVERE, "Could not activate Pocket Base "
+ (activatingFlightPortal ? "flight" : "return") + " portal", exception);
player.sendMessage(ChatColor.RED + "The Pocket Base "
+ (activatingFlightPortal ? "flight" : "return")
+ " portal could not be activated.");
event.setCancelled(true);
return;
}
event.setCancelled(true);
player.sendMessage(ChatColor.GREEN + "Pocket Base return portal activated.");
player.sendMessage(ChatColor.GREEN + "Pocket Base "
+ (activatingFlightPortal ? "flight" : "return") + " portal activated.");
return;
}
Optional<PocketPortalLocation> portal = findFrame(clicked, frameMaterial);
if (portal.isEmpty()) {
return;
}
PlayerState owner = baseStates.player(player.getUniqueId(), player.getName());
@@ -161,6 +193,13 @@ final class PocketBaseController implements Listener, Runnable {
deactivate(state.ownerId());
}
}
if (state.flightPortal().isPresent()) {
PocketPortalLocation portal = state.flightPortal().orElseThrow();
if (portal.worldId().equals(worldId)
&& PocketPortalGeometry.isFrame(portal, broken)) {
deactivateFlightPortal(state.ownerId());
}
}
}
}
@@ -199,6 +238,17 @@ final class PocketBaseController implements Listener, Runnable {
pocketBases.returnPortal(ownerId), position(destination)
)) {
leavePocket(player, ownerId);
return;
}
PocketBaseState state = pocketBases.state(ownerId);
if (flightController != null && state.flightPortal().isPresent()) {
PocketPortalLocation portal = state.flightPortal().orElseThrow();
if (isIntact(portal, Material.GOLD_BLOCK)
&& PocketPortalGeometry.isInterior(
portal, position(destination)
)) {
flightController.grantPocketPortalFlight(player, ownerId);
}
}
return;
}
@@ -316,18 +366,26 @@ final class PocketBaseController implements Listener, Runnable {
}
private boolean isIntact(PocketPortalLocation portal) {
World world = server.getWorld(portal.worldId());
if (world == null) {
return false;
}
Material frame = Material.valueOf(settings.current().pocketBasePortalFrameMaterial());
return PocketPortalGeometry.frameBlocks(portal).stream()
return isIntact(portal, frame);
}
private boolean isIntact(PocketPortalLocation portal, Material frame) {
World world = server.getWorld(portal.worldId());
return world != null && PocketPortalGeometry.frameBlocks(portal).stream()
.allMatch(position -> block(world, position).getType() == frame);
}
private void showPortal(PocketPortalLocation portal) {
showPortal(
portal,
Material.valueOf(settings.current().pocketBasePortalFrameMaterial())
);
}
private void showPortal(PocketPortalLocation portal, Material frame) {
World world = server.getWorld(portal.worldId());
if (world == null || !isIntact(portal)) {
if (world == null || !isIntact(portal, frame)) {
return;
}
for (BlockPosition position : PocketPortalGeometry.interiorBlocks(portal)) {
@@ -345,6 +403,14 @@ final class PocketBaseController implements Listener, Runnable {
}
}
private void deactivateFlightPortal(UUID ownerId) {
try {
pocketBases.deactivateFlightPortal(ownerId);
} catch (IOException exception) {
logger.log(Level.SEVERE, "Could not deactivate Pocket Base flight portal", exception);
}
}
private void deactivate(UUID ownerId) {
try {
pocketBases.deactivateEntrance(ownerId);
@@ -383,6 +449,17 @@ final class PocketBaseController implements Listener, Runnable {
);
}
private Optional<PocketPortalLocation> findFrame(Block clicked, Material material) {
World world = clicked.getWorld();
return PocketPortalGeometry.findFrame(
position(clicked),
world.getUID(),
world.getName(),
candidate -> block(world, candidate).getType() == material,
candidate -> isAir(block(world, candidate).getType())
);
}
private static boolean isAir(Material material) {
return material == Material.AIR || material == Material.CAVE_AIR
|| material == Material.VOID_AIR;
@@ -60,6 +60,24 @@ final class PocketBaseManager {
}
}
PocketBaseState activateFlightPortal(
UUID ownerId,
PocketPortalLocation portal
) throws IOException {
PocketBaseState current = states.state(ownerId);
if (!current.flightUnlocked()) {
throw new IllegalStateException("Pocket Base flight is still locked");
}
return states.updateAndSave(ownerId, state -> state.withFlightPortal(portal));
}
void deactivateFlightPortal(UUID ownerId) throws IOException {
PocketBaseState current = states.state(ownerId);
if (current.flightPortal().isPresent()) {
states.updateAndSave(ownerId, PocketBaseState::withoutFlightPortal);
}
}
PocketBaseState activateReturnPortal(
UUID ownerId,
PocketPortalLocation portal
@@ -103,6 +121,19 @@ final class PocketBaseManager {
return states.updateAndSave(ownerId, PocketBaseState::withFlightUnlocked);
}
PocketBaseState setFlightEnabled(UUID ownerId, boolean enabled) throws IOException {
PocketBaseState current = states.state(ownerId);
if (!current.flightUnlocked()) {
throw new IllegalStateException("Pocket Base flight is still locked");
}
if (current.flightEnabled() == enabled) {
return current;
}
return states.updateAndSave(
ownerId, state -> state.withFlightEnabled(enabled)
);
}
PocketBaseState setHostileMobSpawning(UUID ownerId, boolean enabled)
throws IOException {
PocketBaseState current = states.state(ownerId);
@@ -11,7 +11,9 @@ public record PocketBaseState(
boolean hostileMobSpawningEnabled,
boolean passiveMobSpawningEnabled,
PocketBaseBiome biome,
boolean flightUnlocked
boolean flightUnlocked,
Optional<PocketPortalLocation> flightPortal,
boolean flightEnabled
) {
public PocketBaseState {
if (ownerId == null) {
@@ -22,10 +24,34 @@ public record PocketBaseState(
}
entrance = entrance == null ? Optional.empty() : entrance;
returnPortal = returnPortal == null ? Optional.empty() : returnPortal;
flightPortal = flightPortal == null ? Optional.empty() : flightPortal;
biome = biome == null ? PocketBaseBiome.THE_VOID : biome;
if (level == 0 && (entrance.isPresent() || returnPortal.isPresent())) {
throw new IllegalArgumentException("a locked Pocket Base cannot have a portal");
if (level == 0 && (entrance.isPresent() || returnPortal.isPresent()
|| flightPortal.isPresent() || flightUnlocked || flightEnabled)) {
throw new IllegalArgumentException("a locked Pocket Base cannot have unlocks or portals");
}
if (!flightUnlocked && (flightPortal.isPresent() || flightEnabled)) {
throw new IllegalArgumentException(
"Pocket Base flight must be unlocked before it is enabled or shared"
);
}
}
public PocketBaseState(
UUID ownerId,
int level,
Optional<PocketPortalLocation> entrance,
Optional<PocketPortalLocation> returnPortal,
boolean hostileMobSpawningEnabled,
boolean passiveMobSpawningEnabled,
PocketBaseBiome biome,
boolean flightUnlocked
) {
this(
ownerId, level, entrance, returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome, flightUnlocked, Optional.empty(),
flightUnlocked
);
}
public PocketBaseState(
@@ -39,7 +65,7 @@ public record PocketBaseState(
) {
this(
ownerId, level, entrance, returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome, false
passiveMobSpawningEnabled, biome, false, Optional.empty(), false
);
}
@@ -50,70 +76,88 @@ public record PocketBaseState(
) {
this(
ownerId, level, entrance, Optional.empty(), false, false,
PocketBaseBiome.THE_VOID, false
PocketBaseBiome.THE_VOID, false, Optional.empty(), false
);
}
public static PocketBaseState locked(UUID ownerId) {
return new PocketBaseState(
ownerId, 0, Optional.empty(), Optional.empty(), false, false,
PocketBaseBiome.THE_VOID, false
PocketBaseBiome.THE_VOID, false, Optional.empty(), false
);
}
public PocketBaseState withLevel(int newLevel) {
return new PocketBaseState(
ownerId, newLevel, entrance, returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome, flightUnlocked
);
return copy(newLevel, entrance, returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome, flightUnlocked, flightPortal, flightEnabled);
}
public PocketBaseState withEntrance(PocketPortalLocation portal) {
return new PocketBaseState(
ownerId, level, Optional.of(portal), returnPortal,
hostileMobSpawningEnabled, passiveMobSpawningEnabled, biome, flightUnlocked
);
return copy(level, Optional.of(portal), returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome, flightUnlocked, flightPortal, flightEnabled);
}
public PocketBaseState withoutEntrance() {
return new PocketBaseState(
ownerId, level, Optional.empty(), returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome, flightUnlocked
);
return copy(level, Optional.empty(), returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome, flightUnlocked, flightPortal, flightEnabled);
}
public PocketBaseState withReturnPortal(PocketPortalLocation portal) {
return new PocketBaseState(
ownerId, level, entrance, Optional.of(portal), hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome, flightUnlocked
);
return copy(level, entrance, Optional.of(portal), hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome, flightUnlocked, flightPortal, flightEnabled);
}
public PocketBaseState withHostileMobSpawningEnabled(boolean enabled) {
return new PocketBaseState(
ownerId, level, entrance, returnPortal, enabled,
passiveMobSpawningEnabled, biome, flightUnlocked
);
return copy(level, entrance, returnPortal, enabled, passiveMobSpawningEnabled,
biome, flightUnlocked, flightPortal, flightEnabled);
}
public PocketBaseState withPassiveMobSpawningEnabled(boolean enabled) {
return new PocketBaseState(
ownerId, level, entrance, returnPortal, hostileMobSpawningEnabled,
enabled, biome, flightUnlocked
);
return copy(level, entrance, returnPortal, hostileMobSpawningEnabled, enabled,
biome, flightUnlocked, flightPortal, flightEnabled);
}
public PocketBaseState withBiome(PocketBaseBiome selectedBiome) {
return new PocketBaseState(
ownerId, level, entrance, returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, selectedBiome, flightUnlocked
);
return copy(level, entrance, returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, selectedBiome, flightUnlocked, flightPortal,
flightEnabled);
}
public PocketBaseState withFlightUnlocked() {
return copy(level, entrance, returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome, true, flightPortal, true);
}
public PocketBaseState withFlightEnabled(boolean enabled) {
return copy(level, entrance, returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome, flightUnlocked, flightPortal, enabled);
}
public PocketBaseState withFlightPortal(PocketPortalLocation portal) {
return copy(level, entrance, returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome, flightUnlocked, Optional.of(portal),
flightEnabled);
}
public PocketBaseState withoutFlightPortal() {
return copy(level, entrance, returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome, flightUnlocked, Optional.empty(), flightEnabled);
}
private PocketBaseState copy(
int newLevel,
Optional<PocketPortalLocation> newEntrance,
Optional<PocketPortalLocation> newReturnPortal,
boolean hostileEnabled,
boolean passiveEnabled,
PocketBaseBiome selectedBiome,
boolean unlockedFlight,
Optional<PocketPortalLocation> newFlightPortal,
boolean enabledFlight
) {
return new PocketBaseState(
ownerId, level, entrance, returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome, true
ownerId, newLevel, newEntrance, newReturnPortal, hostileEnabled, passiveEnabled,
selectedBiome, unlockedFlight, newFlightPortal, enabledFlight
);
}
}
@@ -82,7 +82,8 @@ public final class SpigotBasePlugin extends JavaPlugin {
boundsService,
pocketBaseManager,
settingsProvider,
getLogger()
getLogger(),
flightController
);
getServer().getPluginManager().registerEvents(progressListener, this);
getServer().getPluginManager().registerEvents(flightController, this);
@@ -39,6 +39,9 @@ public final class YamlPocketBaseRepository {
UUID ownerId = UUID.fromString(key);
String path = "owners." + key;
int level = yaml.getInt(path + ".level", 0);
boolean flightUnlocked = yaml.getBoolean(
path + ".flight-unlocked", false
);
PocketBaseState state = new PocketBaseState(
ownerId,
level,
@@ -47,7 +50,9 @@ public final class YamlPocketBaseRepository {
loadMobPreference(yaml, path, "hostile-mob-spawning-enabled"),
loadMobPreference(yaml, path, "passive-mob-spawning-enabled"),
loadBiome(yaml, path),
yaml.getBoolean(path + ".flight-unlocked", false)
flightUnlocked,
loadPortal(yaml, path + ".flight-portal"),
yaml.getBoolean(path + ".flight-enabled", flightUnlocked)
);
states.put(ownerId, state);
} catch (IllegalArgumentException ignored) {
@@ -75,12 +80,16 @@ public final class YamlPocketBaseRepository {
state.passiveMobSpawningEnabled()
);
yaml.set(path + ".flight-unlocked", state.flightUnlocked());
yaml.set(path + ".flight-enabled", state.flightEnabled());
yaml.set(path + ".world-type", state.biome().worldType().commandName());
yaml.set(path + ".biome", state.biome().commandName());
state.entrance().ifPresent(portal -> savePortal(yaml, path + ".entrance", portal));
state.returnPortal().ifPresent(portal ->
savePortal(yaml, path + ".return-portal", portal)
);
state.flightPortal().ifPresent(portal ->
savePortal(yaml, path + ".flight-portal", portal)
);
}
Path temporary = Files.createTempFile(parent, "spigot-base-pocket-", ".yml");
try {