mirror of
https://github.com/BentoBoxWorld/BentoBox.git
synced 2024-10-31 15:59:48 +01:00
Optimize safe location searching with configurable range limit (#1892)
* better safe spot finder * this should be better * limit how far the y-coordinate will be expanded * load chunks passively
This commit is contained in:
parent
ed50765b98
commit
e3b99195ca
@ -298,6 +298,13 @@ public class Settings implements ConfigObject {
|
||||
@ConfigEntry(path = "island.deletion.keep-previous-island-on-reset", since = "1.13.0")
|
||||
private boolean keepPreviousIslandOnReset = false;
|
||||
|
||||
@ConfigComment("By default, If the destination is not safe, the plugin will try to search for a safe spot around the destination,")
|
||||
@ConfigComment("then it will try to expand the y-coordinate up and down from the destination.")
|
||||
@ConfigComment("This setting limits how far the y-coordinate will be expanded.")
|
||||
@ConfigComment("If set to 0 or lower, the plugin will not expand the y-coordinate.")
|
||||
@ConfigEntry(path = "island.safe-spot-search-vertical-range", since = "1.19.1")
|
||||
private int safeSpotSearchVerticalRange = 400;
|
||||
|
||||
/* WEB */
|
||||
@ConfigComment("Toggle whether BentoBox can connect to GitHub to get data about updates and addons.")
|
||||
@ConfigComment("Disabling this will result in the deactivation of the update checker and of some other")
|
||||
@ -890,4 +897,12 @@ public class Settings implements ConfigObject {
|
||||
public void setMinPortalSearchRadius(int minPortalSearchRadius) {
|
||||
this.minPortalSearchRadius = minPortalSearchRadius;
|
||||
}
|
||||
|
||||
public int getSafeSpotSearchVerticalRange() {
|
||||
return safeSpotSearchVerticalRange;
|
||||
}
|
||||
|
||||
public void setSafeSpotSearchVerticalRange(int safeSpotSearchVerticalRange) {
|
||||
this.safeSpotSearchVerticalRange = safeSpotSearchVerticalRange;
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,6 @@
|
||||
package world.bentobox.bentobox.util.teleport;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChunkSnapshot;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.World.Environment;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.entity.Entity;
|
||||
@ -17,45 +8,50 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import world.bentobox.bentobox.BentoBox;
|
||||
import world.bentobox.bentobox.api.user.User;
|
||||
import world.bentobox.bentobox.database.objects.Island;
|
||||
import world.bentobox.bentobox.util.Pair;
|
||||
import world.bentobox.bentobox.util.Util;
|
||||
|
||||
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) {
|
||||
@ -68,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()));
|
||||
}
|
||||
@ -88,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) {
|
||||
@ -133,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
|
||||
@ -142,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) {
|
||||
@ -166,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);
|
||||
@ -179,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();
|
||||
@ -206,41 +206,59 @@ 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) {
|
||||
// Run through the chunk
|
||||
int startY = location.getBlockY();
|
||||
int minY = world.getMinHeight();
|
||||
int maxY = 60; // Just a dummy value
|
||||
|
||||
// Check the safe spot at the current height
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
// Work down from the entry point up
|
||||
for (int y = Math.min(chunk.getHighestBlockYAt(x, z), maxHeight); y >= 0; y--) {
|
||||
if (checkBlock(chunk, x,y,z)) {
|
||||
if (minY >= startY && checkBlock(chunk, x, startY, z)) {
|
||||
return true;
|
||||
}
|
||||
} // end y
|
||||
} //end z
|
||||
} // end x
|
||||
maxY = Math.max(chunk.getHighestBlockYAt(x, z), maxY);
|
||||
}
|
||||
}
|
||||
maxY = Math.min(maxY, maxHeight);
|
||||
|
||||
// Expand the height up and down until a safe spot is found
|
||||
int upperY = startY + 1;
|
||||
int lowerY = startY - 1;
|
||||
boolean checkUpper = upperY <= maxY;
|
||||
boolean checkLower = lowerY >= minY;
|
||||
int limitRange = plugin.getSettings().getSafeSpotSearchVerticalRange(); // Limit the y-coordinate range
|
||||
while (limitRange > 0 && (checkUpper || checkLower)) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
if (checkUpper && checkBlock(chunk, x, upperY, z)) {
|
||||
return true;
|
||||
}
|
||||
if (checkLower && checkBlock(chunk, x, lowerY, z)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (checkUpper) {
|
||||
upperY++;
|
||||
if (upperY > maxY) {
|
||||
checkUpper = false;
|
||||
}
|
||||
}
|
||||
if (checkLower) {
|
||||
lowerY--;
|
||||
if (lowerY < minY) {
|
||||
checkLower = false;
|
||||
}
|
||||
}
|
||||
limitRange--;
|
||||
}
|
||||
|
||||
// We can't find a safe spot
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -264,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
|
||||
@ -271,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);
|
||||
@ -301,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 = "";
|
||||
@ -309,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;
|
||||
@ -317,6 +335,7 @@ public class SafeSpotTeleport {
|
||||
|
||||
/**
|
||||
* Set who or what is going to teleport
|
||||
*
|
||||
* @param entity entity to teleport
|
||||
* @return Builder
|
||||
*/
|
||||
@ -327,6 +346,7 @@ public class SafeSpotTeleport {
|
||||
|
||||
/**
|
||||
* Set the island to teleport to
|
||||
*
|
||||
* @param island island destination
|
||||
* @return Builder
|
||||
*/
|
||||
@ -337,6 +357,7 @@ public class SafeSpotTeleport {
|
||||
|
||||
/**
|
||||
* Set the home number to this number
|
||||
*
|
||||
* @param homeNumber home number
|
||||
* @return Builder
|
||||
* @deprecated use {@link #homeName}
|
||||
@ -349,6 +370,7 @@ public class SafeSpotTeleport {
|
||||
|
||||
/**
|
||||
* Set the home name
|
||||
*
|
||||
* @param homeName - home name
|
||||
* @return Builder
|
||||
* @since 1.16.0
|
||||
@ -360,6 +382,7 @@ public class SafeSpotTeleport {
|
||||
|
||||
/**
|
||||
* This is a portal teleportation
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
public Builder portal() {
|
||||
@ -369,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
|
||||
*/
|
||||
@ -379,6 +403,7 @@ public class SafeSpotTeleport {
|
||||
|
||||
/**
|
||||
* Set the desired location
|
||||
*
|
||||
* @param location the location
|
||||
* @return Builder
|
||||
*/
|
||||
@ -389,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
|
||||
*/
|
||||
@ -400,6 +426,7 @@ public class SafeSpotTeleport {
|
||||
|
||||
/**
|
||||
* Try to teleport the player
|
||||
*
|
||||
* @return SafeSpotTeleport
|
||||
*/
|
||||
@Nullable
|
||||
@ -415,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";
|
||||
}
|
||||
@ -423,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
|
||||
@ -434,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
|
||||
*/
|
||||
|
@ -195,6 +195,12 @@ island:
|
||||
# This is the default behaviour.
|
||||
# Added since 1.13.0.
|
||||
keep-previous-island-on-reset: false
|
||||
# By default, If the destination is not safe, the plugin will try to search for a safe spot around the destination,
|
||||
# then it will try to expand the y-coordinate up and down from the destination.
|
||||
# This setting limits how far the y-coordinate will be expanded.
|
||||
# If set to 0 or lower, the plugin will not expand the y-coordinate.
|
||||
# Added since 1.19.1.
|
||||
safe-spot-search-vertical-range: 400
|
||||
web:
|
||||
github:
|
||||
# Toggle whether BentoBox can connect to GitHub to get data about updates and addons.
|
||||
|
Loading…
Reference in New Issue
Block a user