From ca96459eb7fdf072141cec64f9833520c48936d0 Mon Sep 17 00:00:00 2001 From: rkfsociety Date: Mon, 4 May 2026 19:26:55 +0200 Subject: [PATCH 1/3] Paper 26: AsyncPlayerSpawnLocationEvent for rescue spawn Replace deprecated PlayerSpawnLocationEvent; run world access on the main thread via callSyncMethod. Compile against paper-api 26.1.2.build.60-stable. Update DummyChunk for current Chunk API (load level, structures, tile entities, four-arg chunk snapshot). --- pom.xml | 8 +- .../listeners/RescueListener.java | 85 +++++++++++++------ .../jikoo/regionerator/world/DummyChunk.java | 44 ++++++++-- 3 files changed, 98 insertions(+), 39 deletions(-) diff --git a/pom.xml b/pom.xml index bc8a65db..96faee20 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ UTF-8 - 17 + 21 @@ -20,9 +20,9 @@ provided - org.spigotmc - spigot-api - 1.19.3-R0.1-SNAPSHOT + io.papermc.paper + paper-api + 26.1.2.build.60-stable provided diff --git a/src/main/java/com/github/jikoo/regionerator/listeners/RescueListener.java b/src/main/java/com/github/jikoo/regionerator/listeners/RescueListener.java index 9f3821dc..1ed27f27 100644 --- a/src/main/java/com/github/jikoo/regionerator/listeners/RescueListener.java +++ b/src/main/java/com/github/jikoo/regionerator/listeners/RescueListener.java @@ -11,10 +11,17 @@ package com.github.jikoo.regionerator.listeners; import com.github.jikoo.regionerator.Regionerator; +import io.papermc.paper.event.player.AsyncPlayerSpawnLocationEvent; +import io.papermc.paper.persistence.PersistentDataContainerView; +import java.util.Collection; +import java.util.UUID; +import java.util.logging.Level; +import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.NamespacedKey; +import org.bukkit.OfflinePlayer; import org.bukkit.Tag; import org.bukkit.World; import org.bukkit.block.Block; @@ -28,10 +35,6 @@ import org.bukkit.persistence.PersistentDataType; import org.bukkit.util.BoundingBox; import org.jetbrains.annotations.NotNull; -import org.spigotmc.event.player.PlayerSpawnLocationEvent; - -import java.util.Collection; -import java.util.UUID; public class RescueListener implements Listener { @@ -50,40 +53,67 @@ private void onPlayerQuit(@NotNull PlayerQuitEvent event) { chunk.getPersistentDataContainer().set(key, PersistentDataType.BYTE, (byte) 1); } + /** + * Runs on an async thread; world and chunk access are delegated to the main thread. + */ @EventHandler(priority = EventPriority.LOWEST) // Run early so everyone overrides us - private void onPlayerSpawn(@NotNull PlayerSpawnLocationEvent event) { - Player player = event.getPlayer(); - // Check if the player has logged in since this feature was added. - PersistentDataContainer playerPdc = player.getPersistentDataContainer(); - if (!playerPdc.has(loggedInSinceFeature, PersistentDataType.BYTE)) { - playerPdc.set(loggedInSinceFeature, PersistentDataType.BYTE, (byte) 1); + private void onAsyncPlayerSpawn(@NotNull AsyncPlayerSpawnLocationEvent event) { + Runnable sync = () -> applySpawnRescue(event); + if (Bukkit.isPrimaryThread()) { + sync.run(); return; } + try { + Bukkit.getScheduler().callSyncMethod(plugin, () -> { + sync.run(); + return null; + }); + } catch (IllegalStateException ex) { + plugin.getLogger().warning("Rescue spawn skipped (scheduler unavailable): " + ex.getMessage()); + } catch (RuntimeException ex) { + plugin.getLogger().log(Level.WARNING, "Rescue spawn failed on main thread", ex); + } + } - Chunk chunk = event.getSpawnLocation().getChunk(); + private void applySpawnRescue(@NotNull AsyncPlayerSpawnLocationEvent event) { + UUID uuid = event.getConnection().getProfile().getId(); + if (uuid == null) { + return; + } + + OfflinePlayer offline = Bukkit.getOfflinePlayer(uuid); + PersistentDataContainerView playerPdcView = offline.getPersistentDataContainer(); + if (!playerPdcView.has(loggedInSinceFeature, PersistentDataType.BYTE)) { + plugin.getServer().getScheduler().runTask(plugin, () -> { + Player p = Bukkit.getPlayer(uuid); + if (p != null && p.isOnline()) { + p.getPersistentDataContainer().set(loggedInSinceFeature, PersistentDataType.BYTE, (byte) 1); + } + }); + return; + } + + Location spawnLoc = event.getSpawnLocation(); + Chunk chunk = spawnLoc.getChunk(); PersistentDataContainer chunkPdc = chunk.getPersistentDataContainer(); - NamespacedKey logoutKey = getLogoutKey(player.getUniqueId()); - // If the key is set, the chunk has not been deleted since the player last logged out. + NamespacedKey logoutKey = getLogoutKey(uuid); if (chunkPdc.has(logoutKey, PersistentDataType.BYTE)) { chunkPdc.remove(logoutKey); return; } - // If rescue is not enabled, exit early. Note that we do still want to do the tagging in case enabled later. if (!plugin.config().rescueEnabled()) { return; } - // Only rescue safe players if configured to do so. - if (!plugin.config().rescueIfSafe() && !isUnsafe(event.getSpawnLocation())) { + if (!plugin.config().rescueIfSafe() && !isUnsafe(spawnLoc)) { return; } - // If rescuing up, check if top block can be stood on safely. if (plugin.config().rescueToTopBlock()) { - World world = event.getSpawnLocation().getWorld(); + World world = spawnLoc.getWorld(); if (world != null) { - Block topBlock = world.getHighestBlockAt(event.getSpawnLocation()); + Block topBlock = world.getHighestBlockAt(spawnLoc); if (!isUnsafe(topBlock.getType()) && !isNotStandable(topBlock)) { event.setSpawnLocation(topBlock.getLocation().add(0.5, 1, 0.5)); return; @@ -91,21 +121,20 @@ private void onPlayerSpawn(@NotNull PlayerSpawnLocationEvent event) { } } - // If rescuing to personal respawn location, do so if available. if (plugin.config().rescueToRespawn()) { - Location spawnLoc = player.getBedSpawnLocation(); - if (spawnLoc != null) { - event.setSpawnLocation(spawnLoc); + Location respawn = offline.getRespawnLocation(true); + if (respawn != null) { + event.setSpawnLocation(respawn); return; } } - // Otherwise, use respawn location of rescue world. - World defaultWorld = event.getSpawnLocation().getWorld(); + World defaultWorld = spawnLoc.getWorld(); + if (defaultWorld == null) { + defaultWorld = Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0); + } if (defaultWorld == null) { - // Prefer event world, but fall through to player world. - // Theoretically player world may be default here, so we should avoid it. - defaultWorld = player.getWorld(); + return; } World spawnWorld = plugin.config().getRescueWorld(defaultWorld); diff --git a/src/main/java/com/github/jikoo/regionerator/world/DummyChunk.java b/src/main/java/com/github/jikoo/regionerator/world/DummyChunk.java index af28e139..787c487b 100644 --- a/src/main/java/com/github/jikoo/regionerator/world/DummyChunk.java +++ b/src/main/java/com/github/jikoo/regionerator/world/DummyChunk.java @@ -18,11 +18,15 @@ import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.generator.structure.GeneratedStructure; +import org.bukkit.generator.structure.Structure; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; import java.util.Collection; +import java.util.function.Predicate; /** * Dummy chunk used to prevent chunk loading when checking protection plugins. @@ -44,28 +48,29 @@ public DummyChunk(@NotNull World world, int chunkX, int chunkZ) { } @Override - public @NotNull ChunkSnapshot getChunkSnapshot() { + public @NotNull ChunkSnapshot getChunkSnapshot(boolean includeMaxblocky, boolean includeBiome, + boolean includeBiomeTempRain, boolean includeLightData) { throw new UnsupportedOperationException("DummyChunk does not support world operations."); } @Override - public @NotNull ChunkSnapshot getChunkSnapshot(boolean includeMaxblocky, boolean includeBiome, - boolean includeBiomeTempRain) { + public boolean isEntitiesLoaded() { throw new UnsupportedOperationException("DummyChunk does not support world operations."); } @Override - public boolean isEntitiesLoaded() { + public @NotNull Entity @NotNull [] getEntities() { throw new UnsupportedOperationException("DummyChunk does not support world operations."); } @Override - public @NotNull Entity @NotNull [] getEntities() { + public @NotNull BlockState @NotNull [] getTileEntities(boolean useSnapshot) { throw new UnsupportedOperationException("DummyChunk does not support world operations."); } @Override - public @NotNull BlockState @NotNull [] getTileEntities() { + public @NotNull Collection getTileEntities( + @NotNull Predicate blockPredicate, boolean useSnapshot) { throw new UnsupportedOperationException("DummyChunk does not support world operations."); } @@ -86,7 +91,17 @@ public int getZ() { @Override public boolean isLoaded() { - return this.world.isChunkLoaded(chunkZ, chunkZ); + return this.world.isChunkLoaded(this.chunkX, this.chunkZ); + } + + @Override + public @NotNull Chunk.LoadLevel getLoadLevel() { + return this.isLoaded() ? Chunk.LoadLevel.ENTITY_TICKING : Chunk.LoadLevel.UNLOADED; + } + + @Override + public boolean isGenerated() { + throw new UnsupportedOperationException("DummyChunk does not support world operations."); } @Override @@ -164,4 +179,19 @@ public boolean contains(@NotNull Biome biome) { throw new UnsupportedOperationException("DummyChunk does not support world operations."); } + @Override + public @NotNull Collection getPlayersSeeingChunk() { + throw new UnsupportedOperationException("DummyChunk does not support world operations."); + } + + @Override + public @NotNull Collection getStructures() { + throw new UnsupportedOperationException("DummyChunk does not support world operations."); + } + + @Override + public @NotNull Collection getStructures(@NotNull Structure structure) { + throw new UnsupportedOperationException("DummyChunk does not support world operations."); + } + } From f20ca344f22613e9d682161f34f3fd2c6db58cd3 Mon Sep 17 00:00:00 2001 From: rkfsociety Date: Mon, 4 May 2026 21:42:11 +0200 Subject: [PATCH 2/3] Fix WorldEdit load order: lazy FlagHandler + softdepend WorldEdit Defer FlagHandler construction until first flag/unflag command so WorldEdit classes (e.g. SessionOwner) are on the classpath. Add WorldEdit to plugin softdepend for ordering when Bukkit resolves dependencies. --- .../commands/RegioneratorExecutor.java | 26 ++++++++++++++++--- src/main/resources/plugin.yml | 1 + 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/github/jikoo/regionerator/commands/RegioneratorExecutor.java b/src/main/java/com/github/jikoo/regionerator/commands/RegioneratorExecutor.java index 1d044eec..f3f66eae 100644 --- a/src/main/java/com/github/jikoo/regionerator/commands/RegioneratorExecutor.java +++ b/src/main/java/com/github/jikoo/regionerator/commands/RegioneratorExecutor.java @@ -40,13 +40,31 @@ public class RegioneratorExecutor implements TabExecutor { private final @NotNull Regionerator plugin; private final @NotNull Map deletionRunnables; - private final @NotNull FlagHandler flagHandler; + /** + * Lazily created: {@link FlagHandler} touches WorldEdit types. If we construct it from + * {@link Regionerator#onEnable()} before WorldEdit has enabled, {@code SessionOwner} etc. are not + * visible yet and the plugin fails with {@link NoClassDefFoundError}. + */ + private volatile @Nullable FlagHandler flagHandler; public RegioneratorExecutor(@NotNull Regionerator plugin, @NotNull Map deletionRunnables) { this.plugin = plugin; this.deletionRunnables = deletionRunnables; - flagHandler = new FlagHandler(plugin); + } + + private @NotNull FlagHandler flagHandler() { + FlagHandler local = this.flagHandler; + if (local == null) { + synchronized (this) { + local = this.flagHandler; + if (local == null) { + local = new FlagHandler(this.plugin); + this.flagHandler = local; + } + } + } + return local; } @Override @@ -114,11 +132,11 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command } if (args[0].equals("flag")) { - flagHandler.handleFlags(sender, args, true); + flagHandler().handleFlags(sender, args, true); return true; } if (args[0].equals("unflag")) { - flagHandler.handleFlags(sender, args, false); + flagHandler().handleFlags(sender, args, false); return true; } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 3c3afe48..8ac29d00 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -18,6 +18,7 @@ softdepend: - Residence - Terrainer - Towny +- WorldEdit - WorldGuard commands: From ca3d78b3e01d3872648abae20cde97cee589a0a5 Mon Sep 17 00:00:00 2001 From: rkfsociety Date: Tue, 5 May 2026 19:08:36 +0200 Subject: [PATCH 3/3] Spigot compatibility: reflectively handle Paper async spawn; revert Paper-only API Restore spigot-api build, revert Chunk API changes, and remove WorldEdit load-order workaround from this PR. Register Paper AsyncPlayerSpawnLocationEvent handler via reflection when available while keeping PlayerSpawnLocationEvent for Spigot. --- pom.xml | 6 +- .../jikoo/regionerator/Regionerator.java | 4 +- .../commands/RegioneratorExecutor.java | 26 +--- .../listeners/RescueListener.java | 132 ++++++++++++------ .../jikoo/regionerator/world/DummyChunk.java | 42 +----- src/main/resources/plugin.yml | 1 - 6 files changed, 109 insertions(+), 102 deletions(-) diff --git a/pom.xml b/pom.xml index 96faee20..5e2b5669 100644 --- a/pom.xml +++ b/pom.xml @@ -20,9 +20,9 @@ provided - io.papermc.paper - paper-api - 26.1.2.build.60-stable + org.spigotmc + spigot-api + 1.19.3-R0.1-SNAPSHOT provided diff --git a/src/main/java/com/github/jikoo/regionerator/Regionerator.java b/src/main/java/com/github/jikoo/regionerator/Regionerator.java index c9f97a44..0f207137 100644 --- a/src/main/java/com/github/jikoo/regionerator/Regionerator.java +++ b/src/main/java/com/github/jikoo/regionerator/Regionerator.java @@ -163,7 +163,9 @@ public void reloadFeatures() { // Enable world case correction listener. getServer().getPluginManager().registerEvents(new WorldListener(this), this); // Enable rescue tagging listener. - getServer().getPluginManager().registerEvents(new RescueListener(this), this); + RescueListener rescueListener = new RescueListener(this); + getServer().getPluginManager().registerEvents(rescueListener, this); + rescueListener.registerPaperAsyncSpawnIfPresent(); // Always enable hook listener in case someone else adds hooks. getServer().getPluginManager().registerEvents(new HookListener(this), this); diff --git a/src/main/java/com/github/jikoo/regionerator/commands/RegioneratorExecutor.java b/src/main/java/com/github/jikoo/regionerator/commands/RegioneratorExecutor.java index f3f66eae..ddd5643a 100644 --- a/src/main/java/com/github/jikoo/regionerator/commands/RegioneratorExecutor.java +++ b/src/main/java/com/github/jikoo/regionerator/commands/RegioneratorExecutor.java @@ -40,31 +40,13 @@ public class RegioneratorExecutor implements TabExecutor { private final @NotNull Regionerator plugin; private final @NotNull Map deletionRunnables; - /** - * Lazily created: {@link FlagHandler} touches WorldEdit types. If we construct it from - * {@link Regionerator#onEnable()} before WorldEdit has enabled, {@code SessionOwner} etc. are not - * visible yet and the plugin fails with {@link NoClassDefFoundError}. - */ - private volatile @Nullable FlagHandler flagHandler; + private final @NotNull FlagHandler flagHandler; public RegioneratorExecutor(@NotNull Regionerator plugin, @NotNull Map deletionRunnables) { this.plugin = plugin; this.deletionRunnables = deletionRunnables; - } - - private @NotNull FlagHandler flagHandler() { - FlagHandler local = this.flagHandler; - if (local == null) { - synchronized (this) { - local = this.flagHandler; - if (local == null) { - local = new FlagHandler(this.plugin); - this.flagHandler = local; - } - } - } - return local; + this.flagHandler = new FlagHandler(plugin); } @Override @@ -132,11 +114,11 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command } if (args[0].equals("flag")) { - flagHandler().handleFlags(sender, args, true); + flagHandler.handleFlags(sender, args, true); return true; } if (args[0].equals("unflag")) { - flagHandler().handleFlags(sender, args, false); + flagHandler.handleFlags(sender, args, false); return true; } diff --git a/src/main/java/com/github/jikoo/regionerator/listeners/RescueListener.java b/src/main/java/com/github/jikoo/regionerator/listeners/RescueListener.java index 1ed27f27..5399a46b 100644 --- a/src/main/java/com/github/jikoo/regionerator/listeners/RescueListener.java +++ b/src/main/java/com/github/jikoo/regionerator/listeners/RescueListener.java @@ -11,30 +11,34 @@ package com.github.jikoo.regionerator.listeners; import com.github.jikoo.regionerator.Regionerator; -import io.papermc.paper.event.player.AsyncPlayerSpawnLocationEvent; -import io.papermc.paper.persistence.PersistentDataContainerView; import java.util.Collection; +import java.util.Optional; import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.logging.Level; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.NamespacedKey; -import org.bukkit.OfflinePlayer; import org.bukkit.Tag; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.entity.Player; +import org.bukkit.event.Event; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; +import org.bukkit.event.EventException; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; import org.bukkit.util.BoundingBox; import org.jetbrains.annotations.NotNull; +import org.spigotmc.event.player.PlayerSpawnLocationEvent; public class RescueListener implements Listener { @@ -46,6 +50,30 @@ public RescueListener(@NotNull Regionerator plugin) { this.loggedInSinceFeature = new NamespacedKey(plugin, "safe-logout"); } + /** + * Paper fires {@code AsyncPlayerSpawnLocationEvent} instead of {@link PlayerSpawnLocationEvent}. We keep Spigot + * compatibility by registering a reflective handler only if the Paper event class is present at runtime. + */ + public void registerPaperAsyncSpawnIfPresent() { + final Class asyncEventClass; + try { + asyncEventClass = Class.forName("io.papermc.paper.event.player.AsyncPlayerSpawnLocationEvent"); + } catch (ClassNotFoundException ignored) { + return; // Not running on Paper with this API. + } + + Bukkit.getPluginManager().registerEvent( + asyncEventClass.asSubclass(Event.class), + this, + EventPriority.LOWEST, + (listener, event) -> handlePaperAsyncSpawn(event), + plugin, + true + ); + + plugin.getLogger().fine("Registered Paper AsyncPlayerSpawnLocationEvent handler (reflective)."); + } + @EventHandler(priority = EventPriority.MONITOR) // Run late so we get final post-plugin-modification location private void onPlayerQuit(@NotNull PlayerQuitEvent event) { Chunk chunk = event.getPlayer().getLocation().getChunk(); @@ -53,92 +81,118 @@ private void onPlayerQuit(@NotNull PlayerQuitEvent event) { chunk.getPersistentDataContainer().set(key, PersistentDataType.BYTE, (byte) 1); } - /** - * Runs on an async thread; world and chunk access are delegated to the main thread. - */ + @SuppressWarnings("deprecation") // Paper warns about this; Paper handler is registered reflectively when available. @EventHandler(priority = EventPriority.LOWEST) // Run early so everyone overrides us - private void onAsyncPlayerSpawn(@NotNull AsyncPlayerSpawnLocationEvent event) { - Runnable sync = () -> applySpawnRescue(event); - if (Bukkit.isPrimaryThread()) { - sync.run(); + private void onPlayerSpawn(@NotNull PlayerSpawnLocationEvent event) { + Player player = event.getPlayer(); + // Check if the player has logged in since this feature was added. + PersistentDataContainer playerPdc = player.getPersistentDataContainer(); + if (!playerPdc.has(loggedInSinceFeature, PersistentDataType.BYTE)) { + playerPdc.set(loggedInSinceFeature, PersistentDataType.BYTE, (byte) 1); return; } - try { - Bukkit.getScheduler().callSyncMethod(plugin, () -> { - sync.run(); - return null; - }); - } catch (IllegalStateException ex) { - plugin.getLogger().warning("Rescue spawn skipped (scheduler unavailable): " + ex.getMessage()); - } catch (RuntimeException ex) { - plugin.getLogger().log(Level.WARNING, "Rescue spawn failed on main thread", ex); - } + + applySpawnRescue(player.getUniqueId(), Optional.of(player), event.getSpawnLocation(), event::setSpawnLocation); } - private void applySpawnRescue(@NotNull AsyncPlayerSpawnLocationEvent event) { - UUID uuid = event.getConnection().getProfile().getId(); - if (uuid == null) { + private void handlePaperAsyncSpawn(@NotNull Event event) throws EventException { + // Runs on an async thread; we must compute the result on the main thread and block. + final Object connection; + final Object profile; + final UUID uuid; + final Location spawnLoc; + try { + connection = event.getClass().getMethod("getConnection").invoke(event); + profile = connection.getClass().getMethod("getProfile").invoke(connection); + uuid = (UUID) profile.getClass().getMethod("getId").invoke(profile); + spawnLoc = (Location) event.getClass().getMethod("getSpawnLocation").invoke(event); + } catch (ReflectiveOperationException ex) { + throw new EventException(ex); + } + if (uuid == null || spawnLoc == null) { return; } - OfflinePlayer offline = Bukkit.getOfflinePlayer(uuid); - PersistentDataContainerView playerPdcView = offline.getPersistentDataContainer(); - if (!playerPdcView.has(loggedInSinceFeature, PersistentDataType.BYTE)) { - plugin.getServer().getScheduler().runTask(plugin, () -> { - Player p = Bukkit.getPlayer(uuid); - if (p != null && p.isOnline()) { - p.getPersistentDataContainer().set(loggedInSinceFeature, PersistentDataType.BYTE, (byte) 1); - } - }); - return; + try { + Location result = Bukkit.getScheduler().callSyncMethod(plugin, () -> + computeRescueSpawn(uuid, spawnLoc) + ).get(5, TimeUnit.SECONDS); + if (result != null) { + event.getClass().getMethod("setSpawnLocation", Location.class).invoke(event, result); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (ExecutionException | TimeoutException e) { + plugin.getLogger().log(Level.WARNING, "Failed to compute rescue spawn for async spawn event", e); + } catch (ReflectiveOperationException e) { + throw new EventException(e); } + } + + private @NotNull Location computeRescueSpawn(@NotNull UUID uuid, @NotNull Location spawnLoc) { + final Location[] out = new Location[] { spawnLoc }; + applySpawnRescue(uuid, Optional.empty(), spawnLoc, loc -> out[0] = loc); + return out[0]; + } - Location spawnLoc = event.getSpawnLocation(); + private void applySpawnRescue( + @NotNull UUID uuid, + @NotNull Optional player, + @NotNull Location spawnLoc, + @NotNull java.util.function.Consumer setSpawnLocation + ) { Chunk chunk = spawnLoc.getChunk(); PersistentDataContainer chunkPdc = chunk.getPersistentDataContainer(); NamespacedKey logoutKey = getLogoutKey(uuid); + // If the key is set, the chunk has not been deleted since the player last logged out. if (chunkPdc.has(logoutKey, PersistentDataType.BYTE)) { chunkPdc.remove(logoutKey); return; } + // If rescue is not enabled, exit early. Note that we do still want to do the tagging in case enabled later. if (!plugin.config().rescueEnabled()) { return; } + // Only rescue safe players if configured to do so. if (!plugin.config().rescueIfSafe() && !isUnsafe(spawnLoc)) { return; } + // If rescuing up, check if top block can be stood on safely. if (plugin.config().rescueToTopBlock()) { World world = spawnLoc.getWorld(); if (world != null) { Block topBlock = world.getHighestBlockAt(spawnLoc); if (!isUnsafe(topBlock.getType()) && !isNotStandable(topBlock)) { - event.setSpawnLocation(topBlock.getLocation().add(0.5, 1, 0.5)); + setSpawnLocation.accept(topBlock.getLocation().add(0.5, 1, 0.5)); return; } } } + // If rescuing to personal respawn location, do so if available. if (plugin.config().rescueToRespawn()) { - Location respawn = offline.getRespawnLocation(true); + Location respawn = player.map(Player::getBedSpawnLocation) + .orElseGet(() -> Bukkit.getOfflinePlayer(uuid).getBedSpawnLocation()); if (respawn != null) { - event.setSpawnLocation(respawn); + setSpawnLocation.accept(respawn); return; } } + // Otherwise, use respawn location of rescue world. World defaultWorld = spawnLoc.getWorld(); if (defaultWorld == null) { - defaultWorld = Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0); + defaultWorld = player.map(Player::getWorld).orElse(null); } if (defaultWorld == null) { return; } World spawnWorld = plugin.config().getRescueWorld(defaultWorld); - event.setSpawnLocation(spawnWorld.getSpawnLocation()); + setSpawnLocation.accept(spawnWorld.getSpawnLocation()); } private boolean isUnsafe(@NotNull Location location) { diff --git a/src/main/java/com/github/jikoo/regionerator/world/DummyChunk.java b/src/main/java/com/github/jikoo/regionerator/world/DummyChunk.java index 787c487b..d7b7e002 100644 --- a/src/main/java/com/github/jikoo/regionerator/world/DummyChunk.java +++ b/src/main/java/com/github/jikoo/regionerator/world/DummyChunk.java @@ -18,15 +18,11 @@ import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.generator.structure.GeneratedStructure; -import org.bukkit.generator.structure.Structure; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; import java.util.Collection; -import java.util.function.Predicate; /** * Dummy chunk used to prevent chunk loading when checking protection plugins. @@ -48,29 +44,28 @@ public DummyChunk(@NotNull World world, int chunkX, int chunkZ) { } @Override - public @NotNull ChunkSnapshot getChunkSnapshot(boolean includeMaxblocky, boolean includeBiome, - boolean includeBiomeTempRain, boolean includeLightData) { + public @NotNull ChunkSnapshot getChunkSnapshot() { throw new UnsupportedOperationException("DummyChunk does not support world operations."); } @Override - public boolean isEntitiesLoaded() { + public @NotNull ChunkSnapshot getChunkSnapshot(boolean includeMaxblocky, boolean includeBiome, + boolean includeBiomeTempRain) { throw new UnsupportedOperationException("DummyChunk does not support world operations."); } @Override - public @NotNull Entity @NotNull [] getEntities() { + public boolean isEntitiesLoaded() { throw new UnsupportedOperationException("DummyChunk does not support world operations."); } @Override - public @NotNull BlockState @NotNull [] getTileEntities(boolean useSnapshot) { + public @NotNull Entity @NotNull [] getEntities() { throw new UnsupportedOperationException("DummyChunk does not support world operations."); } @Override - public @NotNull Collection getTileEntities( - @NotNull Predicate blockPredicate, boolean useSnapshot) { + public @NotNull BlockState @NotNull [] getTileEntities() { throw new UnsupportedOperationException("DummyChunk does not support world operations."); } @@ -94,16 +89,6 @@ public boolean isLoaded() { return this.world.isChunkLoaded(this.chunkX, this.chunkZ); } - @Override - public @NotNull Chunk.LoadLevel getLoadLevel() { - return this.isLoaded() ? Chunk.LoadLevel.ENTITY_TICKING : Chunk.LoadLevel.UNLOADED; - } - - @Override - public boolean isGenerated() { - throw new UnsupportedOperationException("DummyChunk does not support world operations."); - } - @Override public boolean load() { throw new UnsupportedOperationException("DummyChunk does not support world operations."); @@ -179,19 +164,4 @@ public boolean contains(@NotNull Biome biome) { throw new UnsupportedOperationException("DummyChunk does not support world operations."); } - @Override - public @NotNull Collection getPlayersSeeingChunk() { - throw new UnsupportedOperationException("DummyChunk does not support world operations."); - } - - @Override - public @NotNull Collection getStructures() { - throw new UnsupportedOperationException("DummyChunk does not support world operations."); - } - - @Override - public @NotNull Collection getStructures(@NotNull Structure structure) { - throw new UnsupportedOperationException("DummyChunk does not support world operations."); - } - } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 8ac29d00..3c3afe48 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -18,7 +18,6 @@ softdepend: - Residence - Terrainer - Towny -- WorldEdit - WorldGuard commands: