diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md
index 5b69cda82..87c970593 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.md
+++ b/.github/ISSUE_TEMPLATE/bug-report.md
@@ -19,10 +19,12 @@ assignees: ''
+**/fawe debugpaste**:
+
**Required Information**
-- FAWE Version Number:
-- Spigot/Paper Version Number:
-- Minecraft Version: [e.g. 1.16.3]
+- FAWE Version Number (`/version FastAsyncWorldEdit`):
+- Spigot/Paper Version Number (`/version`):
+- Minecraft Version: [e.g. 1.16.3]
**Describe the bug**
A clear and concise description of what the bug is.
diff --git a/README.md b/README.md
index 2ef4aa97f..8dcc311d0 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
---
diff --git a/fawe-logo.png b/fawe-logo.png
new file mode 100644
index 000000000..ddf2f7021
Binary files /dev/null and b/fawe-logo.png differ
diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java
index c0d778e7b..9ffee2d2f 100644
--- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java
+++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java
@@ -89,10 +89,11 @@ public class BukkitServerInterface extends AbstractPlatform implements MultiUser
return BukkitRegistries.getInstance();
}
+ @SuppressWarnings("deprecation")
@Override
public int getDataVersion() {
if (plugin.getBukkitImplAdapter() != null) {
- return plugin.getBukkitImplAdapter().getDataVersion();
+ return Bukkit.getUnsafe().getDataVersion();
}
return -1;
}
diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java
index c88de2a76..f282901c1 100644
--- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java
+++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java
@@ -32,6 +32,7 @@ import com.sk89q.worldedit.blocks.BaseItem;
import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.entity.Player;
+import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.wna.WorldNativeAccess;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
@@ -42,6 +43,7 @@ import com.sk89q.worldedit.util.SideEffect;
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.util.TreeGenerator;
import com.sk89q.worldedit.world.AbstractWorld;
+import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockStateHolder;
@@ -202,61 +204,18 @@ public class BukkitWorld extends AbstractWorld {
}
@Override
- public boolean regenerate(Region region, EditSession editSession) {
+ public boolean regenerate(Region region, Extent extent, RegenOptions options) {
BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter();
try {
if (adapter != null) {
- return adapter.regenerate(getWorld(), region, editSession);
+ return adapter.regenerate(getWorld(), region, extent, options);
} else {
throw new UnsupportedOperationException("Missing BukkitImplAdapater for this version.");
}
} catch (Exception e) {
logger.warn("Regeneration via adapter failed.", e);
+ return false;
}
- /*
- BaseBlock[] history = new BaseBlock[16 * 16 * (getMaxY() + 1)];
-
- for (BlockVector2 chunk : region.getChunks()) {
- BlockVector3 min = BlockVector3.at(chunk.getBlockX() * 16, 0, chunk.getBlockZ() * 16);
-
- // First save all the blocks inside
- for (int x = 0; x < 16; ++x) {
- for (int y = 0; y < (getMaxY() + 1); ++y) {
- for (int z = 0; z < 16; ++z) {
- BlockVector3 pt = min.add(x, y, z);
- int index = y * 16 * 16 + z * 16 + x;
- history[index] = editSession.getFullBlock(pt);
- }
- }
- }
-
- try {
- getWorld().regenerateChunk(chunk.getBlockX(), chunk.getBlockZ());
- } catch (Throwable t) {
- logger.warn("Chunk generation via Bukkit raised an error", t);
- }
-
- // Then restore
- for (int x = 0; x < 16; ++x) {
- for (int y = 0; y < (getMaxY() + 1); ++y) {
- for (int z = 0; z < 16; ++z) {
- BlockVector3 pt = min.add(x, y, z);
- int index = y * 16 * 16 + z * 16 + x;
-
- // We have to restore the block if it was outside
- if (!region.contains(pt)) {
- editSession.smartSetBlock(pt, history[index]);
- } else { // Otherwise fool with history
- editSession.getChangeSet().add(new BlockChange(pt, history[index], editSession.getFullBlock(pt)));
- }
- }
- }
- }
- }
-
- return true;
- */
- return editSession.regenerate(region);
}
/**
diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java
index f94dc5e12..cd3e86499 100644
--- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java
+++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java
@@ -25,11 +25,11 @@ import com.boydti.fawe.beta.implementation.packet.ChunkPacket;
import com.boydti.fawe.bukkit.FaweBukkit;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.Tag;
-import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.blocks.BaseItem;
import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.entity.BaseEntity;
+import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.wna.WorldNativeAccess;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region;
@@ -37,6 +37,7 @@ import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.util.Direction;
import com.sk89q.worldedit.util.SideEffect;
import com.sk89q.worldedit.world.DataFixer;
+import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
@@ -236,11 +237,12 @@ public interface BukkitImplAdapter extends IBukkitAdapter {
* Regenerate a region in the given world, so it appears "as new".
* @param world the world to regen in
* @param region the region to regen
- * @param session the session to use for setting blocks
+ * @param extent the extent to use for setting blocks
+ * @param options the regeneration options
* @return true on success, false on failure
*/
- default boolean regenerate(org.bukkit.World world, Region region, EditSession session) {
- return session.regenerate(region);
+ default boolean regenerate(World world, Region region, Extent extent, RegenOptions options) throws Exception{
+ throw new UnsupportedOperationException("This adapter does not support regeneration.");
}
default IChunkGet get(World world, int chunkX, int chunkZ) {
diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/Regenerator.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/Regenerator.java
new file mode 100644
index 000000000..cbf099142
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/Regenerator.java
@@ -0,0 +1,545 @@
+package com.sk89q.worldedit.bukkit.adapter;
+
+import com.boydti.fawe.beta.IChunkCache;
+import com.boydti.fawe.beta.IChunkGet;
+import com.boydti.fawe.beta.implementation.queue.SingleThreadQueueExtent;
+import com.boydti.fawe.config.Settings;
+import com.boydti.fawe.util.MathMan;
+import com.sk89q.worldedit.extent.Extent;
+import com.sk89q.worldedit.math.BlockVector2;
+import com.sk89q.worldedit.math.BlockVector3;
+import com.sk89q.worldedit.regions.CuboidRegion;
+import com.sk89q.worldedit.regions.Region;
+import com.sk89q.worldedit.world.RegenOptions;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.stream.Collectors;
+import org.bukkit.generator.BlockPopulator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Represents an abstract regeneration handler.
+ * @param the type of the {@Code IChunkAccess} of the current Minecraft implementation
+ * @param the type of the {@Code ProtoChunk} of the current Minecraft implementation
+ * @param the type of the {@Code Chunk} of the current Minecraft implementation
+ * @param the type of the {@Code ChunkStatusWrapper} wrapping the {@Code ChunkStatus} enum
+ */
+public abstract class Regenerator> {
+
+ public static final Logger logger = LoggerFactory.getLogger(Regenerator.class);
+
+ protected final org.bukkit.World originalBukkitWorld;
+ protected final Region region;
+ protected final Extent target;
+ protected final RegenOptions options;
+
+ //runtime
+ protected final Map chunkStati = new LinkedHashMap<>();
+ protected boolean generateConcurrent = true;
+ protected long seed;
+
+ private final Long2ObjectLinkedOpenHashMap protoChunks = new Long2ObjectLinkedOpenHashMap<>();
+ private final Long2ObjectOpenHashMap chunks = new Long2ObjectOpenHashMap<>();
+ private ExecutorService executor;
+ private SingleThreadQueueExtent source;
+
+ /**
+ * Initializes an abstract regeneration handler.
+ * @param originalBukkitWorld the Bukkit world containing all the information on how to regenerate the {code Region}
+ * @param region the selection to regenerate
+ * @param target the target {@code Extent} to paste the regenerated blocks into
+ * @param options the options to used while regenerating and pasting into the target {@code Extent}
+ */
+ public Regenerator(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) {
+ this.originalBukkitWorld = originalBukkitWorld;
+ this.region = region;
+ this.target = target;
+ this.options = options;
+ }
+
+ /**
+ * Regenerates the selected {@code Region}.
+ * @return whether or not the regeneration process was successful
+ * @throws Exception when something goes terribly wrong
+ */
+ public boolean regenerate() throws Exception {
+ if (!prepare()) {
+ return false;
+ }
+
+ try {
+ if (!initNewWorld()) {
+ cleanup0();
+ return false;
+ }
+ } catch (Exception e) {
+ cleanup0();
+ throw e;
+ }
+
+ try {
+ if (!generate()) {
+ cleanup0();
+ return false;
+ }
+ } catch (Exception e) {
+ cleanup0();
+ throw e;
+ }
+
+ try {
+ copyToWorld();
+ } catch (Exception e) {
+ cleanup0();
+ throw e;
+ }
+
+ cleanup0();
+ return true;
+ }
+
+ /**
+ * Returns the {@code ProtoChunk} at the given chunk coordinates.
+ * @param x the chunk x coordinate
+ * @param z the chunk z coordinate
+ * @return the {@code ProtoChunk} at the given chunk coordinates or null if it is not part of the regeneration process or has not been initialized yet.
+ */
+ protected ProtoChunk getProtoChunkAt(int x, int z) {
+ return protoChunks.get(MathMan.pairInt(x, z));
+ }
+
+ /**
+ * Returns the {@code Chunk} at the given chunk coordinates.
+ * @param x the chunk x coordinate
+ * @param z the chunk z coordinate
+ * @return the {@code Chunk} at the given chunk coordinates or null if it is not part of the regeneration process or has not been converted yet.
+ */
+ protected Chunk getChunkAt(int x, int z) {
+ return chunks.get(MathMan.pairInt(x, z));
+ }
+
+ private boolean generate() throws Exception {
+ if (generateConcurrent) {
+ //Using concurrent chunk generation
+ executor = Executors.newFixedThreadPool(Settings.IMP.QUEUE.PARALLEL_THREADS);
+ } // else using sequential chunk generation, concurrent not supported
+
+ //TODO: can we get that required radius down without affecting chunk generation (e.g. strucures, features, ...)?
+ //for now it is working well and fast, if we are bored in the future we could do the research (a lot of it) to reduce the border radius
+
+ //generate chunk coords lists with a certain radius
+ Int2ObjectOpenHashMap> chunkCoordsForRadius = new Int2ObjectOpenHashMap<>();
+ chunkStati.keySet().stream().map(ChunkStatusWrapper::requiredNeigborChunkRadius0).distinct().forEach(radius -> {
+ if (radius == -1) //ignore ChunkStatus.EMPTY
+ return;
+ int border = 16 - radius; //9 = 8 + 1, 8: max border radius used in chunk stages, 1: need 1 extra chunk for chunk features to generate at the border of the region
+ chunkCoordsForRadius.put(radius, getChunkCoordsRegen(region, border));
+ });
+
+ //create chunks
+ for (Long xz : chunkCoordsForRadius.get(0)) {
+ ProtoChunk chunk = createProtoChunk(MathMan.unpairIntX(xz), MathMan.unpairIntY(xz));
+ protoChunks.put(xz, chunk);
+ }
+
+ //generate lists for RegionLimitedWorldAccess, need to be square with odd length (e.g. 17x17), 17 = 1 middle chunk + 8 border chunks * 2
+ Int2ObjectOpenHashMap>> worldlimits = new Int2ObjectOpenHashMap<>();
+ chunkStati.keySet().stream().map(ChunkStatusWrapper::requiredNeigborChunkRadius0).distinct().forEach(radius -> {
+ if (radius == -1) //ignore ChunkStatus.EMPTY
+ return;
+ Long2ObjectOpenHashMap> map = new Long2ObjectOpenHashMap<>();
+ for (Long xz : chunkCoordsForRadius.get(radius)) {
+ int x = MathMan.unpairIntX(xz);
+ int z = MathMan.unpairIntY(xz);
+ List l = new ArrayList<>((radius + 1 + radius) * (radius + 1 + radius));
+ for (int zz = z - radius; zz <= z + radius; zz++) { //order is important, first z then x
+ for (int xx = x - radius; xx <= x + radius; xx++) {
+ l.add(protoChunks.get(MathMan.pairInt(xx, zz)));
+ }
+ }
+ map.put(xz, l);
+ }
+ worldlimits.put(radius, map);
+ });
+
+ //run generation tasks exluding FULL chunk status
+ for (Map.Entry entry : chunkStati.entrySet()) {
+ ChunkStatus chunkStatus = entry.getKey();
+ int radius = chunkStatus.requiredNeigborChunkRadius0();
+
+ List coords = chunkCoordsForRadius.get(radius);
+ if (this.generateConcurrent && entry.getValue() == Concurrency.RADIUS) {
+ SequentialTasks>> tasks = getChunkStatusTaskRows(coords, radius);
+ for (ConcurrentTasks> para : tasks) {
+ List scheduled = new ArrayList<>(tasks.size());
+ for (SequentialTasks row : para) {
+ scheduled.add((Callable) () -> {
+ for (Long xz : row) {
+ chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz));
+ }
+ return null;
+ });
+ }
+ try {
+ List futures = executor.invokeAll(scheduled);
+ for (Future future : futures) {
+ future.get();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ } else if (this.generateConcurrent && entry.getValue() == Concurrency.FULL) {
+ // every chunk can be processed individually
+ List scheduled = new ArrayList(coords.size());
+ for (long xz : coords) {
+ scheduled.add((Callable) () -> {
+ chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz));
+ return null;
+ });
+ }
+ try {
+ List futures = executor.invokeAll(scheduled);
+ for (Future future : futures) {
+ future.get();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } else { // Concurrency.NONE or generateConcurrent == false
+ // run sequential
+ for (long xz : coords) {
+ chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz));
+ }
+ }
+ }
+
+ //convert to proper chunks
+ for (Long xz : chunkCoordsForRadius.get(0)) {
+ ProtoChunk proto = protoChunks.get(xz);
+ chunks.put(xz, createChunk(proto));
+ }
+
+ //final chunkstatus
+ ChunkStatus FULL = getFullChunkStatus();
+ for (Long xz : chunkCoordsForRadius.get(0)) { //FULL.requiredNeighbourChunkRadius() == 0!
+ Chunk chunk = chunks.get(xz);
+ FULL.processChunkSave(xz, Arrays.asList(chunk));
+ }
+
+ //populate
+ List populators = getBlockPopulators();
+ for (Long xz : chunkCoordsForRadius.get(0)) {
+ int x = MathMan.unpairIntX(xz);
+ int z = MathMan.unpairIntY(xz);
+
+ //prepare chunk seed
+ Random random = getChunkRandom(seed, x, z);
+
+ //actually populate
+ Chunk c = chunks.get(xz);
+ populators.forEach(pop -> {
+ populate(c, random, pop);
+ });
+ }
+
+ source = new SingleThreadQueueExtent();
+ source.init(null, initSourceQueueCache(), null);
+ return true;
+ }
+
+ private void copyToWorld() {
+ //Setting Blocks
+ long start = System.currentTimeMillis();
+ boolean genbiomes = options.shouldRegenBiomes();
+ for (BlockVector3 vec : region) {
+ target.setBlock(vec, source.getBlock(vec));
+ if (genbiomes) {
+ target.setBiome(vec, source.getBiome(vec));
+ }
+// realExtent.setSkyLight(vec, extent.getSkyLight(vec));
+// realExtent.setBlockLight(vec, extent.getBrightness(vec));
+ }
+ }
+
+ private void cleanup0() {
+ if (executor != null) {
+ executor.shutdownNow();
+ }
+ cleanup();
+ }
+
+ //functions to be implemented by sub class
+ /**
+ * Implement the preparation process in here. DO NOT instanciate any variable here that require the cleanup function. This function is for gathering further information before initializing a new
+ * world.
+ *
+ * Fields required to be initialized: chunkStati, seed
+ * For chunkStati also see {code ChunkStatusWrapper}.
+ *
+ * @return whether or not the preparation process was successful
+ */
+ protected abstract boolean prepare();
+
+ /**
+ * Implement the creation of the seperate world in here.
+ *
+ * Fields required to be initialized: generateConcurrent
+ *
+ * @return true if everything went fine, otherwise false. When false is returned the Regenerator halts the regeneration process and calls the cleanup function.
+ * @throws java.lang.Exception When the implementation of this method throws and exception the Regenerator halts the regeneration process and calls the cleanup function.
+ */
+ protected abstract boolean initNewWorld() throws Exception;
+
+ /**
+ * Implement the cleanup of all the mess that is created during the regeneration process (initNewWorld() and generate()).This function must not throw any exceptions.
+ */
+ protected abstract void cleanup();
+
+ //functions to implement by sub class - regenate related
+ /**
+ * Implement the initialization of a {@code ProtoChunk} here.
+ *
+ * @param x the x coorinate of the {@code ProtoChunk} to create
+ * @param z the z coorinate of the {@code ProtoChunk} to create
+ * @return an initialized {@code ProtoChunk}
+ */
+ protected abstract ProtoChunk createProtoChunk(int x, int z);
+
+ /**
+ * Implement the convertion of a {@code ProtoChunk} to a {@code Chunk} here.
+ *
+ * @param protoChunk the {@code ProtoChunk} to be converted to a {@code Chunk}
+ * @return the converted {@code Chunk}
+ */
+ protected abstract Chunk createChunk(ProtoChunk protoChunk);
+
+ /**
+ * Return the {@code ChunkStatus.FULL} here.
+ * ChunkStatus.FULL is the last step of vanilla chunk generation.
+ *
+ * @return {@code ChunkStatus.FULL}
+ */
+ protected abstract ChunkStatus getFullChunkStatus();
+
+ /**
+ * Return a list of {@code BlockPopulator} used to populate the original world here.
+ *
+ * @return {@code ChunkStatus.FULL}
+ */
+ protected abstract List getBlockPopulators();
+
+ /**
+ * Implement the population of the {@code Chunk} with the given chunk random and {@code BlockPopulator} here.
+ *
+ * @param chunk the {@code Chunk} to populate
+ * @param random the chunk random to use for population
+ * @param pop the {@code BlockPopulator} to use
+ */
+ protected abstract void populate(Chunk chunk, Random random, BlockPopulator pop);
+
+ /**
+ * Implement the initialization an {@code IChunkCache} here. Use will need the {@code getChunkAt} function
+ * @return an initialized {@code IChunkCache}
+ */
+ protected abstract IChunkCache initSourceQueueCache();
+
+ //algorithms
+ private List getChunkCoordsRegen(Region region, int border) { //needs to be square num of chunks
+ BlockVector3 oldMin = region.getMinimumPoint();
+ BlockVector3 newMin = BlockVector3.at((oldMin.getX() >> 4 << 4) - border * 16, oldMin.getY(), (oldMin.getZ() >> 4 << 4) - border * 16);
+ BlockVector3 oldMax = region.getMaximumPoint();
+ BlockVector3 newMax = BlockVector3.at((oldMax.getX() >> 4 << 4) + (border + 1) * 16 - 1, oldMax.getY(), (oldMax.getZ() >> 4 << 4) + (border + 1) * 16 - 1);
+ Region adjustedRegion = new CuboidRegion(newMin, newMax);
+ return adjustedRegion.getChunks().stream()
+ .map(c -> BlockVector2.at(c.getX(), c.getZ()))
+ .sorted(Comparator.comparingInt(c -> c.getZ()).thenComparingInt(c -> c.getX())) //needed for RegionLimitedWorldAccess
+ .map(c -> MathMan.pairInt(c.getX(), c.getZ()))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Creates a list of chunkcoord rows that may be executed concurrently
+ *
+ * @param allcoords the coords that should be sorted into rows, must be sorted by z and x
+ * @param requiredNeighborChunkRadius the radius of neighbor chunks that may not be written to conccurently (ChunkStatus.requiredNeighborRadius)
+ * @return a list of chunkcoords rows that may be executed concurrently
+ */
+ private SequentialTasks>> getChunkStatusTaskRows(List allcoords, int requiredNeighborChunkRadius) {
+ int requiredneighbors = Math.max(0, requiredNeighborChunkRadius);
+
+ int minx = allcoords.isEmpty() ? 0 : MathMan.unpairIntX(allcoords.get(0));
+ int maxx = allcoords.isEmpty() ? 0 : MathMan.unpairIntX(allcoords.get(allcoords.size() - 1));
+ int minz = allcoords.isEmpty() ? 0 : MathMan.unpairIntY(allcoords.get(0));
+ int maxz = allcoords.isEmpty() ? 0 : MathMan.unpairIntY(allcoords.get(allcoords.size() - 1));
+ SequentialTasks>> tasks;
+ if (maxz - minz > maxx - minx) {
+ int numlists = Math.min(requiredneighbors * 2 + 1, maxx - minx + 1);
+
+ Int2ObjectOpenHashMap> byx = new Int2ObjectOpenHashMap();
+ int expectedListLength = (allcoords.size() + 1) / (maxx - minx);
+
+ //init lists
+ for (int i = minx; i <= maxx; i++) {
+ byx.put(i, new SequentialTasks(expectedListLength));
+ }
+
+ //sort into lists by x coord
+ for (Long xz : allcoords) {
+ byx.get(MathMan.unpairIntX(xz)).add(xz);
+ }
+
+ //create parallel tasks
+ tasks = new SequentialTasks(numlists);
+ for (int offset = 0; offset < numlists; offset++) {
+ ConcurrentTasks> para = new ConcurrentTasks((maxz - minz + 1) / numlists + 1);
+ for (int i = 0; minx + i * numlists + offset <= maxx; i++)
+ para.add(byx.get(minx + i * numlists + offset));
+ tasks.add(para);
+ }
+ } else {
+ int numlists = Math.min(requiredneighbors * 2 + 1, maxz - minz + 1);
+
+ Int2ObjectOpenHashMap> byz = new Int2ObjectOpenHashMap();
+ int expectedListLength = (allcoords.size() + 1) / (maxz - minz);
+
+ //init lists
+ for (int i = minz; i <= maxz; i++) {
+ byz.put(i, new SequentialTasks(expectedListLength));
+ }
+
+ //sort into lists by x coord
+ for (Long xz : allcoords) {
+ byz.get(MathMan.unpairIntY(xz)).add(xz);
+ }
+
+ //create parallel tasks
+ tasks = new SequentialTasks(numlists);
+ for (int offset = 0; offset < numlists; offset++) {
+ ConcurrentTasks> para = new ConcurrentTasks((maxx - minx + 1) / numlists + 1);
+ for (int i = 0; minz + i * numlists + offset <= maxz; i++)
+ para.add(byz.get(minz + i * numlists + offset));
+ tasks.add(para);
+ }
+ }
+
+ return tasks;
+ }
+
+ private static Random getChunkRandom(long worldseed, int x, int z) {
+ Random random = new Random();
+ random.setSeed(worldseed);
+ long xRand = random.nextLong() / 2L * 2L + 1L;
+ long zRand = random.nextLong() / 2L * 2L + 1L;
+ random.setSeed((long) x * xRand + (long) z * zRand ^ worldseed);
+ return random;
+ }
+
+ //classes
+ /**
+ * This class is used to wrap the ChunkStatus of the current Minecraft implementation and as the implementation to execute a chunk generation step.
+ * @param the IChunkAccess class of the current Minecraft implementation
+ */
+ public static abstract class ChunkStatusWrapper {
+
+ /**
+ * Return the required neighbor chunk radius the wrapped {@code ChunkStatus} requires.
+ *
+ * @return the radius of required neighbor chunks
+ */
+ public abstract int requiredNeigborChunkRadius();
+
+ int requiredNeigborChunkRadius0() {
+ return Math.max(0, requiredNeigborChunkRadius());
+ }
+
+ /**
+ * Return the name of the wrapped {@code ChunkStatus}.
+ *
+ * @return the radius of required neighbor chunks
+ */
+ public abstract String name();
+
+ /**
+ * Return the name of the wrapped {@code ChunkStatus}.
+ *
+ * @param xz represents the chunk coordinates of the chunk to process as denoted by {@code MathMan}
+ * @param accessibleChunks a list of chunks that will be used during the execution of the wrapped {@code ChunkStatus}.
+ * This list is order in the correct order required by the {@code ChunkStatus}, unless Mojang suddenly decides to do things differently.
+ */
+ public abstract void processChunk(Long xz, List accessibleChunks);
+
+ void processChunkSave(Long xz, List accessibleChunks) {
+ try {
+ processChunk(xz, accessibleChunks);
+ } catch (Exception e) {
+ logger.error("Error while running " + name() + " on chunk " + MathMan.unpairIntX(xz) + "/" + MathMan.unpairIntY(xz), e);
+ }
+ }
+ }
+
+ public enum Concurrency {
+ FULL,
+ RADIUS,
+ NONE
+ }
+
+ public static class SequentialTasks extends Tasks {
+
+ public SequentialTasks(int expectedsize) {
+ super(expectedsize);
+ }
+ }
+
+ public static class ConcurrentTasks extends Tasks {
+
+ public ConcurrentTasks(int expectedsize) {
+ super(expectedsize);
+ }
+ }
+
+ public static class Tasks implements Iterable {
+
+ private final List tasks;
+
+ public Tasks(int expectedsize) {
+ tasks = new ArrayList(expectedsize);
+ }
+
+ public void add(T task) {
+ tasks.add(task);
+ }
+
+ public List list() {
+ return tasks;
+ }
+
+ public int size() {
+ return tasks.size();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return tasks.iterator();
+ }
+
+ @Override
+ public String toString() {
+ return tasks.toString();
+ }
+ }
+}
diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java
index 232e22c51..e97f894cd 100644
--- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java
+++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java
@@ -22,36 +22,33 @@ package com.sk89q.worldedit.bukkit.adapter.impl;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.beta.IChunkGet;
-import com.boydti.fawe.beta.IQueueChunk;
-import com.boydti.fawe.beta.IQueueExtent;
import com.boydti.fawe.beta.implementation.packet.ChunkPacket;
-import com.boydti.fawe.beta.implementation.queue.SingleThreadQueueExtent;
import com.boydti.fawe.bukkit.adapter.mc1_15_2.BlockMaterial_1_15_2;
import com.boydti.fawe.bukkit.adapter.mc1_15_2.BukkitAdapter_1_15_2;
import com.boydti.fawe.bukkit.adapter.mc1_15_2.BukkitGetBlocks_1_15_2;
import com.boydti.fawe.bukkit.adapter.mc1_15_2.FAWEWorldNativeAccess_1_15_2;
import com.boydti.fawe.bukkit.adapter.mc1_15_2.MapChunkUtil_1_15_2;
import com.boydti.fawe.bukkit.adapter.mc1_15_2.nbt.LazyCompoundTag_1_15_2;
-import com.google.common.io.Files;
+import com.google.common.base.Preconditions;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
-import com.sk89q.worldedit.EditSession;
-import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.blocks.TileEntityBlock;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.bukkit.adapter.CachedBukkitAdapter;
import com.sk89q.worldedit.bukkit.adapter.IDelegateBukkitImplAdapter;
+import com.sk89q.worldedit.bukkit.adapter.impl.regen.Regen_v1_15_R2;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.LazyBaseEntity;
+import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.wna.WorldNativeAccess;
-import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.util.SideEffect;
import com.sk89q.worldedit.util.SideEffectSet;
+import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
@@ -66,7 +63,6 @@ import net.minecraft.server.v1_15_R1.Block;
import net.minecraft.server.v1_15_R1.BlockPosition;
import net.minecraft.server.v1_15_R1.Chunk;
import net.minecraft.server.v1_15_R1.ChunkCoordIntPair;
-import net.minecraft.server.v1_15_R1.ChunkProviderServer;
import net.minecraft.server.v1_15_R1.ChunkSection;
import net.minecraft.server.v1_15_R1.Entity;
import net.minecraft.server.v1_15_R1.EntityPlayer;
@@ -75,7 +71,6 @@ import net.minecraft.server.v1_15_R1.IBlockData;
import net.minecraft.server.v1_15_R1.IRegistry;
import net.minecraft.server.v1_15_R1.ItemStack;
import net.minecraft.server.v1_15_R1.MinecraftKey;
-import net.minecraft.server.v1_15_R1.MinecraftServer;
import net.minecraft.server.v1_15_R1.NBTBase;
import net.minecraft.server.v1_15_R1.NBTTagCompound;
import net.minecraft.server.v1_15_R1.NBTTagInt;
@@ -83,12 +78,9 @@ import net.minecraft.server.v1_15_R1.PacketPlayOutMapChunk;
import net.minecraft.server.v1_15_R1.PlayerChunk;
import net.minecraft.server.v1_15_R1.TileEntity;
import net.minecraft.server.v1_15_R1.World;
-import net.minecraft.server.v1_15_R1.WorldData;
-import net.minecraft.server.v1_15_R1.WorldNBTStorage;
import net.minecraft.server.v1_15_R1.WorldServer;
import org.bukkit.Bukkit;
import org.bukkit.Location;
-import org.bukkit.World.Environment;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_15_R1.CraftChunk;
import org.bukkit.craftbukkit.v1_15_R1.CraftWorld;
@@ -98,17 +90,11 @@ import org.bukkit.craftbukkit.v1_15_R1.entity.CraftEntity;
import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftItemStack;
import org.bukkit.entity.Player;
-import org.bukkit.generator.ChunkGenerator;
-import java.io.File;
-import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.OptionalInt;
import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
import java.util.function.Supplier;
import java.util.stream.Stream;
import javax.annotation.Nullable;
@@ -119,7 +105,6 @@ import static org.slf4j.LoggerFactory.getLogger;
public final class FAWE_Spigot_v1_15_R2 extends CachedBukkitAdapter implements IDelegateBukkitImplAdapter {
private final Spigot_v1_15_R2 parent;
private char[] ibdToStateOrdinal;
-
// ------------------------------------------------------------------------
// Code that may break between versions of Minecraft
// ------------------------------------------------------------------------
@@ -157,8 +142,6 @@ public final class FAWE_Spigot_v1_15_R2 extends CachedBukkitAdapter implements I
public BlockMaterial getMaterial(BlockState state) {
IBlockData bs = ((CraftBlockData) Bukkit.createBlockData(state.getAsString())).getState();
return new BlockMaterial_1_15_2(bs.getBlock(), bs);
-
-
}
public Block getBlock(BlockType blockType) {
@@ -168,7 +151,7 @@ public final class FAWE_Spigot_v1_15_R2 extends CachedBukkitAdapter implements I
@SuppressWarnings("deprecation")
@Override
public BaseBlock getBlock(Location location) {
- checkNotNull(location);
+ Preconditions.checkNotNull(location);
CraftWorld craftWorld = ((CraftWorld) location.getWorld());
int x = location.getBlockX();
@@ -272,7 +255,7 @@ public final class FAWE_Spigot_v1_15_R2 extends CachedBukkitAdapter implements I
@Override
public BaseEntity getEntity(org.bukkit.entity.Entity entity) {
- checkNotNull(entity);
+ Preconditions.checkNotNull(entity);
CraftEntity craftEntity = ((CraftEntity) entity);
Entity mcEntity = craftEntity.getHandle();
@@ -417,83 +400,9 @@ public final class FAWE_Spigot_v1_15_R2 extends CachedBukkitAdapter implements I
}
return parent.fromNative(foreign);
}
-
@Override
- public boolean regenerate(org.bukkit.World world, Region region, EditSession editSession) {
- WorldServer originalWorld = ((CraftWorld) world).getHandle();
- ChunkProviderServer provider = originalWorld.getChunkProvider();
- if (!(provider instanceof ChunkProviderServer)) {
- return false;
- }
-
- File saveFolder = Files.createTempDir();
- // register this just in case something goes wrong
- // normally it should be deleted at the end of this method
- saveFolder.deleteOnExit();
- try {
- MinecraftServer server = originalWorld.getServer().getServer();
- WorldNBTStorage originalDataManager = originalWorld.getDataManager();
- WorldNBTStorage saveHandler = new WorldNBTStorage(saveFolder, originalDataManager.getDirectory().getName(), server, originalDataManager.getDataFixer());
- WorldData newWorldData = new WorldData(originalWorld.worldData.a((NBTTagCompound) null),
- server.dataConverterManager, getDataVersion(), null);
- newWorldData.setName(UUID.randomUUID().toString());
-
- ChunkGenerator gen = world.getGenerator();
- Environment env = world.getEnvironment();
- try (WorldServer freshWorld = new WorldServer(server,
- server.executorService, saveHandler,
- newWorldData,
- originalWorld.worldProvider.getDimensionManager(),
- originalWorld.getMethodProfiler(),
- server.worldLoadListenerFactory.create(11),
- env,
- gen) {
- @Override
- public boolean addEntityChunk(net.minecraft.server.v1_15_R1.Entity entity) {
- //Fixes #320; Prevent adding entities so we aren't attempting to spawn them asynchronously
- return false;
- }
- }) {
-
- // Pre-gen all the chunks
- // We need to also pull one more chunk in every direction
- Fawe.get().getQueueHandler().startSet(true);
- try {
- IQueueExtent extent = new SingleThreadQueueExtent();
- extent.init(null, (x, z) -> new BukkitGetBlocks_1_15_2(freshWorld, x, z) {
- @Override
- public Chunk ensureLoaded(World nmsWorld, int chunkX, int chunkZ) {
- Chunk cached = nmsWorld.getChunkIfLoaded(chunkX, chunkZ);
- if (cached != null) {
- return cached;
- }
- Future future = Fawe.get().getQueueHandler().sync((Supplier) () -> freshWorld.getChunkAt(chunkX, chunkZ));
- while (!future.isDone()) {
- // this feels so dirty
- freshWorld.getChunkProvider().runTasks();
- }
- try {
- return future.get();
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
- }
- }
- }, null);
- for (BlockVector3 vec : region) {
- editSession.setBlock(vec, extent.getFullBlock(vec));
- }
- } finally {
- Fawe.get().getQueueHandler().endSet(true);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- } catch (MaxChangedBlocksException e) {
- throw new RuntimeException(e);
- } finally {
- saveFolder.delete();
- }
- return true;
+ public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception {
+ return new Regen_v1_15_R2(bukkitWorld, region, target, options).regenerate();
}
@Override
diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R1.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R1.java
index 44e42d099..cda1690cd 100644
--- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R1.java
+++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R1.java
@@ -28,23 +28,26 @@ import com.boydti.fawe.bukkit.adapter.mc1_16_1.BukkitGetBlocks_1_16_1;
import com.boydti.fawe.bukkit.adapter.mc1_16_1.FAWEWorldNativeAccess_1_16;
import com.boydti.fawe.bukkit.adapter.mc1_16_1.MapChunkUtil_1_16_1;
import com.boydti.fawe.bukkit.adapter.mc1_16_1.nbt.LazyCompoundTag_1_16_1;
+import com.google.common.base.Preconditions;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
-import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.blocks.TileEntityBlock;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.bukkit.adapter.CachedBukkitAdapter;
import com.sk89q.worldedit.bukkit.adapter.IDelegateBukkitImplAdapter;
+import com.sk89q.worldedit.bukkit.adapter.impl.regen.Regen_v1_16_R1;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.LazyBaseEntity;
+import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.wna.WorldNativeAccess;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.util.SideEffect;
import com.sk89q.worldedit.util.SideEffectSet;
+import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
@@ -101,7 +104,6 @@ import static org.slf4j.LoggerFactory.getLogger;
public final class FAWE_Spigot_v1_16_R1 extends CachedBukkitAdapter implements IDelegateBukkitImplAdapter {
private final Spigot_v1_16_R1 parent;
private char[] ibdToStateOrdinal;
-
// ------------------------------------------------------------------------
// Code that may break between versions of Minecraft
// ------------------------------------------------------------------------
@@ -139,8 +141,6 @@ public final class FAWE_Spigot_v1_16_R1 extends CachedBukkitAdapter implements I
public BlockMaterial getMaterial(BlockState state) {
IBlockData bs = ((CraftBlockData) Bukkit.createBlockData(state.getAsString())).getState();
return new BlockMaterial_1_16_1(bs.getBlock(), bs);
-
-
}
public Block getBlock(BlockType blockType) {
@@ -150,7 +150,7 @@ public final class FAWE_Spigot_v1_16_R1 extends CachedBukkitAdapter implements I
@SuppressWarnings("deprecation")
@Override
public BaseBlock getBlock(Location location) {
- checkNotNull(location);
+ Preconditions.checkNotNull(location);
CraftWorld craftWorld = ((CraftWorld) location.getWorld());
int x = location.getBlockX();
@@ -254,7 +254,7 @@ public final class FAWE_Spigot_v1_16_R1 extends CachedBukkitAdapter implements I
@Override
public BaseEntity getEntity(org.bukkit.entity.Entity entity) {
- checkNotNull(entity);
+ Preconditions.checkNotNull(entity);
CraftEntity craftEntity = ((CraftEntity) entity);
Entity mcEntity = craftEntity.getHandle();
@@ -401,81 +401,8 @@ public final class FAWE_Spigot_v1_16_R1 extends CachedBukkitAdapter implements I
}
@Override
- public boolean regenerate(org.bukkit.World world, Region region, EditSession editSession) {
-// WorldServer originalWorld = ((CraftWorld) world).getHandle();
-// ChunkProviderServer provider = originalWorld.getChunkProvider();
-// if (!(provider instanceof ChunkProviderServer)) {
-// return false;
-// }
-//
-// File saveFolder = Files.createTempDir();
-// // register this just in case something goes wrong
-// // normally it should be deleted at the end of this method
-// saveFolder.deleteOnExit();
-// try {
-// MinecraftServer server = originalWorld.getServer().getServer();
-// Convertable.ConversionSession originalDataManager = server.convertable;
-//// Convertable.ConversionSession saveHandler = new Convertable.ConversionSession(world.getName(), world.);
-// WorldData newWorldData = new WorldData(originalWorld.worldData.a((NBTTagCompound) null),
-// server.dataConverterManager, getDataVersion(), null);
-// newWorldData.setName(UUID.randomUUID().toString());
-//
-// ChunkGenerator gen = world.getGenerator();
-// Environment env = world.getEnvironment();
-// try (WorldServer freshWorld = new WorldServer(server,
-// server.executorService, originalDataManager,
-// newWorldData,
-// originalWorld.worldProvider.getDimensionManager(),
-// originalWorld.getMethodProfiler(),
-// server.worldLoadListenerFactory.create(11),
-// env,
-// gen) {
-// @Override
-// public boolean addEntityChunk(Entity entity) {
-// //Fixes #320; Prevent adding entities so we aren't attempting to spawn them asynchronously
-// return false;
-// }
-// }) {
-//
-// // Pre-gen all the chunks
-// // We need to also pull one more chunk in every direction
-// Fawe.get().getQueueHandler().startSet(true);
-// try {
-// IQueueExtent extent = new SingleThreadQueueExtent();
-// extent.init(null, (x, z) -> new BukkitGetBlocks_1_16_1(freshWorld, x, z) {
-// @Override
-// public Chunk ensureLoaded(World nmsWorld, int X, int Z) {
-// Chunk cached = nmsWorld.getChunkIfLoaded(X, Z);
-// if (cached != null) return cached;
-// Future future = Fawe.get().getQueueHandler().sync((Supplier) () -> freshWorld.getChunkAt(X, Z));
-// while (!future.isDone()) {
-// // this feels so dirty
-// freshWorld.getChunkProvider().runTasks();
-// }
-// try {
-// return future.get();
-// } catch (InterruptedException | ExecutionException e) {
-// throw new RuntimeException(e);
-// }
-// }
-// }, null);
-// for (BlockVector3 vec : region) {
-// editSession.setBlock(vec, extent.getFullBlock(vec));
-// }
-// } finally {
-// Fawe.get().getQueueHandler().endSet(true);
-// }
-// } catch (IOException e) {
-// throw new RuntimeException(e);
-// }
-// } catch (MaxChangedBlocksException e) {
-// throw new RuntimeException(e);
-// } finally {
-// saveFolder.delete();
-// }
-// return true;
-
- return false; //TODO: rework or remove for 1.16
+ public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception {
+ return new Regen_v1_16_R1(bukkitWorld, region, target, options).regenerate();
}
@Override
diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R2.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R2.java
index 303c405d7..056988439 100644
--- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R2.java
+++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R2.java
@@ -28,23 +28,26 @@ import com.boydti.fawe.bukkit.adapter.mc1_16_2.BukkitGetBlocks_1_16_2;
import com.boydti.fawe.bukkit.adapter.mc1_16_2.FAWEWorldNativeAccess_1_16;
import com.boydti.fawe.bukkit.adapter.mc1_16_2.MapChunkUtil_1_16_2;
import com.boydti.fawe.bukkit.adapter.mc1_16_2.nbt.LazyCompoundTag_1_16_2;
+import com.google.common.base.Preconditions;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
-import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.blocks.TileEntityBlock;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.bukkit.adapter.CachedBukkitAdapter;
import com.sk89q.worldedit.bukkit.adapter.IDelegateBukkitImplAdapter;
+import com.sk89q.worldedit.bukkit.adapter.impl.regen.Regen_v1_16_R2;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.LazyBaseEntity;
+import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.wna.WorldNativeAccess;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.util.SideEffect;
import com.sk89q.worldedit.util.SideEffectSet;
+import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
@@ -87,7 +90,6 @@ import org.bukkit.craftbukkit.v1_16_R2.entity.CraftEntity;
import org.bukkit.craftbukkit.v1_16_R2.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_16_R2.inventory.CraftItemStack;
import org.bukkit.entity.Player;
-
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.OptionalInt;
@@ -149,7 +151,7 @@ public final class FAWE_Spigot_v1_16_R2 extends CachedBukkitAdapter implements I
@SuppressWarnings("deprecation")
@Override
public BaseBlock getBlock(Location location) {
- checkNotNull(location);
+ Preconditions.checkNotNull(location);
CraftWorld craftWorld = ((CraftWorld) location.getWorld());
int x = location.getBlockX();
@@ -253,7 +255,7 @@ public final class FAWE_Spigot_v1_16_R2 extends CachedBukkitAdapter implements I
@Override
public BaseEntity getEntity(org.bukkit.entity.Entity entity) {
- checkNotNull(entity);
+ Preconditions.checkNotNull(entity);
CraftEntity craftEntity = ((CraftEntity) entity);
Entity mcEntity = craftEntity.getHandle();
@@ -400,81 +402,8 @@ public final class FAWE_Spigot_v1_16_R2 extends CachedBukkitAdapter implements I
}
@Override
- public boolean regenerate(org.bukkit.World world, Region region, EditSession editSession) {
-// WorldServer originalWorld = ((CraftWorld) world).getHandle();
-// ChunkProviderServer provider = originalWorld.getChunkProvider();
-// if (!(provider instanceof ChunkProviderServer)) {
-// return false;
-// }
-//
-// File saveFolder = Files.createTempDir();
-// // register this just in case something goes wrong
-// // normally it should be deleted at the end of this method
-// saveFolder.deleteOnExit();
-// try {
-// MinecraftServer server = originalWorld.getServer().getServer();
-// Convertable.ConversionSession originalDataManager = server.convertable;
-//// Convertable.ConversionSession saveHandler = new Convertable.ConversionSession(world.getName(), world.);
-// WorldData newWorldData = new WorldData(originalWorld.worldData.a((NBTTagCompound) null),
-// server.dataConverterManager, getDataVersion(), null);
-// newWorldData.setName(UUID.randomUUID().toString());
-//
-// ChunkGenerator gen = world.getGenerator();
-// Environment env = world.getEnvironment();
-// try (WorldServer freshWorld = new WorldServer(server,
-// server.executorService, originalDataManager,
-// newWorldData,
-// originalWorld.worldProvider.getDimensionManager(),
-// originalWorld.getMethodProfiler(),
-// server.worldLoadListenerFactory.create(11),
-// env,
-// gen) {
-// @Override
-// public boolean addEntityChunk(Entity entity) {
-// //Fixes #320; Prevent adding entities so we aren't attempting to spawn them asynchronously
-// return false;
-// }
-// }) {
-//
-// // Pre-gen all the chunks
-// // We need to also pull one more chunk in every direction
-// Fawe.get().getQueueHandler().startSet(true);
-// try {
-// IQueueExtent extent = new SingleThreadQueueExtent();
-// extent.init(null, (x, z) -> new BukkitGetBlocks_1_16_2(freshWorld, x, z) {
-// @Override
-// public Chunk ensureLoaded(World nmsWorld, int X, int Z) {
-// Chunk cached = nmsWorld.getChunkIfLoaded(X, Z);
-// if (cached != null) return cached;
-// Future future = Fawe.get().getQueueHandler().sync((Supplier) () -> freshWorld.getChunkAt(X, Z));
-// while (!future.isDone()) {
-// // this feels so dirty
-// freshWorld.getChunkProvider().runTasks();
-// }
-// try {
-// return future.get();
-// } catch (InterruptedException | ExecutionException e) {
-// throw new RuntimeException(e);
-// }
-// }
-// }, null);
-// for (BlockVector3 vec : region) {
-// editSession.setBlock(vec, extent.getFullBlock(vec));
-// }
-// } finally {
-// Fawe.get().getQueueHandler().endSet(true);
-// }
-// } catch (IOException e) {
-// throw new RuntimeException(e);
-// }
-// } catch (MaxChangedBlocksException e) {
-// throw new RuntimeException(e);
-// } finally {
-// saveFolder.delete();
-// }
-// return true;
-
- return false; //TODO: rework or remove for 1.16
+ public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception {
+ return new Regen_v1_16_R2(bukkitWorld, region, target, options).regenerate();
}
@Override
diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_15_R2.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_15_R2.java
new file mode 100644
index 000000000..3f9958194
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_15_R2.java
@@ -0,0 +1,478 @@
+package com.sk89q.worldedit.bukkit.adapter.impl.regen;
+
+import com.boydti.fawe.Fawe;
+import com.boydti.fawe.beta.IChunkCache;
+import com.boydti.fawe.beta.IChunkGet;
+import com.boydti.fawe.bukkit.adapter.mc1_15_2.BukkitGetBlocks_1_15_2;
+import com.mojang.datafixers.util.Either;
+import com.sk89q.worldedit.bukkit.adapter.Regenerator;
+import com.sk89q.worldedit.extent.Extent;
+import com.sk89q.worldedit.regions.Region;
+import com.sk89q.worldedit.util.io.file.SafeFiles;
+import com.sk89q.worldedit.world.RegenOptions;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.nio.file.Path;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BooleanSupplier;
+import java.util.function.LongFunction;
+import java.util.function.Supplier;
+import javax.annotation.Nullable;
+import net.minecraft.server.v1_15_R1.Area;
+import net.minecraft.server.v1_15_R1.AreaContextTransformed;
+import net.minecraft.server.v1_15_R1.AreaFactory;
+import net.minecraft.server.v1_15_R1.AreaTransformer8;
+import net.minecraft.server.v1_15_R1.BiomeBase;
+import net.minecraft.server.v1_15_R1.BiomeLayoutOverworldConfiguration;
+import net.minecraft.server.v1_15_R1.Biomes;
+import net.minecraft.server.v1_15_R1.Chunk;
+import net.minecraft.server.v1_15_R1.ChunkConverter;
+import net.minecraft.server.v1_15_R1.ChunkCoordIntPair;
+import net.minecraft.server.v1_15_R1.ChunkGenerator;
+import net.minecraft.server.v1_15_R1.ChunkProviderFlat;
+import net.minecraft.server.v1_15_R1.ChunkProviderGenerate;
+import net.minecraft.server.v1_15_R1.ChunkProviderHell;
+import net.minecraft.server.v1_15_R1.ChunkProviderServer;
+import net.minecraft.server.v1_15_R1.ChunkProviderTheEnd;
+import net.minecraft.server.v1_15_R1.ChunkStatus;
+import net.minecraft.server.v1_15_R1.DefinedStructureManager;
+import net.minecraft.server.v1_15_R1.GenLayer;
+import net.minecraft.server.v1_15_R1.GenLayers;
+import net.minecraft.server.v1_15_R1.GeneratorSettingsEnd;
+import net.minecraft.server.v1_15_R1.GeneratorSettingsFlat;
+import net.minecraft.server.v1_15_R1.GeneratorSettingsNether;
+import net.minecraft.server.v1_15_R1.GeneratorSettingsOverworld;
+import net.minecraft.server.v1_15_R1.IChunkAccess;
+import net.minecraft.server.v1_15_R1.IRegistry;
+import net.minecraft.server.v1_15_R1.LightEngineThreaded;
+import net.minecraft.server.v1_15_R1.LinearCongruentialGenerator;
+import net.minecraft.server.v1_15_R1.MinecraftServer;
+import net.minecraft.server.v1_15_R1.NBTTagCompound;
+import net.minecraft.server.v1_15_R1.NoiseGeneratorPerlin;
+import net.minecraft.server.v1_15_R1.ProtoChunk;
+import net.minecraft.server.v1_15_R1.World;
+import net.minecraft.server.v1_15_R1.WorldChunkManager;
+import net.minecraft.server.v1_15_R1.WorldChunkManagerOverworld;
+import net.minecraft.server.v1_15_R1.WorldData;
+import net.minecraft.server.v1_15_R1.WorldLoadListener;
+import net.minecraft.server.v1_15_R1.WorldNBTStorage;
+import net.minecraft.server.v1_15_R1.WorldServer;
+import net.minecraft.server.v1_15_R1.WorldType;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_15_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_15_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_15_R1.generator.CustomChunkGenerator;
+import org.bukkit.craftbukkit.v1_15_R1.util.CraftMagicNumbers;
+import org.bukkit.generator.BlockPopulator;
+
+public class Regen_v1_15_R2 extends Regenerator {
+
+ private static final Field serverWorldsField;
+ private static final Field worldPaperConfigField;
+ private static final Field flatBedrockField;
+ private static final Field delegateField;
+ private static final Field chunkProviderField;
+
+ //list of chunk stati in correct order without FULL
+ private static final Map chunkStati = new LinkedHashMap<>();
+
+ static {
+ chunkStati.put(ChunkStatus.EMPTY, Regenerator.Concurrency.FULL); // radius -1, does nothing
+ chunkStati.put(ChunkStatus.STRUCTURE_STARTS, Regenerator.Concurrency.NONE); // uses unsynchronized maps
+ chunkStati.put(ChunkStatus.STRUCTURE_REFERENCES, Regenerator.Concurrency.FULL); // radius 8, but no writes to other chunks, only current chunk
+ chunkStati.put(ChunkStatus.BIOMES, Regenerator.Concurrency.FULL); // radius 0
+ chunkStati.put(ChunkStatus.NOISE, Regenerator.Concurrency.RADIUS); // radius 8
+ chunkStati.put(ChunkStatus.SURFACE, Regenerator.Concurrency.FULL); // radius 0
+ chunkStati.put(ChunkStatus.CARVERS, Regenerator.Concurrency.NONE); // radius 0, but RADIUS and FULL change results
+ chunkStati.put(ChunkStatus.LIQUID_CARVERS, Regenerator.Concurrency.NONE); // radius 0, but RADIUS and FULL change results
+ chunkStati.put(ChunkStatus.FEATURES, Regenerator.Concurrency.NONE); // uses unsynchronized maps
+ chunkStati.put(ChunkStatus.LIGHT, Regenerator.Concurrency.FULL); // radius 1, but no writes to other chunks, only current chunk
+ chunkStati.put(ChunkStatus.SPAWN, Regenerator.Concurrency.FULL); // radius 0
+ chunkStati.put(ChunkStatus.HEIGHTMAPS, Regenerator.Concurrency.FULL); // radius 0
+
+ try {
+ serverWorldsField = CraftServer.class.getDeclaredField("worlds");
+ serverWorldsField.setAccessible(true);
+
+ Field tmpPaperConfigField = null;
+ Field tmpFlatBedrockField = null;
+ try { //only present on paper
+ tmpPaperConfigField = World.class.getDeclaredField("paperConfig");
+ tmpPaperConfigField.setAccessible(true);
+
+ tmpFlatBedrockField = tmpPaperConfigField.getType().getDeclaredField("generateFlatBedrock");
+ tmpFlatBedrockField.setAccessible(true);
+ } catch (Exception e) {
+ tmpPaperConfigField = null;
+ tmpFlatBedrockField = null;
+ }
+ worldPaperConfigField = tmpPaperConfigField;
+ flatBedrockField = tmpFlatBedrockField;
+
+ delegateField = CustomChunkGenerator.class.getDeclaredField("delegate");
+ delegateField.setAccessible(true);
+
+ chunkProviderField = World.class.getDeclaredField("chunkProvider");
+ chunkProviderField.setAccessible(true);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ //runtime
+ private WorldServer originalNMSWorld;
+ private ChunkProviderServer originalChunkProvider;
+ private WorldServer freshNMSWorld;
+ private ChunkProviderServer freshChunkProvider;
+ private DefinedStructureManager structureManager;
+ private LightEngineThreaded lightEngine;
+ private ChunkGenerator generator;
+
+ private Path tempDir;
+
+ private boolean generateFlatBedrock = false;
+
+ public Regen_v1_15_R2(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) {
+ super(originalBukkitWorld, region, target, options);
+ }
+
+ @Override
+ protected boolean prepare() {
+ this.originalNMSWorld = ((CraftWorld) originalBukkitWorld).getHandle();
+ originalChunkProvider = originalNMSWorld.getChunkProvider();
+ if (!(originalChunkProvider instanceof ChunkProviderServer)) {
+ return false;
+ }
+
+ //flat bedrock? (only on paper)
+ try {
+ generateFlatBedrock = flatBedrockField.getBoolean(worldPaperConfigField.get(originalNMSWorld));
+ } catch (Exception ignored) {
+ }
+
+ seed = options.getSeed().orElse(originalNMSWorld.getSeed());
+ chunkStati.forEach((s, c) -> super.chunkStati.put(new ChunkStatusWrap(s), c));
+
+ return true;
+ }
+
+ @Override
+ protected boolean initNewWorld() throws Exception {
+ //world folder
+ tempDir = java.nio.file.Files.createTempDirectory("WorldEditWorldGen");
+
+ //prepare for world init (see upstream implementation for reference)
+ org.bukkit.World.Environment env = originalBukkitWorld.getEnvironment();
+ org.bukkit.generator.ChunkGenerator gen = originalBukkitWorld.getGenerator();
+
+ MinecraftServer server = originalNMSWorld.getServer().getServer();
+ WorldData newWorldData = new WorldData(originalNMSWorld.worldData.a((NBTTagCompound) null), server.dataConverterManager, CraftMagicNumbers.INSTANCE.getDataVersion(), (NBTTagCompound) null);
+ newWorldData.setName("worldeditregentempworld");
+ WorldNBTStorage saveHandler = new WorldNBTStorage(new File(tempDir.toUri()), originalNMSWorld.getDataManager().getDirectory().getName(), server, server.dataConverterManager);
+
+ //init world
+ freshNMSWorld = Fawe.get().getQueueHandler().sync((Supplier) () -> new WorldServer(server, server.executorService, saveHandler, newWorldData, originalNMSWorld.worldProvider.getDimensionManager(), originalNMSWorld.getMethodProfiler(), new RegenNoOpWorldLoadListener(), env, gen) {
+ @Override
+ public void doTick(BooleanSupplier booleansupplier) { //no ticking
+ }
+ }).get();
+ freshNMSWorld.savingDisabled = true;
+ removeWorldFromWorldsMap();
+ newWorldData.checkName(originalNMSWorld.getWorldData().getName()); //rename to original world name
+
+ try { //flat bedrock (paper only)
+ Object paperconf = worldPaperConfigField.get(freshNMSWorld);
+ flatBedrockField.setBoolean(paperconf, generateFlatBedrock);
+ } catch (Exception e) {
+ }
+
+ DefinedStructureManager tmpStructureManager = saveHandler.f();
+ freshChunkProvider = new ChunkProviderServer(freshNMSWorld, saveHandler.getDirectory(), server.aC(), tmpStructureManager, server.executorService, originalChunkProvider.chunkGenerator, freshNMSWorld.spigotConfig.viewDistance, new RegenNoOpWorldLoadListener(), () -> freshNMSWorld.getWorldPersistentData()) {
+ // redirect to our protoChunks list
+ @Override
+ public IChunkAccess getChunkAt(int x, int z, ChunkStatus chunkstatus, boolean flag) {
+ return getProtoChunkAt(x, z);
+ }
+ };
+ chunkProviderField.set(freshNMSWorld, freshChunkProvider);
+
+ //generator
+ if (originalChunkProvider.getChunkGenerator() instanceof ChunkProviderFlat) {
+ GeneratorSettingsFlat generatorSettingFlat = (GeneratorSettingsFlat) originalChunkProvider.getChunkGenerator().getSettings();
+ generator = new ChunkProviderFlat(freshNMSWorld, originalChunkProvider.getChunkGenerator().getWorldChunkManager(), generatorSettingFlat);
+ } else if (originalChunkProvider.getChunkGenerator() instanceof ChunkProviderGenerate) { //overworld
+ GeneratorSettingsOverworld settings = (GeneratorSettingsOverworld) originalChunkProvider.getChunkGenerator().getSettings();
+ WorldChunkManager chunkManager = originalChunkProvider.getChunkGenerator().getWorldChunkManager();
+ if (chunkManager instanceof WorldChunkManagerOverworld) { //should always be true
+ chunkManager = fastOverWorldChunkManager(chunkManager);
+ }
+ generator = new ChunkProviderGenerate(freshNMSWorld, chunkManager, settings);
+ } else if (originalChunkProvider.getChunkGenerator() instanceof ChunkProviderHell) { //nether
+ GeneratorSettingsNether settings = (GeneratorSettingsNether) originalChunkProvider.getChunkGenerator().getSettings();
+ generator = new ChunkProviderHell(freshNMSWorld, originalChunkProvider.getChunkGenerator().getWorldChunkManager(), settings);
+ } else if (originalChunkProvider.getChunkGenerator() instanceof ChunkProviderTheEnd) { //end
+ GeneratorSettingsEnd settings = (GeneratorSettingsEnd) originalChunkProvider.getChunkGenerator().getSettings();
+ generator = new ChunkProviderTheEnd(freshNMSWorld, originalChunkProvider.getChunkGenerator().getWorldChunkManager(), settings);
+ } else if (originalChunkProvider.getChunkGenerator() instanceof CustomChunkGenerator) {
+ ChunkGenerator delegate = (ChunkGenerator) delegateField.get(originalChunkProvider.getChunkGenerator());
+ generator = delegate;
+ } else {
+ System.out.println("Unsupported generator type " + originalChunkProvider.getChunkGenerator().getClass().getName());
+ return false;
+ }
+ if (originalNMSWorld.generator != null) {
+ // wrap custom world generator
+ generator = new CustomChunkGenerator(freshNMSWorld, originalNMSWorld.generator);
+ generateConcurrent = originalNMSWorld.generator.isParallelCapable();
+ }
+
+ //lets start then
+ structureManager = tmpStructureManager;
+ lightEngine = freshChunkProvider.getLightEngine();
+
+ return true;
+ }
+
+ @Override
+ protected void cleanup() {
+ //shutdown chunk provider
+ try {
+ Fawe.get().getQueueHandler().sync(() -> {
+ try {
+ freshChunkProvider.close(false);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ } catch (Exception e) {
+ }
+
+ //remove world from server
+ try {
+ removeWorldFromWorldsMap();
+ } catch (Exception e) {
+ }
+
+ //delete directory
+ try {
+ SafeFiles.tryHardToDeleteDir(tempDir);
+ } catch (Exception e) {
+ }
+ }
+
+ @Override
+ protected ProtoChunk createProtoChunk(int x, int z) {
+ return new ProtoChunk(new ChunkCoordIntPair(x, z), ChunkConverter.a) {
+ public boolean generateFlatBedrock() {
+ return generateFlatBedrock;
+ }
+ };
+ }
+
+ @Override
+ protected Chunk createChunk(ProtoChunk protoChunk) {
+ return new Chunk(freshNMSWorld, protoChunk);
+ }
+
+ @Override
+ protected ChunkStatusWrap getFullChunkStatus() {
+ return new ChunkStatusWrap(ChunkStatus.FULL);
+ }
+
+ @Override
+ protected List getBlockPopulators() {
+ return originalNMSWorld.getWorld().getPopulators();
+ }
+
+ @Override
+ protected void populate(Chunk chunk, Random random, BlockPopulator pop) {
+ pop.populate(freshNMSWorld.getWorld(), random, chunk.bukkitChunk);
+ }
+
+ @Override
+ protected IChunkCache initSourceQueueCache() {
+ return (chunkX, chunkZ) -> new BukkitGetBlocks_1_15_2(freshNMSWorld, chunkX, chunkZ) {
+ @Override
+ public Chunk ensureLoaded(World nmsWorld, int x, int z) {
+ return getChunkAt(x, z);
+ }
+ };
+ }
+
+ protected class ChunkStatusWrap extends Regenerator.ChunkStatusWrapper {
+
+ private final ChunkStatus chunkStatus;
+
+ public ChunkStatusWrap(ChunkStatus chunkStatus) {
+ this.chunkStatus = chunkStatus;
+ }
+
+ @Override
+ public int requiredNeigborChunkRadius() {
+ return chunkStatus.f();
+ }
+
+ @Override
+ public String name() {
+ return chunkStatus.d();
+ }
+
+ @Override
+ public void processChunk(Long xz, List accessibleChunks) {
+ chunkStatus.a(freshNMSWorld,
+ generator,
+ structureManager,
+ lightEngine,
+ c -> CompletableFuture.completedFuture(Either.left(c)),
+ accessibleChunks);
+ }
+ }
+
+ //util
+ private void removeWorldFromWorldsMap() {
+ Fawe.get().getQueueHandler().sync(() -> {
+ try {
+ Map map = (Map) serverWorldsField.get(Bukkit.getServer());
+ map.remove("worldeditregentempworld");
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ private WorldChunkManager fastOverWorldChunkManager(WorldChunkManager chunkManager) throws Exception {
+ Field genLayerField = WorldChunkManagerOverworld.class.getDeclaredField("d");
+ genLayerField.setAccessible(true);
+ Field areaLazyField = GenLayer.class.getDeclaredField("b");
+ areaLazyField.setAccessible(true);
+ Method initAreaFactoryMethod = GenLayers.class.getDeclaredMethod("a", WorldType.class, GeneratorSettingsOverworld.class, LongFunction.class);
+ initAreaFactoryMethod.setAccessible(true);
+
+ //init new WorldChunkManagerOverworld
+ BiomeLayoutOverworldConfiguration biomeconfig = new BiomeLayoutOverworldConfiguration(freshNMSWorld.getWorldData())
+ .a((GeneratorSettingsOverworld) originalChunkProvider.getChunkGenerator().getSettings());
+ chunkManager = new WorldChunkManagerOverworld(biomeconfig);
+
+ //replace genLayer
+ AreaFactory factory = (AreaFactory) initAreaFactoryMethod.invoke(null, biomeconfig.b(), biomeconfig.c(), (LongFunction) (l -> new FastWorldGenContextArea(seed, l)));
+ genLayerField.set(chunkManager, new FastGenLayer(factory));
+
+ return chunkManager;
+ }
+
+ private static class FastWorldGenContextArea implements AreaContextTransformed {
+
+ private final ConcurrentHashMap sharedAreaMap = new ConcurrentHashMap<>();
+ private final NoiseGeneratorPerlin perlinNoise;
+ private final long magicrandom;
+ private final ConcurrentHashMap map = new ConcurrentHashMap<>(); //needed for multithreaded generation
+
+ public FastWorldGenContextArea(long seed, long lconst) {
+ this.magicrandom = mix(seed, lconst);
+ this.perlinNoise = new NoiseGeneratorPerlin(new Random(seed));
+ }
+
+ @Override
+ public FastAreaLazy a(AreaTransformer8 var0) {
+ return new FastAreaLazy(sharedAreaMap, var0);
+ }
+
+ @Override
+ public void a(long x, long z) {
+ long l = this.magicrandom;
+ l = LinearCongruentialGenerator.a(l, x);
+ l = LinearCongruentialGenerator.a(l, z);
+ l = LinearCongruentialGenerator.a(l, x);
+ l = LinearCongruentialGenerator.a(l, z);
+ this.map.put(Thread.currentThread().getId(), l);
+ }
+
+ @Override
+ public int a(int y) {
+ long tid = Thread.currentThread().getId();
+ long e = this.map.computeIfAbsent(tid, i -> 0L);
+ int mod = (int) Math.floorMod(e >> 24L, (long) y);
+ this.map.put(tid, LinearCongruentialGenerator.a(e, this.magicrandom));
+ return mod;
+ }
+
+ @Override
+ public NoiseGeneratorPerlin b() {
+ return this.perlinNoise;
+ }
+
+ private static long mix(long seed, long lconst) {
+ long l1 = lconst;
+ l1 = LinearCongruentialGenerator.a(l1, lconst);
+ l1 = LinearCongruentialGenerator.a(l1, lconst);
+ l1 = LinearCongruentialGenerator.a(l1, lconst);
+ long l2 = seed;
+ l2 = LinearCongruentialGenerator.a(l2, l1);
+ l2 = LinearCongruentialGenerator.a(l2, l1);
+ l2 = LinearCongruentialGenerator.a(l2, l1);
+ return l2;
+ }
+ }
+
+ private static class FastGenLayer extends GenLayer {
+
+ private final FastAreaLazy areaLazy;
+
+ public FastGenLayer(AreaFactory factory) throws Exception {
+ super(() -> null);
+ this.areaLazy = factory.make();
+ }
+
+ @Override
+ public BiomeBase a(int x, int z) {
+ BiomeBase biome = IRegistry.BIOME.fromId(this.areaLazy.a(x, z));
+ if (biome == null)
+ return Biomes.b;
+ return biome;
+ }
+ }
+
+ private static class FastAreaLazy implements Area {
+
+ private final AreaTransformer8 transformer;
+ //ConcurrentHashMap is 50% faster that Long2IntLinkedOpenHashMap in a syncronized context
+ //using a map for each thread worsens the performance significantly due to cache misses (factor 5)
+ private final ConcurrentHashMap sharedMap;
+
+ public FastAreaLazy(ConcurrentHashMap sharedMap, AreaTransformer8 transformer) {
+ this.sharedMap = sharedMap;
+ this.transformer = transformer;
+ }
+
+ @Override
+ public int a(int x, int z) {
+ long zx = ChunkCoordIntPair.pair(x, z);
+ return this.sharedMap.computeIfAbsent(zx, i -> this.transformer.apply(x, z));
+ }
+ }
+
+ private static class RegenNoOpWorldLoadListener implements WorldLoadListener {
+
+ private RegenNoOpWorldLoadListener() {
+ }
+
+ @Override
+ public void a(ChunkCoordIntPair chunkCoordIntPair) {
+ }
+
+ @Override
+ public void a(ChunkCoordIntPair chunkCoordIntPair, @Nullable ChunkStatus chunkStatus) {
+ }
+
+ @Override
+ public void b() {
+ }
+ }
+}
diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R1.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R1.java
new file mode 100644
index 000000000..af66ef955
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R1.java
@@ -0,0 +1,519 @@
+package com.sk89q.worldedit.bukkit.adapter.impl.regen;
+
+import com.boydti.fawe.Fawe;
+import com.boydti.fawe.beta.IChunkCache;
+import com.boydti.fawe.beta.IChunkGet;
+import com.boydti.fawe.bukkit.adapter.mc1_16_1.BukkitGetBlocks_1_16_1;
+import com.google.common.collect.ImmutableList;
+import com.mojang.datafixers.util.Either;
+import com.mojang.serialization.Dynamic;
+import com.mojang.serialization.Lifecycle;
+import com.sk89q.worldedit.bukkit.adapter.Regenerator;
+import com.sk89q.worldedit.extent.Extent;
+import com.sk89q.worldedit.regions.Region;
+import com.sk89q.worldedit.util.io.file.SafeFiles;
+import com.sk89q.worldedit.world.RegenOptions;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BooleanSupplier;
+import java.util.function.LongFunction;
+import java.util.function.Supplier;
+import javax.annotation.Nullable;
+import net.minecraft.server.v1_16_R1.Area;
+import net.minecraft.server.v1_16_R1.AreaContextTransformed;
+import net.minecraft.server.v1_16_R1.AreaFactory;
+import net.minecraft.server.v1_16_R1.AreaTransformer8;
+import net.minecraft.server.v1_16_R1.BiomeBase;
+import net.minecraft.server.v1_16_R1.Biomes;
+import net.minecraft.server.v1_16_R1.Chunk;
+import net.minecraft.server.v1_16_R1.ChunkConverter;
+import net.minecraft.server.v1_16_R1.ChunkCoordIntPair;
+import net.minecraft.server.v1_16_R1.ChunkGenerator;
+import net.minecraft.server.v1_16_R1.ChunkGeneratorAbstract;
+import net.minecraft.server.v1_16_R1.ChunkProviderFlat;
+import net.minecraft.server.v1_16_R1.ChunkProviderServer;
+import net.minecraft.server.v1_16_R1.ChunkStatus;
+import net.minecraft.server.v1_16_R1.Convertable;
+import net.minecraft.server.v1_16_R1.DefinedStructureManager;
+import net.minecraft.server.v1_16_R1.DynamicOpsNBT;
+import net.minecraft.server.v1_16_R1.GenLayer;
+import net.minecraft.server.v1_16_R1.GenLayers;
+import net.minecraft.server.v1_16_R1.GeneratorSettingBase;
+import net.minecraft.server.v1_16_R1.GeneratorSettings;
+import net.minecraft.server.v1_16_R1.GeneratorSettingsFlat;
+import net.minecraft.server.v1_16_R1.IChunkAccess;
+import net.minecraft.server.v1_16_R1.IRegistry;
+import net.minecraft.server.v1_16_R1.IRegistryCustom;
+import net.minecraft.server.v1_16_R1.LightEngineThreaded;
+import net.minecraft.server.v1_16_R1.LinearCongruentialGenerator;
+import net.minecraft.server.v1_16_R1.MinecraftServer;
+import net.minecraft.server.v1_16_R1.NBTBase;
+import net.minecraft.server.v1_16_R1.NBTTagCompound;
+import net.minecraft.server.v1_16_R1.NoiseGeneratorPerlin;
+import net.minecraft.server.v1_16_R1.ProtoChunk;
+import net.minecraft.server.v1_16_R1.RegistryReadOps;
+import net.minecraft.server.v1_16_R1.ResourceKey;
+import net.minecraft.server.v1_16_R1.World;
+import net.minecraft.server.v1_16_R1.WorldChunkManager;
+import net.minecraft.server.v1_16_R1.WorldChunkManagerOverworld;
+import net.minecraft.server.v1_16_R1.WorldDataServer;
+import net.minecraft.server.v1_16_R1.WorldDimension;
+import net.minecraft.server.v1_16_R1.WorldLoadListener;
+import net.minecraft.server.v1_16_R1.WorldServer;
+import net.minecraft.server.v1_16_R1.WorldSettings;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_16_R1.CraftServer;
+import org.bukkit.craftbukkit.v1_16_R1.CraftWorld;
+import org.bukkit.craftbukkit.v1_16_R1.generator.CustomChunkGenerator;
+import org.bukkit.generator.BlockPopulator;
+
+public class Regen_v1_16_R1 extends Regenerator {
+
+ private static final Field serverWorldsField;
+ private static final Field worldPaperConfigField;
+ private static final Field flatBedrockField;
+ private static final Field generatorSettingBaseField;
+ private static final Field generatorSettingFlatField;
+ private static final Field delegateField;
+ private static final Field chunkProviderField;
+
+ //list of chunk stati in correct order without FULL
+ private static final Map chunkStati = new LinkedHashMap<>();
+
+ static {
+ chunkStati.put(ChunkStatus.EMPTY, Regenerator.Concurrency.FULL); // radius -1, does nothing
+ chunkStati.put(ChunkStatus.STRUCTURE_STARTS, Regenerator.Concurrency.NONE); // uses unsynchronized maps
+ chunkStati.put(ChunkStatus.STRUCTURE_REFERENCES, Regenerator.Concurrency.FULL); // radius 8, but no writes to other chunks, only current chunk
+ chunkStati.put(ChunkStatus.BIOMES, Regenerator.Concurrency.FULL); // radius 0
+ chunkStati.put(ChunkStatus.NOISE, Regenerator.Concurrency.RADIUS); // radius 8
+ chunkStati.put(ChunkStatus.SURFACE, Regenerator.Concurrency.FULL); // radius 0
+ chunkStati.put(ChunkStatus.CARVERS, Regenerator.Concurrency.NONE); // radius 0, but RADIUS and FULL change results
+ chunkStati.put(ChunkStatus.LIQUID_CARVERS, Regenerator.Concurrency.NONE); // radius 0, but RADIUS and FULL change results
+ chunkStati.put(ChunkStatus.FEATURES, Regenerator.Concurrency.NONE); // uses unsynchronized maps
+ chunkStati.put(ChunkStatus.LIGHT, Regenerator.Concurrency.FULL); // radius 1, but no writes to other chunks, only current chunk
+ chunkStati.put(ChunkStatus.SPAWN, Regenerator.Concurrency.FULL); // radius 0
+ chunkStati.put(ChunkStatus.HEIGHTMAPS, Regenerator.Concurrency.FULL); // radius 0
+
+ try {
+ serverWorldsField = CraftServer.class.getDeclaredField("worlds");
+ serverWorldsField.setAccessible(true);
+
+ Field tmpPaperConfigField = null;
+ Field tmpFlatBedrockField = null;
+ try { //only present on paper
+ tmpPaperConfigField = World.class.getDeclaredField("paperConfig");
+ tmpPaperConfigField.setAccessible(true);
+
+ tmpFlatBedrockField = tmpPaperConfigField.getType().getDeclaredField("generateFlatBedrock");
+ tmpFlatBedrockField.setAccessible(true);
+ } catch (Exception e) {
+ tmpPaperConfigField = null;
+ tmpFlatBedrockField = null;
+ }
+ worldPaperConfigField = tmpPaperConfigField;
+ flatBedrockField = tmpFlatBedrockField;
+
+ generatorSettingBaseField = ChunkGeneratorAbstract.class.getDeclaredField("h");
+ generatorSettingBaseField.setAccessible(true);
+
+ generatorSettingFlatField = ChunkProviderFlat.class.getDeclaredField("e");
+ generatorSettingFlatField.setAccessible(true);
+
+ delegateField = CustomChunkGenerator.class.getDeclaredField("delegate");
+ delegateField.setAccessible(true);
+
+ chunkProviderField = WorldServer.class.getDeclaredField("chunkProvider");
+ chunkProviderField.setAccessible(true);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ //runtime
+ private WorldServer originalNMSWorld;
+ private ChunkProviderServer originalChunkProvider;
+ private WorldServer freshNMSWorld;
+ private ChunkProviderServer freshChunkProvider;
+ private Convertable.ConversionSession session;
+ private DefinedStructureManager structureManager;
+ private LightEngineThreaded lightEngine;
+ private ChunkGenerator generator;
+
+ private Path tempDir;
+
+ private boolean generateFlatBedrock = false;
+
+ public Regen_v1_16_R1(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) {
+ super(originalBukkitWorld, region, target, options);
+ }
+
+ @Override
+ protected boolean prepare() {
+ this.originalNMSWorld = ((CraftWorld) originalBukkitWorld).getHandle();
+ originalChunkProvider = originalNMSWorld.getChunkProvider();
+ if (!(originalChunkProvider instanceof ChunkProviderServer)) {
+ return false;
+ }
+
+ //flat bedrock? (only on paper)
+ try {
+ generateFlatBedrock = flatBedrockField.getBoolean(worldPaperConfigField.get(originalNMSWorld));
+ } catch (Exception ignored) {
+ }
+
+ seed = options.getSeed().orElse(originalNMSWorld.getSeed());
+ chunkStati.forEach((s, c) -> super.chunkStati.put(new ChunkStatusWrap(s), c));
+
+ return true;
+ }
+
+ @Override
+ protected boolean initNewWorld() throws Exception {
+ //world folder
+ tempDir = java.nio.file.Files.createTempDirectory("WorldEditWorldGen");
+
+ //prepare for world init (see upstream implementation for reference)
+ org.bukkit.World.Environment env = originalBukkitWorld.getEnvironment();
+ org.bukkit.generator.ChunkGenerator gen = originalBukkitWorld.getGenerator();
+ Convertable convertable = Convertable.a(tempDir);
+ ResourceKey worldDimKey = getWorldDimKey(env);
+ session = convertable.c("worldeditregentempworld", worldDimKey);
+ WorldDataServer originalWorldData = originalNMSWorld.worldDataServer;
+
+ MinecraftServer server = originalNMSWorld.getServer().getServer();
+ WorldDataServer levelProperties = (WorldDataServer) server.getSaveData();
+ GeneratorSettings newOpts = GeneratorSettings.a.encodeStart(DynamicOpsNBT.a, levelProperties.getGeneratorSettings()).flatMap(tag -> GeneratorSettings.a.parse(this.recursivelySetSeed(new Dynamic<>(DynamicOpsNBT.a, tag), seed, new HashSet<>()))).result().orElseThrow(() -> new IllegalStateException("Unable to map GeneratorOptions"));
+ WorldSettings newWorldSettings = new WorldSettings("worldeditregentempworld", originalWorldData.b.getGameType(), originalWorldData.b.hardcore, originalWorldData.b.getDifficulty(), originalWorldData.b.e(), originalWorldData.b.getGameRules(), originalWorldData.b.g());
+ WorldDataServer newWorldData = new WorldDataServer(newWorldSettings, newOpts, Lifecycle.stable());
+
+ //init world
+ freshNMSWorld = Fawe.get().getQueueHandler().sync((Supplier) () -> new WorldServer(server, server.executorService, session, newWorldData, originalNMSWorld.getDimensionKey(), originalNMSWorld.getTypeKey(), originalNMSWorld.getDimensionManager(), new RegenNoOpWorldLoadListener(), ((WorldDimension) newOpts.e().a(worldDimKey)).c(), originalNMSWorld.isDebugWorld(), seed, ImmutableList.of(), false, env, gen) {
+ @Override
+ public void doTick(BooleanSupplier booleansupplier) { //no ticking
+ }
+ }).get();
+ freshNMSWorld.savingDisabled = true;
+ removeWorldFromWorldsMap();
+ newWorldData.checkName(originalNMSWorld.worldDataServer.getName()); //rename to original world name
+
+ freshChunkProvider = new ChunkProviderServer(freshNMSWorld, session, server.getDataFixer(), server.getDefinedStructureManager(), server.executorService, originalChunkProvider.chunkGenerator, freshNMSWorld.spigotConfig.viewDistance, server.isSyncChunkWrites(), new RegenNoOpWorldLoadListener(), () -> server.D().getWorldPersistentData()) {
+ // redirect to our protoChunks list
+ @Override
+ public IChunkAccess getChunkAt(int x, int z, ChunkStatus chunkstatus, boolean flag) {
+ return getProtoChunkAt(x, z);
+ }
+ };
+ chunkProviderField.set(freshNMSWorld, freshChunkProvider);
+
+ //generator
+ if (originalChunkProvider.getChunkGenerator() instanceof ChunkProviderFlat) {
+ GeneratorSettingsFlat generatorSettingFlat = (GeneratorSettingsFlat) generatorSettingFlatField.get(originalChunkProvider.getChunkGenerator());
+ generator = new ChunkProviderFlat(generatorSettingFlat);
+ } else if (originalChunkProvider.getChunkGenerator() instanceof ChunkGeneratorAbstract) {
+ GeneratorSettingBase generatorSettingBase = (GeneratorSettingBase) generatorSettingBaseField.get(originalChunkProvider.getChunkGenerator());
+ WorldChunkManager chunkManager = originalChunkProvider.getChunkGenerator().getWorldChunkManager();
+ if (chunkManager instanceof WorldChunkManagerOverworld) {
+ chunkManager = fastOverWorldChunkManager(chunkManager);
+ }
+ generator = new ChunkGeneratorAbstract(chunkManager, seed, generatorSettingBase);
+ } else if (originalChunkProvider.getChunkGenerator() instanceof CustomChunkGenerator) {
+ ChunkGenerator delegate = (ChunkGenerator) delegateField.get(originalChunkProvider.getChunkGenerator());
+ generator = delegate;
+ } else {
+ System.out.println("Unsupported generator type " + originalChunkProvider.getChunkGenerator().getClass().getName());
+ return false;
+ }
+ if (originalNMSWorld.generator != null) {
+ // wrap custom world generator
+ generator = new CustomChunkGenerator(freshNMSWorld, generator, originalNMSWorld.generator);
+ generateConcurrent = originalNMSWorld.generator.isParallelCapable();
+ }
+
+ //lets start then
+ structureManager = server.getDefinedStructureManager();
+ lightEngine = freshChunkProvider.getLightEngine();
+
+ return true;
+ }
+
+ @Override
+ protected void cleanup() {
+ try {
+ session.close();
+ } catch (Exception e) {
+ }
+
+ //shutdown chunk provider
+ try {
+ Fawe.get().getQueueHandler().sync(() -> {
+ try {
+ freshChunkProvider.close(false);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ } catch (Exception e) {
+ }
+
+ //remove world from server
+ try {
+ removeWorldFromWorldsMap();
+ } catch (Exception e) {
+ }
+
+ //delete directory
+ try {
+ SafeFiles.tryHardToDeleteDir(tempDir);
+ } catch (Exception e) {
+ }
+ }
+
+ @Override
+ protected ProtoChunk createProtoChunk(int x, int z) {
+ return new ProtoChunk(new ChunkCoordIntPair(x, z), ChunkConverter.a) {
+ public boolean generateFlatBedrock() {
+ return generateFlatBedrock;
+ }
+ };
+ }
+
+ @Override
+ protected Chunk createChunk(ProtoChunk protoChunk) {
+ return new Chunk(freshNMSWorld, protoChunk);
+ }
+
+ @Override
+ protected ChunkStatusWrap getFullChunkStatus() {
+ return new ChunkStatusWrap(ChunkStatus.FULL);
+ }
+
+ @Override
+ protected List getBlockPopulators() {
+ return originalNMSWorld.getWorld().getPopulators();
+ }
+
+ @Override
+ protected void populate(Chunk chunk, Random random, BlockPopulator pop) {
+ pop.populate(freshNMSWorld.getWorld(), random, chunk.bukkitChunk);
+ }
+
+ @Override
+ protected IChunkCache initSourceQueueCache() {
+ return (chunkX, chunkZ) -> new BukkitGetBlocks_1_16_1(freshNMSWorld, chunkX, chunkZ) {
+ @Override
+ public Chunk ensureLoaded(World nmsWorld, int x, int z) {
+ return getChunkAt(x, z);
+ }
+ };
+ }
+
+ protected class ChunkStatusWrap extends Regenerator.ChunkStatusWrapper {
+
+ private final ChunkStatus chunkStatus;
+
+ public ChunkStatusWrap(ChunkStatus chunkStatus) {
+ this.chunkStatus = chunkStatus;
+ }
+
+ @Override
+ public int requiredNeigborChunkRadius() {
+ return chunkStatus.f();
+ }
+
+ @Override
+ public String name() {
+ return chunkStatus.d();
+ }
+
+ @Override
+ public void processChunk(Long xz, List accessibleChunks) {
+ chunkStatus.a(freshNMSWorld,
+ generator,
+ structureManager,
+ lightEngine,
+ c -> CompletableFuture.completedFuture(Either.left(c)),
+ accessibleChunks);
+ }
+ }
+
+ //util
+ private void removeWorldFromWorldsMap() {
+ Fawe.get().getQueueHandler().sync(() -> {
+ try {
+ Map map = (Map) serverWorldsField.get(Bukkit.getServer());
+ map.remove("worldeditregentempworld");
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ private ResourceKey getWorldDimKey(org.bukkit.World.Environment env) {
+ switch (env) {
+ case NETHER:
+ return WorldDimension.THE_NETHER;
+ case THE_END:
+ return WorldDimension.THE_END;
+ case NORMAL:
+ default:
+ return WorldDimension.OVERWORLD;
+ }
+ }
+
+ private Dynamic recursivelySetSeed(Dynamic dynamic, long seed, Set> seen) {
+ return !seen.add(dynamic) ? dynamic : dynamic.updateMapValues((pair) -> {
+ if (((Dynamic) pair.getFirst()).asString("").equals("seed")) {
+ return pair.mapSecond((v) -> {
+ return v.createLong(seed);
+ });
+ } else {
+ return ((Dynamic) pair.getSecond()).getValue() instanceof NBTTagCompound ? pair.mapSecond((v) -> {
+ return this.recursivelySetSeed((Dynamic) v, seed, seen);
+ }) : pair;
+ }
+ });
+ }
+
+ private WorldChunkManager fastOverWorldChunkManager(WorldChunkManager chunkManager) throws Exception {
+ Field legacyBiomeInitLayerField = WorldChunkManagerOverworld.class.getDeclaredField("i");
+ legacyBiomeInitLayerField.setAccessible(true);
+ Field largeBiomesField = WorldChunkManagerOverworld.class.getDeclaredField("j");
+ largeBiomesField.setAccessible(true);
+ Field genLayerField = WorldChunkManagerOverworld.class.getDeclaredField("f");
+ genLayerField.setAccessible(true);
+ Field areaLazyField = GenLayer.class.getDeclaredField("b");
+ areaLazyField.setAccessible(true);
+ Method initAreaFactoryMethod = GenLayers.class.getDeclaredMethod("a", boolean.class, int.class, int.class, LongFunction.class);
+ initAreaFactoryMethod.setAccessible(true);
+
+ //init new WorldChunkManagerOverworld
+ boolean legacyBiomeInitLayer = legacyBiomeInitLayerField.getBoolean(chunkManager);
+ boolean largebiomes = largeBiomesField.getBoolean(chunkManager);
+ chunkManager = new WorldChunkManagerOverworld(seed, legacyBiomeInitLayer, largebiomes);
+
+ //replace genLayer
+ AreaFactory factory = (AreaFactory) initAreaFactoryMethod.invoke(null, legacyBiomeInitLayer, largebiomes ? 6 : 4, 4, (LongFunction) (l -> new FastWorldGenContextArea(seed, l)));
+ genLayerField.set(chunkManager, new FastGenLayer(factory));
+
+ return chunkManager;
+ }
+
+ private static class FastWorldGenContextArea implements AreaContextTransformed {
+
+ private final ConcurrentHashMap sharedAreaMap = new ConcurrentHashMap<>();
+ private final NoiseGeneratorPerlin perlinNoise;
+ private final long magicrandom;
+ private final ConcurrentHashMap map = new ConcurrentHashMap<>(); //needed for multithreaded generation
+
+ public FastWorldGenContextArea(long seed, long lconst) {
+ this.magicrandom = mix(seed, lconst);
+ this.perlinNoise = new NoiseGeneratorPerlin(new Random(seed));
+ }
+
+ @Override
+ public FastAreaLazy a(AreaTransformer8 var0) {
+ return new FastAreaLazy(sharedAreaMap, var0);
+ }
+
+ @Override
+ public void a(long x, long z) {
+ long l = this.magicrandom;
+ l = LinearCongruentialGenerator.a(l, x);
+ l = LinearCongruentialGenerator.a(l, z);
+ l = LinearCongruentialGenerator.a(l, x);
+ l = LinearCongruentialGenerator.a(l, z);
+ this.map.put(Thread.currentThread().getId(), l);
+ }
+
+ @Override
+ public int a(int y) {
+ long tid = Thread.currentThread().getId();
+ long e = this.map.computeIfAbsent(tid, i -> 0L);
+ int mod = (int) Math.floorMod(e >> 24L, (long) y);
+ this.map.put(tid, LinearCongruentialGenerator.a(e, this.magicrandom));
+ return mod;
+ }
+
+ @Override
+ public NoiseGeneratorPerlin b() {
+ return this.perlinNoise;
+ }
+
+ private static long mix(long seed, long lconst) {
+ long l1 = lconst;
+ l1 = LinearCongruentialGenerator.a(l1, lconst);
+ l1 = LinearCongruentialGenerator.a(l1, lconst);
+ l1 = LinearCongruentialGenerator.a(l1, lconst);
+ long l2 = seed;
+ l2 = LinearCongruentialGenerator.a(l2, l1);
+ l2 = LinearCongruentialGenerator.a(l2, l1);
+ l2 = LinearCongruentialGenerator.a(l2, l1);
+ return l2;
+ }
+ }
+
+ private static class FastGenLayer extends GenLayer {
+
+ private final FastAreaLazy areaLazy;
+
+ public FastGenLayer(AreaFactory factory) throws Exception {
+ super(() -> null);
+ this.areaLazy = factory.make();
+ }
+
+ @Override
+ public BiomeBase a(int x, int z) {
+ BiomeBase biome = IRegistry.BIOME.fromId(this.areaLazy.a(x, z));
+ if (biome == null)
+ return Biomes.b;
+ return biome;
+ }
+ }
+
+ private static class FastAreaLazy implements Area {
+
+ private final AreaTransformer8 transformer;
+ //ConcurrentHashMap is 50% faster that Long2IntLinkedOpenHashMap in a syncronized context
+ //using a map for each thread worsens the performance significantly due to cache misses (factor 5)
+ private final ConcurrentHashMap sharedMap;
+
+ public FastAreaLazy(ConcurrentHashMap sharedMap, AreaTransformer8 transformer) {
+ this.sharedMap = sharedMap;
+ this.transformer = transformer;
+ }
+
+ @Override
+ public int a(int x, int z) {
+ long zx = ChunkCoordIntPair.pair(x, z);
+ return this.sharedMap.computeIfAbsent(zx, i -> this.transformer.apply(x, z));
+ }
+ }
+
+ private static class RegenNoOpWorldLoadListener implements WorldLoadListener {
+
+ private RegenNoOpWorldLoadListener() {
+ }
+
+ @Override
+ public void a(ChunkCoordIntPair chunkCoordIntPair) {
+ }
+
+ @Override
+ public void a(ChunkCoordIntPair chunkCoordIntPair, @Nullable ChunkStatus chunkStatus) {
+ }
+
+ @Override
+ public void b() {
+ }
+ }
+}
diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R2.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R2.java
new file mode 100644
index 000000000..af58d56cf
--- /dev/null
+++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R2.java
@@ -0,0 +1,529 @@
+package com.sk89q.worldedit.bukkit.adapter.impl.regen;
+
+import com.boydti.fawe.Fawe;
+import com.boydti.fawe.beta.IChunkCache;
+import com.boydti.fawe.beta.IChunkGet;
+import com.boydti.fawe.bukkit.adapter.mc1_16_2.BukkitGetBlocks_1_16_2;
+import com.google.common.collect.ImmutableList;
+import com.mojang.datafixers.util.Either;
+import com.mojang.serialization.Dynamic;
+import com.mojang.serialization.Lifecycle;
+import com.sk89q.worldedit.bukkit.adapter.Regenerator;
+import com.sk89q.worldedit.extent.Extent;
+import com.sk89q.worldedit.regions.Region;
+import com.sk89q.worldedit.util.io.file.SafeFiles;
+import com.sk89q.worldedit.world.RegenOptions;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BooleanSupplier;
+import java.util.function.LongFunction;
+import java.util.function.Supplier;
+import javax.annotation.Nullable;
+import net.minecraft.server.v1_16_R2.Area;
+import net.minecraft.server.v1_16_R2.AreaContextTransformed;
+import net.minecraft.server.v1_16_R2.AreaFactory;
+import net.minecraft.server.v1_16_R2.AreaTransformer8;
+import net.minecraft.server.v1_16_R2.BiomeBase;
+import net.minecraft.server.v1_16_R2.BiomeRegistry;
+import net.minecraft.server.v1_16_R2.Chunk;
+import net.minecraft.server.v1_16_R2.ChunkConverter;
+import net.minecraft.server.v1_16_R2.ChunkCoordIntPair;
+import net.minecraft.server.v1_16_R2.ChunkGenerator;
+import net.minecraft.server.v1_16_R2.ChunkGeneratorAbstract;
+import net.minecraft.server.v1_16_R2.ChunkProviderFlat;
+import net.minecraft.server.v1_16_R2.ChunkProviderServer;
+import net.minecraft.server.v1_16_R2.ChunkStatus;
+import net.minecraft.server.v1_16_R2.Convertable;
+import net.minecraft.server.v1_16_R2.DefinedStructureManager;
+import net.minecraft.server.v1_16_R2.DynamicOpsNBT;
+import net.minecraft.server.v1_16_R2.GenLayer;
+import net.minecraft.server.v1_16_R2.GenLayers;
+import net.minecraft.server.v1_16_R2.GeneratorSettingBase;
+import net.minecraft.server.v1_16_R2.GeneratorSettings;
+import net.minecraft.server.v1_16_R2.GeneratorSettingsFlat;
+import net.minecraft.server.v1_16_R2.IChunkAccess;
+import net.minecraft.server.v1_16_R2.IRegistry;
+import net.minecraft.server.v1_16_R2.IRegistryCustom;
+import net.minecraft.server.v1_16_R2.LightEngineThreaded;
+import net.minecraft.server.v1_16_R2.LinearCongruentialGenerator;
+import net.minecraft.server.v1_16_R2.MinecraftServer;
+import net.minecraft.server.v1_16_R2.NBTBase;
+import net.minecraft.server.v1_16_R2.NBTTagCompound;
+import net.minecraft.server.v1_16_R2.NoiseGeneratorPerlin;
+import net.minecraft.server.v1_16_R2.ProtoChunk;
+import net.minecraft.server.v1_16_R2.RegistryReadOps;
+import net.minecraft.server.v1_16_R2.ResourceKey;
+import net.minecraft.server.v1_16_R2.World;
+import net.minecraft.server.v1_16_R2.WorldChunkManager;
+import net.minecraft.server.v1_16_R2.WorldChunkManagerOverworld;
+import net.minecraft.server.v1_16_R2.WorldDataServer;
+import net.minecraft.server.v1_16_R2.WorldDimension;
+import net.minecraft.server.v1_16_R2.WorldLoadListener;
+import net.minecraft.server.v1_16_R2.WorldServer;
+import net.minecraft.server.v1_16_R2.WorldSettings;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_16_R2.CraftServer;
+import org.bukkit.craftbukkit.v1_16_R2.CraftWorld;
+import org.bukkit.craftbukkit.v1_16_R2.generator.CustomChunkGenerator;
+import org.bukkit.generator.BlockPopulator;
+
+public class Regen_v1_16_R2 extends Regenerator {
+
+ private static final Field serverWorldsField;
+ private static final Field worldPaperConfigField;
+ private static final Field flatBedrockField;
+ private static final Field generatorSettingBaseSupplierField;
+ private static final Field generatorSettingFlatField;
+ private static final Field delegateField;
+ private static final Field chunkProviderField;
+
+ //list of chunk stati in correct order without FULL
+ private static final Map chunkStati = new LinkedHashMap<>();
+
+ static {
+ chunkStati.put(ChunkStatus.EMPTY, Regenerator.Concurrency.FULL); // radius -1, does nothing
+ chunkStati.put(ChunkStatus.STRUCTURE_STARTS, Regenerator.Concurrency.NONE); // uses unsynchronized maps
+ chunkStati.put(ChunkStatus.STRUCTURE_REFERENCES, Regenerator.Concurrency.FULL); // radius 8, but no writes to other chunks, only current chunk
+ chunkStati.put(ChunkStatus.BIOMES, Regenerator.Concurrency.FULL); // radius 0
+ chunkStati.put(ChunkStatus.NOISE, Regenerator.Concurrency.RADIUS); // radius 8
+ chunkStati.put(ChunkStatus.SURFACE, Regenerator.Concurrency.FULL); // radius 0
+ chunkStati.put(ChunkStatus.CARVERS, Regenerator.Concurrency.NONE); // radius 0, but RADIUS and FULL change results
+ chunkStati.put(ChunkStatus.LIQUID_CARVERS, Regenerator.Concurrency.NONE); // radius 0, but RADIUS and FULL change results
+ chunkStati.put(ChunkStatus.FEATURES, Regenerator.Concurrency.NONE); // uses unsynchronized maps
+ chunkStati.put(ChunkStatus.LIGHT, Regenerator.Concurrency.FULL); // radius 1, but no writes to other chunks, only current chunk
+ chunkStati.put(ChunkStatus.SPAWN, Regenerator.Concurrency.FULL); // radius 0
+ chunkStati.put(ChunkStatus.HEIGHTMAPS, Regenerator.Concurrency.FULL); // radius 0
+
+ try {
+ serverWorldsField = CraftServer.class.getDeclaredField("worlds");
+ serverWorldsField.setAccessible(true);
+
+ Field tmpPaperConfigField = null;
+ Field tmpFlatBedrockField = null;
+ try { //only present on paper
+ tmpPaperConfigField = World.class.getDeclaredField("paperConfig");
+ tmpPaperConfigField.setAccessible(true);
+
+ tmpFlatBedrockField = tmpPaperConfigField.getType().getDeclaredField("generateFlatBedrock");
+ tmpFlatBedrockField.setAccessible(true);
+ } catch (Exception e) {
+ tmpPaperConfigField = null;
+ tmpFlatBedrockField = null;
+ }
+ worldPaperConfigField = tmpPaperConfigField;
+ flatBedrockField = tmpFlatBedrockField;
+
+ generatorSettingBaseSupplierField = ChunkGeneratorAbstract.class.getDeclaredField("h");
+ generatorSettingBaseSupplierField.setAccessible(true);
+
+ generatorSettingFlatField = ChunkProviderFlat.class.getDeclaredField("e");
+ generatorSettingFlatField.setAccessible(true);
+
+ delegateField = CustomChunkGenerator.class.getDeclaredField("delegate");
+ delegateField.setAccessible(true);
+
+ chunkProviderField = WorldServer.class.getDeclaredField("chunkProvider");
+ chunkProviderField.setAccessible(true);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ //runtime
+ private WorldServer originalNMSWorld;
+ private ChunkProviderServer originalChunkProvider;
+ private WorldServer freshNMSWorld;
+ private ChunkProviderServer freshChunkProvider;
+ private Convertable.ConversionSession session;
+ private DefinedStructureManager structureManager;
+ private LightEngineThreaded lightEngine;
+ private ChunkGenerator generator;
+
+ private Path tempDir;
+
+ private boolean generateFlatBedrock = false;
+
+ public Regen_v1_16_R2(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) {
+ super(originalBukkitWorld, region, target, options);
+ }
+
+ @Override
+ protected boolean prepare() {
+ this.originalNMSWorld = ((CraftWorld) originalBukkitWorld).getHandle();
+ originalChunkProvider = originalNMSWorld.getChunkProvider();
+ if (!(originalChunkProvider instanceof ChunkProviderServer)) {
+ return false;
+ }
+
+ //flat bedrock? (only on paper)
+ try {
+ generateFlatBedrock = flatBedrockField.getBoolean(worldPaperConfigField.get(originalNMSWorld));
+ } catch (Exception ignored) {
+ }
+
+ seed = options.getSeed().orElse(originalNMSWorld.getSeed());
+ chunkStati.forEach((s, c) -> super.chunkStati.put(new ChunkStatusWrap(s), c));
+
+ return true;
+ }
+
+ @Override
+ protected boolean initNewWorld() throws Exception {
+ //world folder
+ tempDir = java.nio.file.Files.createTempDirectory("WorldEditWorldGen");
+
+ //prepare for world init (see upstream implementation for reference)
+ org.bukkit.World.Environment env = originalBukkitWorld.getEnvironment();
+ org.bukkit.generator.ChunkGenerator gen = originalBukkitWorld.getGenerator();
+ Convertable convertable = Convertable.a(tempDir);
+ ResourceKey worldDimKey = getWorldDimKey(env);
+ session = convertable.c("worldeditregentempworld", worldDimKey);
+ WorldDataServer originalWorldData = originalNMSWorld.worldDataServer;
+
+ MinecraftServer server = originalNMSWorld.getServer().getServer();
+ WorldDataServer levelProperties = (WorldDataServer) server.getSaveData();
+ RegistryReadOps nbtRegOps = RegistryReadOps.a(DynamicOpsNBT.a, server.dataPackResources.h(), IRegistryCustom.b());
+ GeneratorSettings newOpts = GeneratorSettings.a.encodeStart(nbtRegOps, levelProperties.getGeneratorSettings()).flatMap(tag -> GeneratorSettings.a.parse(this.recursivelySetSeed(new Dynamic<>(nbtRegOps, tag), seed, new HashSet<>()))).result().orElseThrow(() -> new IllegalStateException("Unable to map GeneratorOptions"));
+ WorldSettings newWorldSettings = new WorldSettings("worldeditregentempworld", originalWorldData.b.getGameType(), originalWorldData.b.hardcore, originalWorldData.b.getDifficulty(), originalWorldData.b.e(), originalWorldData.b.getGameRules(), originalWorldData.b.g());
+ WorldDataServer newWorldData = new WorldDataServer(newWorldSettings, newOpts, Lifecycle.stable());
+
+ //init world
+ freshNMSWorld = Fawe.get().getQueueHandler().sync((Supplier) () -> new WorldServer(server, server.executorService, session, newWorldData, originalNMSWorld.getDimensionKey(), originalNMSWorld.getDimensionManager(), new RegenNoOpWorldLoadListener(), ((WorldDimension) newOpts.d().a(worldDimKey)).c(), originalNMSWorld.isDebugWorld(), seed, ImmutableList.of(), false, env, gen) {
+ @Override
+ public void doTick(BooleanSupplier booleansupplier) { //no ticking
+ }
+ }).get();
+ freshNMSWorld.savingDisabled = true;
+ removeWorldFromWorldsMap();
+ newWorldData.checkName(originalNMSWorld.worldDataServer.getName()); //rename to original world name
+
+ freshChunkProvider = new ChunkProviderServer(freshNMSWorld, session, server.getDataFixer(), server.getDefinedStructureManager(), server.executorService, originalChunkProvider.chunkGenerator, freshNMSWorld.spigotConfig.viewDistance, server.isSyncChunkWrites(), new RegenNoOpWorldLoadListener(), () -> server.E().getWorldPersistentData()) {
+ // redirect to our protoChunks list
+ @Override
+ public IChunkAccess getChunkAt(int x, int z, ChunkStatus chunkstatus, boolean flag) {
+ return getProtoChunkAt(x, z);
+ }
+ };
+ chunkProviderField.set(freshNMSWorld, freshChunkProvider);
+
+ //generator
+ if (originalChunkProvider.getChunkGenerator() instanceof ChunkProviderFlat) {
+ GeneratorSettingsFlat generatorSettingFlat = (GeneratorSettingsFlat) generatorSettingFlatField.get(originalChunkProvider.getChunkGenerator());
+ generator = new ChunkProviderFlat(generatorSettingFlat);
+ } else if (originalChunkProvider.getChunkGenerator() instanceof ChunkGeneratorAbstract) {
+ Supplier generatorSettingBaseSupplier = (Supplier) generatorSettingBaseSupplierField.get(originalChunkProvider.getChunkGenerator());
+ WorldChunkManager chunkManager = originalChunkProvider.getChunkGenerator().getWorldChunkManager();
+ if (chunkManager instanceof WorldChunkManagerOverworld) {
+ chunkManager = fastOverWorldChunkManager(chunkManager);
+ }
+ generator = new ChunkGeneratorAbstract(chunkManager, seed, generatorSettingBaseSupplier);
+ } else if (originalChunkProvider.getChunkGenerator() instanceof CustomChunkGenerator) {
+ ChunkGenerator delegate = (ChunkGenerator) delegateField.get(originalChunkProvider.getChunkGenerator());
+ generator = delegate;
+ } else {
+ System.out.println("Unsupported generator type " + originalChunkProvider.getChunkGenerator().getClass().getName());
+ return false;
+ }
+ if (originalNMSWorld.generator != null) {
+ // wrap custom world generator
+ generator = new CustomChunkGenerator(freshNMSWorld, generator, originalNMSWorld.generator);
+ generateConcurrent = originalNMSWorld.generator.isParallelCapable();
+ }
+
+ //lets start then
+ structureManager = server.getDefinedStructureManager();
+ lightEngine = freshChunkProvider.getLightEngine();
+
+ return true;
+ }
+
+ @Override
+ protected void cleanup() {
+ try {
+ session.close();
+ } catch (Exception e) {
+ }
+
+ //shutdown chunk provider
+ try {
+ Fawe.get().getQueueHandler().sync(() -> {
+ try {
+ freshChunkProvider.close(false);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ } catch (Exception e) {
+ }
+
+ //remove world from server
+ try {
+ Fawe.get().getQueueHandler().sync(() -> {
+ removeWorldFromWorldsMap();
+ });
+ } catch (Exception e) {
+ }
+
+ //delete directory
+ try {
+ SafeFiles.tryHardToDeleteDir(tempDir);
+ } catch (Exception e) {
+ }
+ }
+
+ @Override
+ protected ProtoChunk createProtoChunk(int x, int z) {
+ return new ProtoChunk(new ChunkCoordIntPair(x, z), ChunkConverter.a) {
+ public boolean generateFlatBedrock() {
+ return generateFlatBedrock;
+ }
+ };
+ }
+
+ @Override
+ protected Chunk createChunk(ProtoChunk protoChunk) {
+ return new Chunk(freshNMSWorld, protoChunk);
+ }
+
+ @Override
+ protected ChunkStatusWrap getFullChunkStatus() {
+ return new ChunkStatusWrap(ChunkStatus.FULL);
+ }
+
+ @Override
+ protected List getBlockPopulators() {
+ return originalNMSWorld.getWorld().getPopulators();
+ }
+
+ @Override
+ protected void populate(Chunk chunk, Random random, BlockPopulator pop) {
+ pop.populate(freshNMSWorld.getWorld(), random, chunk.bukkitChunk);
+ }
+
+ @Override
+ protected IChunkCache initSourceQueueCache() {
+ return (chunkX, chunkZ) -> new BukkitGetBlocks_1_16_2(freshNMSWorld, chunkX, chunkZ) {
+ @Override
+ public Chunk ensureLoaded(World nmsWorld, int x, int z) {
+ return getChunkAt(x, z);
+ }
+ };
+ }
+
+ protected class ChunkStatusWrap extends Regenerator.ChunkStatusWrapper {
+
+ private final ChunkStatus chunkStatus;
+
+ public ChunkStatusWrap(ChunkStatus chunkStatus) {
+ this.chunkStatus = chunkStatus;
+ }
+
+ @Override
+ public int requiredNeigborChunkRadius() {
+ return chunkStatus.f();
+ }
+
+ @Override
+ public String name() {
+ return chunkStatus.d();
+ }
+
+ @Override
+ public void processChunk(Long xz, List accessibleChunks) {
+ chunkStatus.a(freshNMSWorld,
+ generator,
+ structureManager,
+ lightEngine,
+ c -> CompletableFuture.completedFuture(Either.left(c)),
+ accessibleChunks);
+ }
+ }
+
+ //util
+ private void removeWorldFromWorldsMap() {
+ Fawe.get().getQueueHandler().sync(() -> {
+ try {
+ Map map = (Map) serverWorldsField.get(Bukkit.getServer());
+ map.remove("worldeditregentempworld");
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ private ResourceKey getWorldDimKey(org.bukkit.World.Environment env) {
+ switch (env) {
+ case NETHER:
+ return WorldDimension.THE_NETHER;
+ case THE_END:
+ return WorldDimension.THE_END;
+ case NORMAL:
+ default:
+ return WorldDimension.OVERWORLD;
+ }
+ }
+
+ private Dynamic recursivelySetSeed(Dynamic dynamic, long seed, Set> seen) {
+ return !seen.add(dynamic) ? dynamic : dynamic.updateMapValues((pair) -> {
+ if (((Dynamic) pair.getFirst()).asString("").equals("seed")) {
+ return pair.mapSecond((v) -> {
+ return v.createLong(seed);
+ });
+ } else {
+ return ((Dynamic) pair.getSecond()).getValue() instanceof NBTTagCompound ? pair.mapSecond((v) -> {
+ return this.recursivelySetSeed((Dynamic) v, seed, seen);
+ }) : pair;
+
+ }
+ });
+ }
+
+ private WorldChunkManager fastOverWorldChunkManager(WorldChunkManager chunkManager) throws Exception {
+ Field legacyBiomeInitLayerField = WorldChunkManagerOverworld.class.getDeclaredField("i");
+ legacyBiomeInitLayerField.setAccessible(true);
+ Field largeBiomesField = WorldChunkManagerOverworld.class.getDeclaredField("j");
+ largeBiomesField.setAccessible(true);
+ Field biomeRegistryField = WorldChunkManagerOverworld.class.getDeclaredField("k");
+ biomeRegistryField.setAccessible(true);
+ Field genLayerField = WorldChunkManagerOverworld.class.getDeclaredField("f");
+ genLayerField.setAccessible(true);
+ Field areaLazyField = GenLayer.class.getDeclaredField("b");
+ areaLazyField.setAccessible(true);
+ Method initAreaFactoryMethod = GenLayers.class.getDeclaredMethod("a", boolean.class, int.class, int.class, LongFunction.class);
+ initAreaFactoryMethod.setAccessible(true);
+
+ //init new WorldChunkManagerOverworld
+ boolean legacyBiomeInitLayer = legacyBiomeInitLayerField.getBoolean(chunkManager);
+ boolean largebiomes = largeBiomesField.getBoolean(chunkManager);
+ IRegistry biomeRegistry = (IRegistry) biomeRegistryField.get(chunkManager);
+ chunkManager = new WorldChunkManagerOverworld(seed, legacyBiomeInitLayer, largebiomes, biomeRegistry);
+
+ //replace genLayer
+ AreaFactory factory = (AreaFactory) initAreaFactoryMethod.invoke(null, legacyBiomeInitLayer, largebiomes ? 6 : 4, 4, (LongFunction) (l -> new FastWorldGenContextArea(seed, l)));
+ genLayerField.set(chunkManager, new FastGenLayer(factory));
+
+ return chunkManager;
+ }
+
+ private static class FastWorldGenContextArea implements AreaContextTransformed {
+
+ private final ConcurrentHashMap sharedAreaMap = new ConcurrentHashMap<>();
+ private final NoiseGeneratorPerlin perlinNoise;
+ private final long magicrandom;
+ private final ConcurrentHashMap map = new ConcurrentHashMap<>(); //needed for multithreaded generation
+
+ public FastWorldGenContextArea(long seed, long lconst) {
+ this.magicrandom = mix(seed, lconst);
+ this.perlinNoise = new NoiseGeneratorPerlin(new Random(seed));
+ }
+
+ @Override
+ public FastAreaLazy a(AreaTransformer8 var0) {
+ return new FastAreaLazy(sharedAreaMap, var0);
+ }
+
+ @Override
+ public void a(long x, long z) {
+ long l = this.magicrandom;
+ l = LinearCongruentialGenerator.a(l, x);
+ l = LinearCongruentialGenerator.a(l, z);
+ l = LinearCongruentialGenerator.a(l, x);
+ l = LinearCongruentialGenerator.a(l, z);
+ this.map.put(Thread.currentThread().getId(), l);
+ }
+
+ @Override
+ public int a(int y) {
+ long tid = Thread.currentThread().getId();
+ long e = this.map.computeIfAbsent(tid, i -> 0L);
+ int mod = (int) Math.floorMod(e >> 24L, (long) y);
+ this.map.put(tid, LinearCongruentialGenerator.a(e, this.magicrandom));
+ return mod;
+ }
+
+ @Override
+ public NoiseGeneratorPerlin b() {
+ return this.perlinNoise;
+ }
+
+ private static long mix(long seed, long lconst) {
+ long l1 = lconst;
+ l1 = LinearCongruentialGenerator.a(l1, lconst);
+ l1 = LinearCongruentialGenerator.a(l1, lconst);
+ l1 = LinearCongruentialGenerator.a(l1, lconst);
+ long l2 = seed;
+ l2 = LinearCongruentialGenerator.a(l2, l1);
+ l2 = LinearCongruentialGenerator.a(l2, l1);
+ l2 = LinearCongruentialGenerator.a(l2, l1);
+ return l2;
+ }
+ }
+
+ private static class FastGenLayer extends GenLayer {
+
+ private final FastAreaLazy areaLazy;
+
+ public FastGenLayer(AreaFactory factory) throws Exception {
+ super(() -> null);
+ this.areaLazy = factory.make();
+ }
+
+ @Override
+ public BiomeBase a(IRegistry registry, int x, int z) {
+ ResourceKey key = BiomeRegistry.a(this.areaLazy.a(x, z));
+ if (key == null)
+ return registry.a(BiomeRegistry.a(0));
+ BiomeBase biome = registry.a(key);
+ if (biome == null)
+ return registry.a(BiomeRegistry.a(0));
+ return biome;
+ }
+ }
+
+ private static class FastAreaLazy implements Area {
+
+ private final AreaTransformer8 transformer;
+ //ConcurrentHashMap is 50% faster that Long2IntLinkedOpenHashMap in a syncronized context
+ //using a map for each thread worsens the performance significantly due to cache misses (factor 5)
+ private final ConcurrentHashMap sharedMap;
+
+ public FastAreaLazy(ConcurrentHashMap sharedMap, AreaTransformer8 transformer) {
+ this.sharedMap = sharedMap;
+ this.transformer = transformer;
+ }
+
+ @Override
+ public int a(int x, int z) {
+ long zx = ChunkCoordIntPair.pair(x, z);
+ return this.sharedMap.computeIfAbsent(zx, i -> this.transformer.apply(x, z));
+ }
+ }
+
+ private static class RegenNoOpWorldLoadListener implements WorldLoadListener {
+
+ private RegenNoOpWorldLoadListener() {
+ }
+
+ @Override
+ public void a(ChunkCoordIntPair chunkCoordIntPair) {
+ }
+
+ @Override
+ public void a(ChunkCoordIntPair chunkCoordIntPair, @Nullable ChunkStatus chunkStatus) {
+ }
+
+ @Override
+ public void b() {
+ }
+ }
+}
diff --git a/worldedit-bukkit/src/main/resources/worldedit-adapters.jar b/worldedit-bukkit/src/main/resources/worldedit-adapters.jar
index 11e34adbe..d86472521 100644
Binary files a/worldedit-bukkit/src/main/resources/worldedit-adapters.jar and b/worldedit-bukkit/src/main/resources/worldedit-adapters.jar differ
diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharBlocks.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharBlocks.java
index a94cc84f8..c62207a1a 100644
--- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharBlocks.java
+++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharBlocks.java
@@ -21,7 +21,7 @@ public abstract class CharBlocks implements IBlocks {
};
public static final Section EMPTY = new Section() {
@Override
- public final char[] get(CharBlocks blocks, int layer) {
+ public final synchronized char[] get(CharBlocks blocks, int layer) {
char[] arr = blocks.blocks[layer];
if (arr == null) {
arr = blocks.blocks[layer] = blocks.update(layer, null);
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java
index c8f5005a9..d357d111d 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java
@@ -167,6 +167,7 @@ public class RegionCommands {
@Command(
name = "/removelighting",
+ aliases = "/removelight",
desc = "Removing lighting in a selection"
)
@CommandPermissions("worldedit.light.remove")
@@ -203,6 +204,7 @@ public class RegionCommands {
@Command(
name = "/setblocklight",
+ aliases = "/setlight",
desc = "Set block lighting in a selection"
)
@CommandPermissions("worldedit.light.set")
@@ -614,6 +616,7 @@ public class RegionCommands {
try {
session.setMask((Mask) null);
session.setSourceMask((Mask) null);
+ actor.printInfo(TranslatableComponent.of("fawe.regen.time"));
success = world.regenerate(region, editSession);
} finally {
session.setMask(mask);
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java
index 2bb552c74..c221acb6b 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java
@@ -850,7 +850,7 @@ public final class PlatformCommandManager {
event.setSuggestions(suggestions.stream()
.map(suggestion -> {
- int noSlashLength = arguments.length() - 1;
+ int noSlashLength = arguments.length();
Substring original = suggestion.getReplacedArgument() == split.size()
? Substring.from(arguments, noSlashLength, noSlashLength)
: split.get(suggestion.getReplacedArgument());
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/binding/ConsumeBindings.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/binding/ConsumeBindings.java
index 42149269d..61e855538 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/binding/ConsumeBindings.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/binding/ConsumeBindings.java
@@ -3,17 +3,14 @@ package com.sk89q.worldedit.extension.platform.binding;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.config.Caption;
import com.boydti.fawe.util.MainUtil;
-import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.image.ImageUtil;
import com.sk89q.worldedit.WorldEdit;
-import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.command.util.annotation.Confirm;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.NoMatchException;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.extension.platform.Actor;
-import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.extension.platform.PlatformCommandManager;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.annotation.Selection;
@@ -23,20 +20,13 @@ import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Identifiable;
-import com.sk89q.worldedit.util.TreeGenerator;
-import com.sk89q.worldedit.util.TreeGenerator.TreeType;
import com.sk89q.worldedit.world.World;
-import com.sk89q.worldedit.world.biome.BiomeType;
-import com.sk89q.worldedit.world.biome.BiomeTypes;
-import com.sk89q.worldedit.world.biome.Biomes;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockType;
-import com.sk89q.worldedit.world.registry.BiomeRegistry;
import org.enginehub.piston.inject.InjectedValueAccess;
-import java.util.Collection;
import java.util.UUID;
public class ConsumeBindings extends Bindings {
@@ -167,58 +157,4 @@ public class ConsumeBindings extends Bindings {
throw new InputParseException(e.getMessage());
}
}
-
- /**
- * Gets an {@link TreeType} from a {@link Binding}.
- *
- * @param argument the context
- * @return a TreeType
- * @throws WorldEditException on error
- */
- @Binding
- public TreeGenerator.TreeType getTreeType(String argument) throws WorldEditException {
- if (argument != null) {
- TreeGenerator.TreeType type = TreeGenerator.lookup(argument);
- if (type != null) {
- return type;
- } else {
- throw new InputParseException(
- String.format("Can't recognize tree type '%s' -- choose from: %s", argument,
- TreeGenerator.TreeType.getPrimaryAliases()));
- }
- } else {
- return TreeGenerator.TreeType.TREE;
- }
- }
-
- /**
- * Gets an {@link BiomeType} from a {@link Binding}.
- *
- * @param argument the context
- * @return a BiomeType
- * @throws WorldEditException on error
- */
- @Binding
- public BiomeType getBiomeType(String argument) throws WorldEditException {
- if (argument != null) {
-
- if (MathMan.isInteger(argument)) {
- return BiomeTypes.getLegacy(Integer.parseInt(argument));
- }
- BiomeRegistry biomeRegistry = WorldEdit.getInstance().getPlatformManager()
- .queryCapability(Capability.GAME_HOOKS).getRegistries().getBiomeRegistry();
- Collection knownBiomes = BiomeType.REGISTRY.values();
- BiomeType biome = Biomes.findBiomeByName(knownBiomes, argument, biomeRegistry);
- if (biome != null) {
- return biome;
- } else {
- throw new InputParseException(
- String.format("Can't recognize biome type '%s' -- use /biomelist to list available types", argument));
- }
- } else {
- throw new InputParseException(
- "This command takes a 'default' biome if one is not set, except there is no particular " +
- "biome that should be 'default', so the command should not be taking a default biome");
- }
- }
}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java
index 21b2cd39f..1c9348fa9 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java
@@ -186,7 +186,7 @@ public interface Extent extends InputExtent, OutputExtent {
}
default boolean regenerateChunk(int x, int z, @Nullable BiomeType type, @Nullable Long seed) {
- throw new UnsupportedOperationException("TODO NOT IMPLEMENTED: " + isWorld());
+ return false;
}
/*
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java
index 0ac331042..ae7fd5dcc 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java
@@ -382,10 +382,11 @@ public class ForwardExtentCopy implements Operation {
List extends Entity> entities;
if (copyingEntities) {
// filter players since they can't be copied
- entities = source.getEntities(region)
- .stream()
- .filter(e -> e.getType() != EntityTypes.PLAYER)
- .collect(Collectors.toList());
+ entities = source.getEntities(region);
+ entities.removeIf(entity -> {
+ EntityProperties properties = entity.getFacet(EntityProperties.class);
+ return properties != null && !properties.isPasteable();
+ });
} else {
entities = Collections.emptyList();
}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java
index 1d2ff9e97..747675232 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java
@@ -31,6 +31,7 @@ import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.entity.Player;
+import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.Vector3;
import com.sk89q.worldedit.regions.Region;
@@ -226,6 +227,10 @@ public class NullWorld extends AbstractWorld {
@Override
public void sendFakeChunk(@Nullable Player player, ChunkPacket packet) {
+ }
+ @Override
+ public boolean regenerate(Region region, Extent extent, RegenOptions options) {
+ return false;
}
}
diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java
index f0771063a..05ef1e5c7 100644
--- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java
+++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java
@@ -31,6 +31,8 @@ import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.platform.Platform;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.mask.Mask;
+import com.sk89q.worldedit.internal.util.DeprecationUtil;
+import com.sk89q.worldedit.internal.util.NonAbstractForCompatibility;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.Vector3;
@@ -218,7 +220,44 @@ public interface World extends Extent, Keyed, IChunkCache {
* @param editSession the {@link EditSession}
* @return true if re-generation was successful
*/
- boolean regenerate(Region region, EditSession editSession);
+ default boolean regenerate(Region region, EditSession editSession) {
+ return regenerate(region, editSession, RegenOptions.builder().build());
+ }
+
+ /**
+ * Regenerate an area.
+ *
+ * @param region the region
+ * @param extent the {@link Extent}
+ * @return true if re-generation was successful
+ */
+ default boolean regenerate(Region region, Extent extent) {
+ return regenerate(region, extent, RegenOptions.builder().build());
+ }
+
+ /**
+ * Regenerate an area.
+ *
+ * @param region the region
+ * @param extent the {@link Extent}
+ * @param options the regeneration options
+ * @return true if regeneration was successful
+ * @apiNote This must be overridden by new subclasses. See {@link NonAbstractForCompatibility}
+ * for details
+ */
+ @NonAbstractForCompatibility(
+ delegateName = "regenerate",
+ delegateParams = { Region.class, EditSession.class }
+ )
+ default boolean regenerate(Region region, Extent extent, RegenOptions options) {
+ DeprecationUtil.checkDelegatingOverride(getClass());
+ if (extent instanceof EditSession) {
+ return regenerate(region, (EditSession) extent);
+ }
+ throw new UnsupportedOperationException("This World class ("
+ + getClass().getName()
+ + ") does not implement the general Extent variant of this method");
+ }
/**
* Generate a tree at the given position.
diff --git a/worldedit-core/src/main/resources/lang/strings.json b/worldedit-core/src/main/resources/lang/strings.json
index de060a1ed..a074f2ed0 100644
--- a/worldedit-core/src/main/resources/lang/strings.json
+++ b/worldedit-core/src/main/resources/lang/strings.json
@@ -165,6 +165,8 @@
"fawe.tips.tip.biome.pattern": "Tip: The #biome[forest] pattern can be used in any command",
"fawe.tips.tip.biome.mask": "Tip: Restrict to a biome with the `$jungle` mask",
+ "fawe.regen.time": "Regenerating region, this might take a while!",
+
"worldedit.expand.description.vert": "Vertically expand the selection to world limits.",
"worldedit.expand.expanded": "Region expanded {0} blocks",
"worldedit.expand.expanded.vert": "Region expanded {0} blocks (top-to-bottom).",