Rewrote chunk deletion (#648)

As of 1.14, chunk regeneration is no longer supported. This PR implements a not-chunk-based deletion that supports both 1.13 and 1.14 and which also allows us to get rid of the "multiple of 16" rule for island distances. This PR however does not remove the "multiple of 16" rule and a commit should be made thereafter.
This PR is also a pre-requisite to #640.

* Makes GameModes responsible for regenerating chunks.

* Deletes chunks manually to solve 1.14 chunk regen removal

* Fixes round up to 16 for island distance bug.

* Clean up - removing imports and stack traces

* Revert "Fixes round up to 16 for island distance bug."

This reverts commit 54f1ce0940.

* Adds island edge protection for deletion. Needs full testing.

* Completed testing. Works correctly.
This commit is contained in:
tastybento 2019-05-01 07:25:35 -07:00 committed by Florian CUNY
parent 95c0c612da
commit 650e370ffe
4 changed files with 165 additions and 47 deletions

View File

@ -123,10 +123,11 @@ public abstract class GameModeAddon extends Addon {
@NonNull
public abstract ChunkGenerator getDefaultWorldGenerator(String worldName, String id);
/**
* Tells the Game Mode Addon to save its settings. Used when world settings are changed
* in-game and need to be saved.
* @since 1.4.0
*/
public abstract void saveWorldSettings();
/**
* Tells the Game Mode Addon to save its settings. Used when world settings are changed
* in-game and need to be saved.
* @since 1.4.0
*/
public abstract void saveWorldSettings();
}

View File

@ -4,6 +4,8 @@ import java.util.UUID;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector;
import com.google.gson.annotations.Expose;
@ -32,24 +34,35 @@ public class IslandDeletion implements DataObject {
@Expose
private int maxZChunk;
@Expose
private int minX;
@Expose
private int minZ;
@Expose
private int maxX;
@Expose
private int maxZ;
@Expose
BoundingBox box;
public IslandDeletion() {}
public IslandDeletion(Island island) {
uniqueId = UUID.randomUUID().toString();
location = island.getCenter();
minXChunk = (location.getBlockX() - island.getMaxEverProtectionRange()) >> 4;
maxXChunk = (island.getMaxEverProtectionRange() + location.getBlockX() - 1) >> 4;
minZChunk = (location.getBlockZ() - island.getMaxEverProtectionRange()) >> 4;
maxZChunk = (island.getMaxEverProtectionRange() + location.getBlockZ() - 1) >> 4;
}
public IslandDeletion(Location location, int minXChunk, int maxXChunk, int minZChunk, int maxZChunk) {
this.uniqueId = UUID.randomUUID().toString();
this.location = location;
this.minXChunk = minXChunk;
this.maxXChunk = maxXChunk;
this.minZChunk = minZChunk;
this.maxZChunk = maxZChunk;
minX = location.getBlockX() - island.getMaxEverProtectionRange();
minXChunk = minX >> 4;
maxX = island.getMaxEverProtectionRange() + location.getBlockX();
maxXChunk = maxX >> 4;
minZ = location.getBlockZ() - island.getMaxEverProtectionRange();
minZChunk = minZ >> 4;
maxZ = island.getMaxEverProtectionRange() + location.getBlockZ();
maxZChunk = maxZ >> 4;
box = BoundingBox.of(new Vector(minX, 0, minZ), new Vector(maxX, 255, maxZ));
}
/* (non-Javadoc)
@ -170,9 +183,60 @@ public class IslandDeletion implements DataObject {
this.minZChunk = minZChunk;
}
public int getMinX() {
return minX;
}
public void setMinX(int minX) {
this.minX = minX;
}
public int getMinZ() {
return minZ;
}
public void setMinZ(int minZ) {
this.minZ = minZ;
}
public int getMaxX() {
return maxX;
}
public void setMaxX(int maxX) {
this.maxX = maxX;
}
public int getMaxZ() {
return maxZ;
}
public void setMaxZ(int maxZ) {
this.maxZ = maxZ;
}
@Override
public void setUniqueId(String uniqueId) {
this.uniqueId = uniqueId;
}
public boolean inBounds(int x, int z) {
return box.contains(new Vector(x, 0, z));
}
/**
* @return the box
*/
public BoundingBox getBox() {
return box;
}
/**
* @param box the box to set
*/
public void setBox(BoundingBox box) {
this.box = box;
}
}

View File

@ -1,15 +1,29 @@
package world.bentobox.bentobox.util;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.block.Biome;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.generator.ChunkGenerator.BiomeGrid;
import org.bukkit.generator.ChunkGenerator.ChunkData;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.Vector;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.events.island.IslandEvent;
import world.bentobox.bentobox.api.events.island.IslandEvent.Reason;
import world.bentobox.bentobox.database.objects.IslandDeletion;
/**
* Deletes islands fast using chunk regeneration
* Deletes islands chunk by chunk
*
* @author tastybento
*/
@ -19,39 +33,79 @@ public class DeleteIslandChunks {
* This is how many chunks per world will be done in one tick.
*/
private static final int SPEED = 5;
private int x;
private int z;
private int chunkX;
private int chunkZ;
private BukkitTask task;
private IslandDeletion di;
@SuppressWarnings({"deprecation", "squid:CallToDeprecatedMethod"})
public DeleteIslandChunks(BentoBox plugin, IslandDeletion di) {
// Fire event
IslandEvent.builder().deletedIslandInfo(di).reason(Reason.DELETE_CHUNKS).build();
x = di.getMinXChunk();
z = di.getMinZChunk();
this.chunkX = di.getMinXChunk();
this.chunkZ = di.getMinZChunk();
this.di = di;
// Run through all chunks of the islands and regenerate them.
task = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
for (int i = 0; i < SPEED; i++) {
// World#regenerateChunk(int, int) from Bukkit is deprecated because it may not regenerate decoration correctly
di.getWorld().regenerateChunk(x, z);
if (plugin.getIWM().isNetherGenerate(di.getWorld()) && plugin.getIWM().isNetherIslands(di.getWorld())) {
plugin.getIWM().getNetherWorld(di.getWorld()).regenerateChunk(x, z);
}
if (plugin.getIWM().isEndGenerate(di.getWorld()) && plugin.getIWM().isEndIslands(di.getWorld())) {
plugin.getIWM().getEndWorld(di.getWorld()).regenerateChunk(x, z);
}
z++;
if (z > di.getMaxZChunk()) {
z = di.getMinZChunk();
x++;
if (x > di.getMaxXChunk()) {
// We're done
task.cancel();
// Fire event
IslandEvent.builder().deletedIslandInfo(di).reason(Reason.DELETED).build();
plugin.getIWM().getAddon(di.getWorld()).ifPresent(gm -> {
regerateChunk(gm, di.getWorld().getChunkAt(chunkX, chunkZ));
if (plugin.getIWM().isNetherGenerate(di.getWorld()) && plugin.getIWM().isNetherIslands(di.getWorld())) {
regerateChunk(gm, plugin.getIWM().getNetherWorld(di.getWorld()).getChunkAt(chunkX, chunkZ));
}
}
if (plugin.getIWM().isEndGenerate(di.getWorld()) && plugin.getIWM().isEndIslands(di.getWorld())) {
regerateChunk(gm, plugin.getIWM().getEndWorld(di.getWorld()).getChunkAt(chunkX, chunkZ));
}
chunkZ++;
if (chunkZ > di.getMaxZChunk()) {
chunkZ = di.getMinZChunk();
chunkX++;
if (chunkX > di.getMaxXChunk()) {
// We're done
task.cancel();
// Fire event
IslandEvent.builder().deletedIslandInfo(di).reason(Reason.DELETED).build();
}
}
});
}
}, 0L, 1L);
}
private void regerateChunk(GameModeAddon gm, Chunk chunk) {
// Clear all inventories
Arrays.stream(chunk.getTileEntities()).filter(te -> (te instanceof InventoryHolder))
.filter(te -> di.inBounds(te.getLocation().getBlockX(), te.getLocation().getBlockZ()))
.forEach(te -> ((InventoryHolder)te).getInventory().clear());
// Reset blocks
MyBiomeGrid grid = new MyBiomeGrid();
ChunkData cd = gm.getDefaultWorldGenerator(chunk.getWorld().getName(), "").generateChunkData(chunk.getWorld(), new Random(), chunk.getX(), chunk.getZ(), grid);
int baseX = chunk.getX() << 4;
int baseZ = chunk.getZ() << 4;
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
if (di.inBounds(baseX + x, baseZ + z)) {
chunk.getBlock(x, 0, z).setBiome(grid.getBiome(x, z));
for (int y = 0; y < chunk.getWorld().getMaxHeight(); y++) {
chunk.getBlock(x, y, z).setBlockData(cd.getBlockData(x, y, z));
}
}
}
}
// Remove all entities in chunk, including any dropped items as a result of clearing the blocks above
Arrays.stream(chunk.getEntities()).filter(e -> !(e instanceof Player) && di.inBounds(e.getLocation().getBlockX(), e.getLocation().getBlockZ())).forEach(Entity::remove);
}
class MyBiomeGrid implements BiomeGrid {
Map<Vector, Biome> map = new HashMap<>();
@Override
public Biome getBiome(int x, int z) {
return map.getOrDefault(new Vector(x,0,z), Biome.PLAINS);
}
@Override
public void setBiome(int x, int z, Biome bio) {
map.put(new Vector(x,0,z), bio);
}
}
}

View File

@ -31,6 +31,7 @@ public class SafeSpotTeleport {
private static final int MAX_CHUNKS = 200;
private static final long SPEED = 1;
private static final int MAX_RADIUS = 200;
private static final int MAX_HEIGHT = 235;
private boolean checking;
private BukkitTask task;
@ -219,14 +220,12 @@ public class SafeSpotTeleport {
* @return true if a safe spot was found
*/
private boolean scanChunk(ChunkSnapshot chunk) {
// Max height
int maxHeight = location.getWorld().getMaxHeight() - 20;
// Run through the chunk
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, maxHeight)) {
for (int y = Math.min(chunk.getHighestBlockYAt(x, z), MAX_HEIGHT); y >= 0; y--) {
if (checkBlock(chunk, x,y,z, MAX_HEIGHT)) {
return true;
}
} // end y