mirror of
https://github.com/BentoBoxWorld/BentoBox.git
synced 2024-11-01 00:10:40 +01:00
load chunks passively
This commit is contained in:
parent
168de622ea
commit
333c9a8272
@ -18,39 +18,40 @@ import java.util.ArrayList;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that calculates finds a safe spot asynchronously and then teleports the player there.
|
* A class that calculates finds a safe spot asynchronously and then teleports the player there.
|
||||||
* @author tastybento
|
|
||||||
*
|
*
|
||||||
|
* @author tastybento
|
||||||
*/
|
*/
|
||||||
public class SafeSpotTeleport {
|
public class SafeSpotTeleport {
|
||||||
|
|
||||||
private static final int MAX_CHUNKS = 6;
|
private static final int MAX_CHUNKS = 6;
|
||||||
private static final long SPEED = 1;
|
private static final long SPEED = 1;
|
||||||
private static final int MAX_RADIUS = 50;
|
private static final int MAX_RADIUS = 50;
|
||||||
private boolean notChecking;
|
|
||||||
private BukkitTask task;
|
|
||||||
|
|
||||||
// Parameters
|
// Parameters
|
||||||
private final Entity entity;
|
private final Entity entity;
|
||||||
private final Location location;
|
private final Location location;
|
||||||
private boolean portal;
|
|
||||||
private final int homeNumber;
|
private final int homeNumber;
|
||||||
|
|
||||||
// Locations
|
|
||||||
private Location bestSpot;
|
|
||||||
|
|
||||||
private final BentoBox plugin;
|
private final BentoBox plugin;
|
||||||
private List<Pair<Integer, Integer>> chunksToScan;
|
|
||||||
private final Runnable runnable;
|
private final Runnable runnable;
|
||||||
private final Runnable failRunnable;
|
private final Runnable failRunnable;
|
||||||
private final CompletableFuture<Boolean> result;
|
private final CompletableFuture<Boolean> result;
|
||||||
private final String homeName;
|
private final String homeName;
|
||||||
private final int maxHeight;
|
private final int maxHeight;
|
||||||
|
private final World world;
|
||||||
|
private final AtomicBoolean checking = new AtomicBoolean();
|
||||||
|
private BukkitTask task;
|
||||||
|
private boolean portal;
|
||||||
|
// Locations
|
||||||
|
private Location bestSpot;
|
||||||
|
private Iterator<Pair<Integer, Integer>> chunksToScanIterator;
|
||||||
|
private int checkedChunks = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Teleports and entity to a safe spot on island
|
* Teleports and entity to a safe spot on island
|
||||||
|
*
|
||||||
* @param builder - safe spot teleport builder
|
* @param builder - safe spot teleport builder
|
||||||
*/
|
*/
|
||||||
SafeSpotTeleport(Builder builder) {
|
SafeSpotTeleport(Builder builder) {
|
||||||
@ -63,9 +64,11 @@ public class SafeSpotTeleport {
|
|||||||
this.runnable = builder.getRunnable();
|
this.runnable = builder.getRunnable();
|
||||||
this.failRunnable = builder.getFailRunnable();
|
this.failRunnable = builder.getFailRunnable();
|
||||||
this.result = builder.getResult();
|
this.result = builder.getResult();
|
||||||
this.maxHeight = location.getWorld().getMaxHeight() - 20;
|
this.world = location.getWorld();
|
||||||
|
assert world != null;
|
||||||
|
this.maxHeight = world.getMaxHeight() - 20;
|
||||||
// Try to go
|
// Try to go
|
||||||
Util.getChunkAtAsync(location).thenRun(()-> tryToGo(builder.getFailureMessage()));
|
Util.getChunkAtAsync(location).thenRun(() -> tryToGo(builder.getFailureMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryToGo(String failureMessage) {
|
private void tryToGo(String failureMessage) {
|
||||||
@ -83,41 +86,42 @@ public class SafeSpotTeleport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Get chunks to scan
|
// Get chunks to scan
|
||||||
chunksToScan = getChunksToScan();
|
chunksToScanIterator = getChunksToScan().iterator();
|
||||||
|
|
||||||
// Start checking
|
|
||||||
notChecking = true;
|
|
||||||
|
|
||||||
// Start a recurring task until done or cancelled
|
// Start a recurring task until done or cancelled
|
||||||
task = Bukkit.getScheduler().runTaskTimer(plugin, () -> gatherChunks(failureMessage), 0L, SPEED);
|
task = Bukkit.getScheduler().runTaskTimer(plugin, () -> gatherChunks(failureMessage), 0L, SPEED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void gatherChunks(String failureMessage) {
|
private void gatherChunks(String failureMessage) {
|
||||||
if (!notChecking) {
|
if (checking.get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
notChecking = false;
|
checking.set(true);
|
||||||
List<ChunkSnapshot> chunkSnapshot = new ArrayList<>();
|
if (checkedChunks > MAX_CHUNKS || !chunksToScanIterator.hasNext()) {
|
||||||
Iterator<Pair<Integer, Integer>> it = chunksToScan.iterator();
|
|
||||||
if (!it.hasNext()) {
|
|
||||||
// Nothing left
|
// Nothing left
|
||||||
tidyUp(entity, failureMessage);
|
tidyUp(entity, failureMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Add chunk snapshots to the list
|
|
||||||
while (it.hasNext() && chunkSnapshot.size() < MAX_CHUNKS) {
|
// Get the chunk
|
||||||
Pair<Integer, Integer> pair = it.next();
|
Pair<Integer, Integer> chunkPair = chunksToScanIterator.next();
|
||||||
if (location.getWorld() != null) {
|
chunksToScanIterator.remove();
|
||||||
boolean isLoaded = location.getWorld().getChunkAt(pair.x, pair.z).isLoaded();
|
checkedChunks++;
|
||||||
chunkSnapshot.add(location.getWorld().getChunkAt(pair.x, pair.z).getChunkSnapshot());
|
if (checkedChunks >= MAX_CHUNKS) {
|
||||||
if (!isLoaded) {
|
checking.set(false);
|
||||||
location.getWorld().getChunkAt(pair.x, pair.z).unload();
|
return;
|
||||||
}
|
|
||||||
}
|
|
||||||
it.remove();
|
|
||||||
}
|
}
|
||||||
// Move to next step
|
|
||||||
checkChunks(chunkSnapshot);
|
// Get the chunk snapshot and scan it
|
||||||
|
Util.getChunkAtAsync(world, chunkPair.x, chunkPair.z)
|
||||||
|
.thenApply(Chunk::getChunkSnapshot)
|
||||||
|
.whenCompleteAsync((snapshot, e) -> {
|
||||||
|
if (snapshot != null && scanChunk(snapshot)) {
|
||||||
|
task.cancel();
|
||||||
|
} else {
|
||||||
|
checking.set(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tidyUp(Entity entity, String failureMessage) {
|
private void tidyUp(Entity entity, String failureMessage) {
|
||||||
@ -128,7 +132,7 @@ public class SafeSpotTeleport {
|
|||||||
if (portal && bestSpot != null) {
|
if (portal && bestSpot != null) {
|
||||||
// Portals found, teleport to the best spot we found
|
// Portals found, teleport to the best spot we found
|
||||||
teleportEntity(bestSpot);
|
teleportEntity(bestSpot);
|
||||||
} else if (entity instanceof Player) {
|
} else if (entity instanceof Player player) {
|
||||||
// Return to main thread and teleport the player
|
// Return to main thread and teleport the player
|
||||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
// Failed, no safe spot
|
// Failed, no safe spot
|
||||||
@ -137,15 +141,15 @@ public class SafeSpotTeleport {
|
|||||||
}
|
}
|
||||||
if (!plugin.getIWM().inWorld(entity.getLocation())) {
|
if (!plugin.getIWM().inWorld(entity.getLocation())) {
|
||||||
// Last resort
|
// Last resort
|
||||||
((Player)entity).performCommand("spawn");
|
player.performCommand("spawn");
|
||||||
} else {
|
} else {
|
||||||
// Create a spot for the player to be
|
// Create a spot for the player to be
|
||||||
if (location.getWorld().getEnvironment().equals(Environment.NETHER)) {
|
if (world.getEnvironment().equals(Environment.NETHER)) {
|
||||||
makeAndTelport(Material.NETHERRACK);
|
makeAndTeleport(Material.NETHERRACK);
|
||||||
} else if (location.getWorld().getEnvironment().equals(Environment.THE_END)) {
|
} else if (world.getEnvironment().equals(Environment.THE_END)) {
|
||||||
makeAndTelport(Material.END_STONE);
|
makeAndTeleport(Material.END_STONE);
|
||||||
} else {
|
} else {
|
||||||
makeAndTelport(Material.COBBLESTONE);
|
makeAndTeleport(Material.COBBLESTONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (failRunnable != null) {
|
if (failRunnable != null) {
|
||||||
@ -161,7 +165,7 @@ public class SafeSpotTeleport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeAndTelport(Material m) {
|
private void makeAndTeleport(Material m) {
|
||||||
location.getBlock().getRelative(BlockFace.DOWN).setType(m, false);
|
location.getBlock().getRelative(BlockFace.DOWN).setType(m, false);
|
||||||
location.getBlock().setType(Material.AIR, false);
|
location.getBlock().setType(Material.AIR, false);
|
||||||
location.getBlock().getRelative(BlockFace.UP).setType(Material.AIR, false);
|
location.getBlock().getRelative(BlockFace.UP).setType(Material.AIR, false);
|
||||||
@ -174,20 +178,21 @@ public class SafeSpotTeleport {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a set of chunk coords that will be scanned.
|
* Gets a set of chunk coords that will be scanned.
|
||||||
|
*
|
||||||
* @return - list of chunk coords to be scanned
|
* @return - list of chunk coords to be scanned
|
||||||
*/
|
*/
|
||||||
private List<Pair<Integer, Integer>> getChunksToScan() {
|
private List<Pair<Integer, Integer>> getChunksToScan() {
|
||||||
List<Pair<Integer, Integer>> chunksToScan = new ArrayList<>();
|
List<Pair<Integer, Integer>> chunksToScan = new ArrayList<>();
|
||||||
int maxRadius = plugin.getIslands().getIslandAt(location).map(Island::getProtectionRange).orElseGet(() -> plugin.getIWM().getIslandProtectionRange(location.getWorld()));
|
int maxRadius = plugin.getIslands().getIslandAt(location).map(Island::getProtectionRange).orElseGet(() -> plugin.getIWM().getIslandProtectionRange(world));
|
||||||
maxRadius = Math.min(MAX_RADIUS, maxRadius);
|
maxRadius = Math.min(MAX_RADIUS, maxRadius);
|
||||||
int x = location.getBlockX();
|
int x = location.getBlockX();
|
||||||
int z = location.getBlockZ();
|
int z = location.getBlockZ();
|
||||||
// Create ever increasing squares around the target location
|
// Create ever increasing squares around the target location
|
||||||
int radius = 0;
|
int radius = 0;
|
||||||
do {
|
do {
|
||||||
for (int i = x - radius; i <= x + radius; i+=16) {
|
for (int i = x - radius; i <= x + radius; i += 16) {
|
||||||
for (int j = z - radius; j <= z + radius; j+=16) {
|
for (int j = z - radius; j <= z + radius; j += 16) {
|
||||||
addChunk(chunksToScan, new Pair<>(i,j), new Pair<>(i >> 4, j >> 4));
|
addChunk(chunksToScan, new Pair<>(i, j), new Pair<>(i >> 4, j >> 4));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
radius++;
|
radius++;
|
||||||
@ -201,32 +206,13 @@ public class SafeSpotTeleport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loops through the chunks and if a safe spot is found, fires off the teleportation
|
|
||||||
* @param chunkSnapshot - list of chunk snapshots to check
|
|
||||||
*/
|
|
||||||
private void checkChunks(final List<ChunkSnapshot> chunkSnapshot) {
|
|
||||||
// Run async task to scan chunks
|
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
|
||||||
for (ChunkSnapshot chunk: chunkSnapshot) {
|
|
||||||
if (scanChunk(chunk)) {
|
|
||||||
task.cancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Nothing happened, change state
|
|
||||||
notChecking = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param chunk - chunk snapshot
|
* @param chunk - chunk snapshot
|
||||||
* @return true if a safe spot was found
|
* @return true if a safe spot was found
|
||||||
*/
|
*/
|
||||||
private boolean scanChunk(ChunkSnapshot chunk) {
|
private boolean scanChunk(ChunkSnapshot chunk) {
|
||||||
int startY = location.getBlockY();
|
int startY = location.getBlockY();
|
||||||
int minY = location.getWorld().getMinHeight();
|
int minY = world.getMinHeight();
|
||||||
int maxY = 60; // Just a dummy value
|
int maxY = 60; // Just a dummy value
|
||||||
|
|
||||||
// Check the safe spot at the current height
|
// Check the safe spot at the current height
|
||||||
@ -296,14 +282,14 @@ public class SafeSpotTeleport {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the location is a safe one.
|
* Returns true if the location is a safe one.
|
||||||
|
*
|
||||||
* @param chunk - chunk snapshot
|
* @param chunk - chunk snapshot
|
||||||
* @param x - x coordinate
|
* @param x - x coordinate
|
||||||
* @param y - y coordinate
|
* @param y - y coordinate
|
||||||
* @param z - z coordinate
|
* @param z - z coordinate
|
||||||
* @return true if this is a safe spot, false if this is a portal scan
|
* @return true if this is a safe spot, false if this is a portal scan
|
||||||
*/
|
*/
|
||||||
boolean checkBlock(ChunkSnapshot chunk, int x, int y, int z) {
|
boolean checkBlock(ChunkSnapshot chunk, int x, int y, int z) {
|
||||||
World world = location.getWorld();
|
|
||||||
Material type = chunk.getBlockType(x, y, z);
|
Material type = chunk.getBlockType(x, y, z);
|
||||||
Material space1 = chunk.getBlockType(x, Math.min(y + 1, maxHeight), z);
|
Material space1 = chunk.getBlockType(x, Math.min(y + 1, maxHeight), z);
|
||||||
Material space2 = chunk.getBlockType(x, Math.min(y + 2, maxHeight), z);
|
Material space2 = chunk.getBlockType(x, Math.min(y + 2, maxHeight), z);
|
||||||
@ -333,6 +319,7 @@ public class SafeSpotTeleport {
|
|||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
private final BentoBox plugin;
|
private final BentoBox plugin;
|
||||||
|
private final CompletableFuture<Boolean> result = new CompletableFuture<>();
|
||||||
private Entity entity;
|
private Entity entity;
|
||||||
private int homeNumber = 0;
|
private int homeNumber = 0;
|
||||||
private String homeName = "";
|
private String homeName = "";
|
||||||
@ -341,7 +328,6 @@ public class SafeSpotTeleport {
|
|||||||
private Location location;
|
private Location location;
|
||||||
private Runnable runnable;
|
private Runnable runnable;
|
||||||
private Runnable failRunnable;
|
private Runnable failRunnable;
|
||||||
private final CompletableFuture<Boolean> result = new CompletableFuture<>();
|
|
||||||
|
|
||||||
public Builder(BentoBox plugin) {
|
public Builder(BentoBox plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
@ -349,6 +335,7 @@ public class SafeSpotTeleport {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set who or what is going to teleport
|
* Set who or what is going to teleport
|
||||||
|
*
|
||||||
* @param entity entity to teleport
|
* @param entity entity to teleport
|
||||||
* @return Builder
|
* @return Builder
|
||||||
*/
|
*/
|
||||||
@ -359,6 +346,7 @@ public class SafeSpotTeleport {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the island to teleport to
|
* Set the island to teleport to
|
||||||
|
*
|
||||||
* @param island island destination
|
* @param island island destination
|
||||||
* @return Builder
|
* @return Builder
|
||||||
*/
|
*/
|
||||||
@ -369,6 +357,7 @@ public class SafeSpotTeleport {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the home number to this number
|
* Set the home number to this number
|
||||||
|
*
|
||||||
* @param homeNumber home number
|
* @param homeNumber home number
|
||||||
* @return Builder
|
* @return Builder
|
||||||
* @deprecated use {@link #homeName}
|
* @deprecated use {@link #homeName}
|
||||||
@ -381,6 +370,7 @@ public class SafeSpotTeleport {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the home name
|
* Set the home name
|
||||||
|
*
|
||||||
* @param homeName - home name
|
* @param homeName - home name
|
||||||
* @return Builder
|
* @return Builder
|
||||||
* @since 1.16.0
|
* @since 1.16.0
|
||||||
@ -392,6 +382,7 @@ public class SafeSpotTeleport {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a portal teleportation
|
* This is a portal teleportation
|
||||||
|
*
|
||||||
* @return Builder
|
* @return Builder
|
||||||
*/
|
*/
|
||||||
public Builder portal() {
|
public Builder portal() {
|
||||||
@ -401,6 +392,7 @@ public class SafeSpotTeleport {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the failure message if this teleport cannot happen
|
* Set the failure message if this teleport cannot happen
|
||||||
|
*
|
||||||
* @param failureMessage failure message to report to user
|
* @param failureMessage failure message to report to user
|
||||||
* @return Builder
|
* @return Builder
|
||||||
*/
|
*/
|
||||||
@ -411,6 +403,7 @@ public class SafeSpotTeleport {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the desired location
|
* Set the desired location
|
||||||
|
*
|
||||||
* @param location the location
|
* @param location the location
|
||||||
* @return Builder
|
* @return Builder
|
||||||
*/
|
*/
|
||||||
@ -421,6 +414,7 @@ public class SafeSpotTeleport {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to teleport the player
|
* Try to teleport the player
|
||||||
|
*
|
||||||
* @return CompletableFuture that will become true if successful and false if not
|
* @return CompletableFuture that will become true if successful and false if not
|
||||||
* @since 1.14.0
|
* @since 1.14.0
|
||||||
*/
|
*/
|
||||||
@ -432,6 +426,7 @@ public class SafeSpotTeleport {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to teleport the player
|
* Try to teleport the player
|
||||||
|
*
|
||||||
* @return SafeSpotTeleport
|
* @return SafeSpotTeleport
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -447,6 +442,11 @@ public class SafeSpotTeleport {
|
|||||||
result.complete(null);
|
result.complete(null);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (location.getWorld() == null) {
|
||||||
|
plugin.logError("Attempt to safe teleport to a null world!");
|
||||||
|
result.complete(null);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (failureMessage.isEmpty() && entity instanceof Player) {
|
if (failureMessage.isEmpty() && entity instanceof Player) {
|
||||||
failureMessage = "general.errors.no-safe-location-found";
|
failureMessage = "general.errors.no-safe-location-found";
|
||||||
}
|
}
|
||||||
@ -455,6 +455,7 @@ public class SafeSpotTeleport {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The task to run after the player is safely teleported.
|
* The task to run after the player is safely teleported.
|
||||||
|
*
|
||||||
* @param runnable - task
|
* @param runnable - task
|
||||||
* @return Builder
|
* @return Builder
|
||||||
* @since 1.13.0
|
* @since 1.13.0
|
||||||
@ -466,14 +467,16 @@ public class SafeSpotTeleport {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The task to run if the player is not safely teleported
|
* The task to run if the player is not safely teleported
|
||||||
|
*
|
||||||
* @param runnable - task
|
* @param runnable - task
|
||||||
* @return Builder
|
* @return Builder
|
||||||
* @since 1.18.0
|
* @since 1.18.0
|
||||||
*/
|
*/
|
||||||
public Builder ifFail(Runnable rannable) {
|
public Builder ifFail(Runnable runnable) {
|
||||||
this.failRunnable = runnable;
|
this.failRunnable = runnable;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the plugin
|
* @return the plugin
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user