load chunks passively

This commit is contained in:
Huynh Tien 2021-12-20 19:37:50 +07:00
parent 168de622ea
commit 333c9a8272

View File

@ -18,39 +18,40 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
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.
* @author tastybento
*
* @author tastybento
*/
public class SafeSpotTeleport {
private static final int MAX_CHUNKS = 6;
private static final long SPEED = 1;
private static final int MAX_RADIUS = 50;
private boolean notChecking;
private BukkitTask task;
// Parameters
private final Entity entity;
private final Location location;
private boolean portal;
private final int homeNumber;
// Locations
private Location bestSpot;
private final BentoBox plugin;
private List<Pair<Integer, Integer>> chunksToScan;
private final Runnable runnable;
private final Runnable failRunnable;
private final CompletableFuture<Boolean> result;
private final String homeName;
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
*
* @param builder - safe spot teleport builder
*/
SafeSpotTeleport(Builder builder) {
@ -63,7 +64,9 @@ public class SafeSpotTeleport {
this.runnable = builder.getRunnable();
this.failRunnable = builder.getFailRunnable();
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
Util.getChunkAtAsync(location).thenRun(() -> tryToGo(builder.getFailureMessage()));
}
@ -83,41 +86,42 @@ public class SafeSpotTeleport {
}
}
// Get chunks to scan
chunksToScan = getChunksToScan();
// Start checking
notChecking = true;
chunksToScanIterator = getChunksToScan().iterator();
// Start a recurring task until done or cancelled
task = Bukkit.getScheduler().runTaskTimer(plugin, () -> gatherChunks(failureMessage), 0L, SPEED);
}
private void gatherChunks(String failureMessage) {
if (!notChecking) {
if (checking.get()) {
return;
}
notChecking = false;
List<ChunkSnapshot> chunkSnapshot = new ArrayList<>();
Iterator<Pair<Integer, Integer>> it = chunksToScan.iterator();
if (!it.hasNext()) {
checking.set(true);
if (checkedChunks > MAX_CHUNKS || !chunksToScanIterator.hasNext()) {
// Nothing left
tidyUp(entity, failureMessage);
return;
}
// Add chunk snapshots to the list
while (it.hasNext() && chunkSnapshot.size() < MAX_CHUNKS) {
Pair<Integer, Integer> pair = it.next();
if (location.getWorld() != null) {
boolean isLoaded = location.getWorld().getChunkAt(pair.x, pair.z).isLoaded();
chunkSnapshot.add(location.getWorld().getChunkAt(pair.x, pair.z).getChunkSnapshot());
if (!isLoaded) {
location.getWorld().getChunkAt(pair.x, pair.z).unload();
// Get the chunk
Pair<Integer, Integer> chunkPair = chunksToScanIterator.next();
chunksToScanIterator.remove();
checkedChunks++;
if (checkedChunks >= MAX_CHUNKS) {
checking.set(false);
return;
}
// 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);
}
it.remove();
}
// Move to next step
checkChunks(chunkSnapshot);
});
}
private void tidyUp(Entity entity, String failureMessage) {
@ -128,7 +132,7 @@ public class SafeSpotTeleport {
if (portal && bestSpot != null) {
// Portals found, teleport to the best spot we found
teleportEntity(bestSpot);
} else if (entity instanceof Player) {
} else if (entity instanceof Player player) {
// Return to main thread and teleport the player
Bukkit.getScheduler().runTask(plugin, () -> {
// Failed, no safe spot
@ -137,15 +141,15 @@ public class SafeSpotTeleport {
}
if (!plugin.getIWM().inWorld(entity.getLocation())) {
// Last resort
((Player)entity).performCommand("spawn");
player.performCommand("spawn");
} else {
// Create a spot for the player to be
if (location.getWorld().getEnvironment().equals(Environment.NETHER)) {
makeAndTelport(Material.NETHERRACK);
} else if (location.getWorld().getEnvironment().equals(Environment.THE_END)) {
makeAndTelport(Material.END_STONE);
if (world.getEnvironment().equals(Environment.NETHER)) {
makeAndTeleport(Material.NETHERRACK);
} else if (world.getEnvironment().equals(Environment.THE_END)) {
makeAndTeleport(Material.END_STONE);
} else {
makeAndTelport(Material.COBBLESTONE);
makeAndTeleport(Material.COBBLESTONE);
}
}
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().setType(Material.AIR, false);
location.getBlock().getRelative(BlockFace.UP).setType(Material.AIR, false);
@ -174,11 +178,12 @@ public class SafeSpotTeleport {
/**
* Gets a set of chunk coords that will be scanned.
*
* @return - list of chunk coords to be scanned
*/
private List<Pair<Integer, Integer>> getChunksToScan() {
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);
int x = location.getBlockX();
int z = location.getBlockZ();
@ -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
* @return true if a safe spot was found
*/
private boolean scanChunk(ChunkSnapshot chunk) {
int startY = location.getBlockY();
int minY = location.getWorld().getMinHeight();
int minY = world.getMinHeight();
int maxY = 60; // Just a dummy value
// Check the safe spot at the current height
@ -296,6 +282,7 @@ public class SafeSpotTeleport {
/**
* Returns true if the location is a safe one.
*
* @param chunk - chunk snapshot
* @param x - x coordinate
* @param y - y coordinate
@ -303,7 +290,6 @@ public class SafeSpotTeleport {
* @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) {
World world = location.getWorld();
Material type = chunk.getBlockType(x, y, z);
Material space1 = chunk.getBlockType(x, Math.min(y + 1, maxHeight), z);
Material space2 = chunk.getBlockType(x, Math.min(y + 2, maxHeight), z);
@ -333,6 +319,7 @@ public class SafeSpotTeleport {
public static class Builder {
private final BentoBox plugin;
private final CompletableFuture<Boolean> result = new CompletableFuture<>();
private Entity entity;
private int homeNumber = 0;
private String homeName = "";
@ -341,7 +328,6 @@ public class SafeSpotTeleport {
private Location location;
private Runnable runnable;
private Runnable failRunnable;
private final CompletableFuture<Boolean> result = new CompletableFuture<>();
public Builder(BentoBox plugin) {
this.plugin = plugin;
@ -349,6 +335,7 @@ public class SafeSpotTeleport {
/**
* Set who or what is going to teleport
*
* @param entity entity to teleport
* @return Builder
*/
@ -359,6 +346,7 @@ public class SafeSpotTeleport {
/**
* Set the island to teleport to
*
* @param island island destination
* @return Builder
*/
@ -369,6 +357,7 @@ public class SafeSpotTeleport {
/**
* Set the home number to this number
*
* @param homeNumber home number
* @return Builder
* @deprecated use {@link #homeName}
@ -381,6 +370,7 @@ public class SafeSpotTeleport {
/**
* Set the home name
*
* @param homeName - home name
* @return Builder
* @since 1.16.0
@ -392,6 +382,7 @@ public class SafeSpotTeleport {
/**
* This is a portal teleportation
*
* @return Builder
*/
public Builder portal() {
@ -401,6 +392,7 @@ public class SafeSpotTeleport {
/**
* Set the failure message if this teleport cannot happen
*
* @param failureMessage failure message to report to user
* @return Builder
*/
@ -411,6 +403,7 @@ public class SafeSpotTeleport {
/**
* Set the desired location
*
* @param location the location
* @return Builder
*/
@ -421,6 +414,7 @@ public class SafeSpotTeleport {
/**
* Try to teleport the player
*
* @return CompletableFuture that will become true if successful and false if not
* @since 1.14.0
*/
@ -432,6 +426,7 @@ public class SafeSpotTeleport {
/**
* Try to teleport the player
*
* @return SafeSpotTeleport
*/
@Nullable
@ -447,6 +442,11 @@ public class SafeSpotTeleport {
result.complete(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) {
failureMessage = "general.errors.no-safe-location-found";
}
@ -455,6 +455,7 @@ public class SafeSpotTeleport {
/**
* The task to run after the player is safely teleported.
*
* @param runnable - task
* @return Builder
* @since 1.13.0
@ -466,14 +467,16 @@ public class SafeSpotTeleport {
/**
* The task to run if the player is not safely teleported
*
* @param runnable - task
* @return Builder
* @since 1.18.0
*/
public Builder ifFail(Runnable rannable) {
public Builder ifFail(Runnable runnable) {
this.failRunnable = runnable;
return this;
}
/**
* @return the plugin
*/