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
@@ -1,21 +1,31 @@
package games.dmg.spigotbase;
import java.io.IOException;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.raid.RaidFinishEvent;
import org.bukkit.inventory.ItemStack;
final class BaseFlightController implements Runnable {
final class BaseFlightController implements Runnable, Listener {
private static final int POCKET_FLIGHT_BUFFER = 16;
private final Server server;
private final BaseStateManager stateManager;
private final SecondaryProgressionService progressionService;
private final BaseBoundsService boundsService;
private final PluginSettingsProvider settings;
private final PocketBaseManager pocketBases;
private final Logger logger;
private final Set<UUID> grantedFlight = new HashSet<>();
private final Set<UUID> warned = new HashSet<>();
@@ -25,12 +35,60 @@ final class BaseFlightController implements Runnable {
SecondaryProgressionService progressionService,
BaseBoundsService boundsService,
PluginSettingsProvider settings
) {
this(server, stateManager, progressionService, boundsService, settings, null,
Logger.getLogger(BaseFlightController.class.getName()));
}
BaseFlightController(
Server server,
BaseStateManager stateManager,
SecondaryProgressionService progressionService,
BaseBoundsService boundsService,
PluginSettingsProvider settings,
PocketBaseManager pocketBases,
Logger logger
) {
this.server = server;
this.stateManager = stateManager;
this.progressionService = progressionService;
this.boundsService = boundsService;
this.settings = settings;
this.pocketBases = pocketBases;
this.logger = logger;
}
@EventHandler
public void onRaidFinish(RaidFinishEvent event) {
if (pocketBases == null) {
return;
}
Optional<UUID> owner = pocketBases.ownerForPocketWorld(event.getWorld().getUID());
if (owner.isEmpty() || event.getWinners().stream().noneMatch(
winner -> winner.getUniqueId().equals(owner.orElseThrow()))) {
return;
}
UUID ownerId = owner.orElseThrow();
if (pocketBases.state(ownerId).flightUnlocked()) {
return;
}
try {
pocketBases.unlockFlight(ownerId);
} catch (IOException | RuntimeException exception) {
logger.log(Level.SEVERE, "Could not unlock Pocket Base flight", exception);
return;
}
event.getWinners().stream()
.filter(winner -> winner.getUniqueId().equals(ownerId))
.findFirst()
.orElseThrow()
.sendTitle(
ChatColor.GOLD + "Pocket Base Flight Unlocked",
ChatColor.YELLOW + "Flight is now available in your Pocket Base",
settings.current().titleFadeInTicks(),
settings.current().titleStayTicks(),
settings.current().titleFadeOutTicks()
);
}
@Override
@@ -89,8 +147,20 @@ final class BaseFlightController implements Runnable {
}
private void applyFlight(Player player, PlayerState state) {
if (player.getGameMode() != GameMode.SURVIVAL
|| !state.flightEnabled() || state.flightLevel() < 1 || state.base().isEmpty()) {
if (player.getGameMode() != GameMode.SURVIVAL) {
removeGrantedFlight(player);
return;
}
if (pocketBases != null) {
Optional<UUID> pocketOwner = pocketBases.ownerForPocketWorld(
player.getWorld().getUID()
);
if (pocketOwner.isPresent()) {
applyPocketFlight(player, pocketOwner.orElseThrow());
return;
}
}
if (!state.flightEnabled() || state.flightLevel() < 1 || state.base().isEmpty()) {
removeGrantedFlight(player);
return;
}
@@ -127,6 +197,43 @@ final class BaseFlightController implements Runnable {
}
}
private void applyPocketFlight(Player player, UUID ownerId) {
PocketBaseState pocket = pocketBases.state(ownerId);
if (!ownerId.equals(player.getUniqueId()) || !pocket.flightUnlocked()
|| pocket.level() < 1) {
removeGrantedFlight(player);
return;
}
int halfSize = pocketBases.policy().size(pocket.level()) / 2;
int x = player.getLocation().getBlockX();
int z = player.getLocation().getBlockZ();
if (x < -halfSize - POCKET_FLIGHT_BUFFER
|| x >= halfSize + POCKET_FLIGHT_BUFFER
|| z < -halfSize - POCKET_FLIGHT_BUFFER
|| z >= halfSize + POCKET_FLIGHT_BUFFER) {
removeGrantedFlight(player);
return;
}
if (!player.getAllowFlight()) {
player.setAllowFlight(true);
grantedFlight.add(player.getUniqueId());
}
boolean inBuffer = !pocketBases.policy().contains(pocket.level(), x, z);
if (inBuffer && player.isFlying()) {
if (warned.add(player.getUniqueId())) {
player.sendTitle(
ChatColor.RED + "Leaving Your Pocket Base",
ChatColor.YELLOW + "Turn back before Pocket Base flight ends",
0,
Math.min(30, settings.current().titleStayTicks()),
settings.current().titleFadeOutTicks()
);
}
} else {
warned.remove(player.getUniqueId());
}
}
private boolean withinVerticalRange(Player player, PlayerState state, BaseLocation base) {
int y = player.getLocation().getBlockY();
if (state.flightLevel() == 3) {
@@ -565,7 +565,9 @@ final class BaseSettingsCommand implements CommandExecutor, TabCompleter {
+ "; hostile spawning="
+ (pocket.hostileMobSpawningEnabled() ? "enabled" : "disabled")
+ "; passive spawning="
+ (pocket.passiveMobSpawningEnabled() ? "enabled" : "disabled"));
+ (pocket.passiveMobSpawningEnabled() ? "enabled" : "disabled")
+ "; flight="
+ (pocket.flightUnlocked() ? "unlocked" : "complete a raid to unlock"));
}
private void showCooldownPath(Player player, PlayerState state) {
@@ -159,7 +159,10 @@ final class BaseSettingsDialogFactory {
: "Biome: " + pocket.biome().worldType().commandName() + "/"
+ pocket.biome().commandName() + "\nHostile mobs: "
+ enabled(pocket.hostileMobSpawningEnabled()) + " • Passive mobs: "
+ enabled(pocket.passiveMobSpawningEnabled())
+ enabled(pocket.passiveMobSpawningEnabled()) + "\nFlight: "
+ (pocket.flightUnlocked()
? "Unlocked"
: "Complete a raid to unlock")
),
dialogs,
1,
@@ -92,6 +92,17 @@ final class PocketBaseManager {
}
}
PocketBaseState unlockFlight(UUID ownerId) throws IOException {
PocketBaseState current = states.state(ownerId);
if (current.level() < 1) {
throw new IllegalStateException("Pocket Base I is still locked");
}
if (current.flightUnlocked()) {
return current;
}
return states.updateAndSave(ownerId, PocketBaseState::withFlightUnlocked);
}
PocketBaseState setHostileMobSpawning(UUID ownerId, boolean enabled)
throws IOException {
PocketBaseState current = states.state(ownerId);
@@ -10,7 +10,8 @@ public record PocketBaseState(
Optional<PocketPortalLocation> returnPortal,
boolean hostileMobSpawningEnabled,
boolean passiveMobSpawningEnabled,
PocketBaseBiome biome
PocketBaseBiome biome,
boolean flightUnlocked
) {
public PocketBaseState {
if (ownerId == null) {
@@ -27,6 +28,21 @@ public record PocketBaseState(
}
}
public PocketBaseState(
UUID ownerId,
int level,
Optional<PocketPortalLocation> entrance,
Optional<PocketPortalLocation> returnPortal,
boolean hostileMobSpawningEnabled,
boolean passiveMobSpawningEnabled,
PocketBaseBiome biome
) {
this(
ownerId, level, entrance, returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome, false
);
}
public PocketBaseState(
UUID ownerId,
int level,
@@ -34,63 +50,70 @@ public record PocketBaseState(
) {
this(
ownerId, level, entrance, Optional.empty(), false, false,
PocketBaseBiome.THE_VOID
PocketBaseBiome.THE_VOID, false
);
}
public static PocketBaseState locked(UUID ownerId) {
return new PocketBaseState(
ownerId, 0, Optional.empty(), Optional.empty(), false, false,
PocketBaseBiome.THE_VOID
PocketBaseBiome.THE_VOID, false
);
}
public PocketBaseState withLevel(int newLevel) {
return new PocketBaseState(
ownerId, newLevel, entrance, returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome
passiveMobSpawningEnabled, biome, flightUnlocked
);
}
public PocketBaseState withEntrance(PocketPortalLocation portal) {
return new PocketBaseState(
ownerId, level, Optional.of(portal), returnPortal,
hostileMobSpawningEnabled, passiveMobSpawningEnabled, biome
hostileMobSpawningEnabled, passiveMobSpawningEnabled, biome, flightUnlocked
);
}
public PocketBaseState withoutEntrance() {
return new PocketBaseState(
ownerId, level, Optional.empty(), returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome
passiveMobSpawningEnabled, biome, flightUnlocked
);
}
public PocketBaseState withReturnPortal(PocketPortalLocation portal) {
return new PocketBaseState(
ownerId, level, entrance, Optional.of(portal), hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome
passiveMobSpawningEnabled, biome, flightUnlocked
);
}
public PocketBaseState withHostileMobSpawningEnabled(boolean enabled) {
return new PocketBaseState(
ownerId, level, entrance, returnPortal, enabled,
passiveMobSpawningEnabled, biome
passiveMobSpawningEnabled, biome, flightUnlocked
);
}
public PocketBaseState withPassiveMobSpawningEnabled(boolean enabled) {
return new PocketBaseState(
ownerId, level, entrance, returnPortal, hostileMobSpawningEnabled,
enabled, biome
enabled, biome, flightUnlocked
);
}
public PocketBaseState withBiome(PocketBaseBiome selectedBiome) {
return new PocketBaseState(
ownerId, level, entrance, returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, selectedBiome
passiveMobSpawningEnabled, selectedBiome, flightUnlocked
);
}
public PocketBaseState withFlightUnlocked() {
return new PocketBaseState(
ownerId, level, entrance, returnPortal, hostileMobSpawningEnabled,
passiveMobSpawningEnabled, biome, true
);
}
}
@@ -64,7 +64,8 @@ public final class SpigotBasePlugin extends JavaPlugin {
settingsProvider
);
flightController = new BaseFlightController(
getServer(), stateManager, secondaryProgressionService, boundsService, settingsProvider
getServer(), stateManager, secondaryProgressionService, boundsService,
settingsProvider, pocketBaseManager, getLogger()
);
VisitorPolicy visitorPolicy = new VisitorPolicy();
teleportManager = new BaseTeleportManager(
@@ -84,6 +85,7 @@ public final class SpigotBasePlugin extends JavaPlugin {
getLogger()
);
getServer().getPluginManager().registerEvents(progressListener, this);
getServer().getPluginManager().registerEvents(flightController, this);
getServer().getPluginManager().registerEvents(teleportManager, this);
getServer().getPluginManager().registerEvents(pocketBaseController, this);
@@ -46,7 +46,8 @@ public final class YamlPocketBaseRepository {
loadPortal(yaml, path + ".return-portal"),
loadMobPreference(yaml, path, "hostile-mob-spawning-enabled"),
loadMobPreference(yaml, path, "passive-mob-spawning-enabled"),
loadBiome(yaml, path)
loadBiome(yaml, path),
yaml.getBoolean(path + ".flight-unlocked", false)
);
states.put(ownerId, state);
} catch (IllegalArgumentException ignored) {
@@ -73,6 +74,7 @@ public final class YamlPocketBaseRepository {
path + ".passive-mob-spawning-enabled",
state.passiveMobSpawningEnabled()
);
yaml.set(path + ".flight-unlocked", state.flightUnlocked());
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));