mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2024-12-22 17:27:38 +00:00
Fix regen on modern versions (#2881)
* work on regen * simplify * fix more regen * cleanup, backport * revert unneeded change
This commit is contained in:
parent
1e8778b528
commit
e8c7d67b5b
@ -4,167 +4,74 @@ import com.fastasyncworldedit.bukkit.adapter.Regenerator;
|
|||||||
import com.fastasyncworldedit.core.Fawe;
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
import com.fastasyncworldedit.core.queue.IChunkCache;
|
import com.fastasyncworldedit.core.queue.IChunkCache;
|
||||||
import com.fastasyncworldedit.core.queue.IChunkGet;
|
import com.fastasyncworldedit.core.queue.IChunkGet;
|
||||||
import com.fastasyncworldedit.core.util.ReflectionUtils;
|
import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache;
|
||||||
import com.fastasyncworldedit.core.util.TaskManager;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.mojang.datafixers.util.Either;
|
|
||||||
import com.mojang.serialization.Lifecycle;
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.PaperweightGetBlocks;
|
|
||||||
import com.sk89q.worldedit.extent.Extent;
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
|
||||||
import com.sk89q.worldedit.regions.Region;
|
import com.sk89q.worldedit.regions.Region;
|
||||||
import com.sk89q.worldedit.util.io.file.SafeFiles;
|
import com.sk89q.worldedit.util.io.file.SafeFiles;
|
||||||
import com.sk89q.worldedit.world.RegenOptions;
|
import com.sk89q.worldedit.world.RegenOptions;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
|
|
||||||
import net.minecraft.core.Holder;
|
import net.minecraft.core.Holder;
|
||||||
import net.minecraft.core.Registry;
|
|
||||||
import net.minecraft.core.registries.Registries;
|
|
||||||
import net.minecraft.nbt.CompoundTag;
|
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.dedicated.DedicatedServer;
|
import net.minecraft.server.dedicated.DedicatedServer;
|
||||||
import net.minecraft.server.level.ChunkMap;
|
|
||||||
import net.minecraft.server.level.ChunkTaskPriorityQueueSorter.Message;
|
|
||||||
import net.minecraft.server.level.ServerChunkCache;
|
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.server.level.ThreadedLevelLightEngine;
|
|
||||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||||
import net.minecraft.util.thread.ProcessorHandle;
|
import net.minecraft.util.ProgressListener;
|
||||||
import net.minecraft.util.thread.ProcessorMailbox;
|
|
||||||
import net.minecraft.world.level.ChunkPos;
|
import net.minecraft.world.level.ChunkPos;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.LevelHeightAccessor;
|
|
||||||
import net.minecraft.world.level.LevelSettings;
|
import net.minecraft.world.level.LevelSettings;
|
||||||
import net.minecraft.world.level.biome.Biome;
|
import net.minecraft.world.level.biome.Biome;
|
||||||
import net.minecraft.world.level.biome.BiomeSource;
|
|
||||||
import net.minecraft.world.level.biome.FixedBiomeSource;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
|
||||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
|
||||||
import net.minecraft.world.level.chunk.UpgradeData;
|
|
||||||
import net.minecraft.world.level.dimension.LevelStem;
|
import net.minecraft.world.level.dimension.LevelStem;
|
||||||
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
|
||||||
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
||||||
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
|
|
||||||
import net.minecraft.world.level.levelgen.WorldOptions;
|
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||||
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
|
||||||
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
|
||||||
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
|
|
||||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
|
||||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Chunk;
|
import org.bukkit.World;
|
||||||
import org.bukkit.craftbukkit.v1_20_R2.CraftServer;
|
import org.bukkit.craftbukkit.v1_20_R2.CraftServer;
|
||||||
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
|
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
|
||||||
import org.bukkit.craftbukkit.v1_20_R2.generator.CustomChunkGenerator;
|
|
||||||
import org.bukkit.generator.BiomeProvider;
|
import org.bukkit.generator.BiomeProvider;
|
||||||
import org.bukkit.generator.BlockPopulator;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.OptionalLong;
|
import java.util.OptionalLong;
|
||||||
import java.util.Random;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import static net.minecraft.core.registries.Registries.BIOME;
|
import static net.minecraft.core.registries.Registries.BIOME;
|
||||||
|
|
||||||
public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, LevelChunk, PaperweightRegen.ChunkStatusWrap> {
|
public class PaperweightRegen extends Regenerator {
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
|
||||||
|
|
||||||
private static final Field serverWorldsField;
|
private static final Field serverWorldsField;
|
||||||
private static final Field paperConfigField;
|
private static final Field paperConfigField;
|
||||||
private static final Field flatBedrockField;
|
|
||||||
private static final Field generatorSettingFlatField;
|
|
||||||
private static final Field generatorSettingBaseSupplierField;
|
private static final Field generatorSettingBaseSupplierField;
|
||||||
private static final Field delegateField;
|
|
||||||
private static final Field chunkSourceField;
|
|
||||||
private static final Field generatorStructureStateField;
|
|
||||||
private static final Field ringPositionsField;
|
|
||||||
private static final Field hasGeneratedPositionsField;
|
|
||||||
|
|
||||||
//list of chunk stati in correct order without FULL
|
|
||||||
private static final Map<ChunkStatus, Concurrency> chunkStati = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
chunkStati.put(ChunkStatus.EMPTY, Concurrency.FULL); // empty: radius -1, does nothing
|
|
||||||
chunkStati.put(ChunkStatus.STRUCTURE_STARTS, Concurrency.NONE); // structure starts: uses unsynchronized maps
|
|
||||||
chunkStati.put(
|
|
||||||
ChunkStatus.STRUCTURE_REFERENCES,
|
|
||||||
Concurrency.FULL
|
|
||||||
); // structure refs: radius 8, but only writes to current chunk
|
|
||||||
chunkStati.put(ChunkStatus.BIOMES, Concurrency.FULL); // biomes: radius 0
|
|
||||||
chunkStati.put(ChunkStatus.NOISE, Concurrency.RADIUS); // noise: radius 8
|
|
||||||
chunkStati.put(ChunkStatus.SURFACE, Concurrency.NONE); // surface: radius 0, requires NONE
|
|
||||||
chunkStati.put(ChunkStatus.CARVERS, Concurrency.NONE); // carvers: radius 0, but RADIUS and FULL change results
|
|
||||||
/*chunkStati.put(
|
|
||||||
ChunkStatus.LIQUID_CARVERS,
|
|
||||||
Concurrency.NONE
|
|
||||||
); // liquid carvers: radius 0, but RADIUS and FULL change results*/
|
|
||||||
chunkStati.put(ChunkStatus.FEATURES, Concurrency.NONE); // features: uses unsynchronized maps
|
|
||||||
chunkStati.put(
|
|
||||||
ChunkStatus.LIGHT,
|
|
||||||
Concurrency.FULL
|
|
||||||
); // light: radius 1, but no writes to other chunks, only current chunk
|
|
||||||
chunkStati.put(ChunkStatus.SPAWN, Concurrency.FULL); // spawn: radius 0
|
|
||||||
// chunkStati.put(ChunkStatus.HEIGHTMAPS, Concurrency.FULL); // heightmaps: radius 0
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
serverWorldsField = CraftServer.class.getDeclaredField("worlds");
|
serverWorldsField = CraftServer.class.getDeclaredField("worlds");
|
||||||
serverWorldsField.setAccessible(true);
|
serverWorldsField.setAccessible(true);
|
||||||
|
|
||||||
Field tmpPaperConfigField;
|
Field tmpPaperConfigField;
|
||||||
Field tmpFlatBedrockField;
|
|
||||||
try { //only present on paper
|
try { //only present on paper
|
||||||
tmpPaperConfigField = Level.class.getDeclaredField("paperConfig");
|
tmpPaperConfigField = Level.class.getDeclaredField("paperConfig");
|
||||||
tmpPaperConfigField.setAccessible(true);
|
tmpPaperConfigField.setAccessible(true);
|
||||||
|
|
||||||
tmpFlatBedrockField = tmpPaperConfigField.getType().getDeclaredField("generateFlatBedrock");
|
|
||||||
tmpFlatBedrockField.setAccessible(true);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
tmpPaperConfigField = null;
|
tmpPaperConfigField = null;
|
||||||
tmpFlatBedrockField = null;
|
|
||||||
}
|
}
|
||||||
paperConfigField = tmpPaperConfigField;
|
paperConfigField = tmpPaperConfigField;
|
||||||
flatBedrockField = tmpFlatBedrockField;
|
|
||||||
|
|
||||||
generatorSettingBaseSupplierField = NoiseBasedChunkGenerator.class.getDeclaredField(Refraction.pickName(
|
generatorSettingBaseSupplierField = NoiseBasedChunkGenerator.class.getDeclaredField(Refraction.pickName(
|
||||||
"settings", "e"));
|
"settings", "e"));
|
||||||
generatorSettingBaseSupplierField.setAccessible(true);
|
generatorSettingBaseSupplierField.setAccessible(true);
|
||||||
|
|
||||||
generatorSettingFlatField = FlatLevelSource.class.getDeclaredField(Refraction.pickName("settings", "d"));
|
|
||||||
generatorSettingFlatField.setAccessible(true);
|
|
||||||
|
|
||||||
delegateField = CustomChunkGenerator.class.getDeclaredField("delegate");
|
|
||||||
delegateField.setAccessible(true);
|
|
||||||
|
|
||||||
chunkSourceField = ServerLevel.class.getDeclaredField(Refraction.pickName("chunkSource", "I"));
|
|
||||||
chunkSourceField.setAccessible(true);
|
|
||||||
|
|
||||||
generatorStructureStateField = ChunkMap.class.getDeclaredField(Refraction.pickName("chunkGeneratorState", "v"));
|
|
||||||
generatorStructureStateField.setAccessible(true);
|
|
||||||
|
|
||||||
ringPositionsField = ChunkGeneratorStructureState.class.getDeclaredField(Refraction.pickName("ringPositions", "g"));
|
|
||||||
ringPositionsField.setAccessible(true);
|
|
||||||
|
|
||||||
hasGeneratedPositionsField = ChunkGeneratorStructureState.class.getDeclaredField(
|
|
||||||
Refraction.pickName("hasGeneratedPositions", "h")
|
|
||||||
);
|
|
||||||
hasGeneratedPositionsField.setAccessible(true);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@ -172,43 +79,37 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
|
|
||||||
//runtime
|
//runtime
|
||||||
private ServerLevel originalServerWorld;
|
private ServerLevel originalServerWorld;
|
||||||
private ServerChunkCache originalChunkProvider;
|
|
||||||
private ServerLevel freshWorld;
|
private ServerLevel freshWorld;
|
||||||
private ServerChunkCache freshChunkProvider;
|
|
||||||
private LevelStorageSource.LevelStorageAccess session;
|
private LevelStorageSource.LevelStorageAccess session;
|
||||||
private StructureTemplateManager structureTemplateManager;
|
|
||||||
private ThreadedLevelLightEngine threadedLevelLightEngine;
|
|
||||||
private ChunkGenerator chunkGenerator;
|
|
||||||
|
|
||||||
private Path tempDir;
|
private Path tempDir;
|
||||||
|
|
||||||
private boolean generateFlatBedrock = false;
|
public PaperweightRegen(
|
||||||
|
World originalBukkitWorld,
|
||||||
public PaperweightRegen(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) {
|
Region region,
|
||||||
|
Extent target,
|
||||||
|
RegenOptions options
|
||||||
|
) {
|
||||||
super(originalBukkitWorld, region, target, options);
|
super(originalBukkitWorld, region, target, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void runTasks(final BooleanSupplier shouldKeepTicking) {
|
||||||
|
while (shouldKeepTicking.getAsBoolean()) {
|
||||||
|
if (!this.freshWorld.getChunkSource().pollTask()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean prepare() {
|
protected boolean prepare() {
|
||||||
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
||||||
originalChunkProvider = originalServerWorld.getChunkSource();
|
|
||||||
|
|
||||||
//flat bedrock? (only on paper)
|
|
||||||
if (paperConfigField != null) {
|
|
||||||
try {
|
|
||||||
generateFlatBedrock = flatBedrockField.getBoolean(paperConfigField.get(originalServerWorld));
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
||||||
chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c));
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected boolean initNewWorld() throws Exception {
|
protected boolean initNewWorld() throws Exception {
|
||||||
//world folder
|
//world folder
|
||||||
tempDir = java.nio.file.Files.createTempDirectory("FastAsyncWorldEditWorldGen");
|
tempDir = java.nio.file.Files.createTempDirectory("FastAsyncWorldEditWorldGen");
|
||||||
@ -254,8 +155,10 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
session,
|
session,
|
||||||
newWorldData,
|
newWorldData,
|
||||||
originalServerWorld.dimension(),
|
originalServerWorld.dimension(),
|
||||||
DedicatedServer.getServer().registryAccess().registry(Registries.LEVEL_STEM).orElseThrow()
|
new LevelStem(
|
||||||
.getOrThrow(levelStemResourceKey),
|
originalServerWorld.dimensionTypeRegistration(),
|
||||||
|
originalServerWorld.getChunkSource().getGenerator()
|
||||||
|
),
|
||||||
new RegenNoOpWorldLoadListener(),
|
new RegenNoOpWorldLoadListener(),
|
||||||
originalServerWorld.isDebug(),
|
originalServerWorld.isDebug(),
|
||||||
seed,
|
seed,
|
||||||
@ -273,17 +176,30 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(BooleanSupplier shouldKeepTicking) { //no ticking
|
public @NotNull Holder<Biome> getUncachedNoiseBiome(int biomeX, int biomeY, int biomeZ) {
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Holder<Biome> getUncachedNoiseBiome(int biomeX, int biomeY, int biomeZ) {
|
|
||||||
if (options.hasBiomeType()) {
|
if (options.hasBiomeType()) {
|
||||||
return singleBiome;
|
return singleBiome;
|
||||||
}
|
}
|
||||||
return PaperweightRegen.this.chunkGenerator.getBiomeSource().getNoiseBiome(
|
return super.getUncachedNoiseBiome(biomeX, biomeY, biomeZ);
|
||||||
biomeX, biomeY, biomeZ, getChunkSource().randomState().sampler()
|
}
|
||||||
);
|
|
||||||
|
@Override
|
||||||
|
public void save(
|
||||||
|
@org.jetbrains.annotations.Nullable final ProgressListener progressListener,
|
||||||
|
final boolean flush,
|
||||||
|
final boolean savingDisabled
|
||||||
|
) {
|
||||||
|
// noop, spigot
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(
|
||||||
|
@Nullable final ProgressListener progressListener,
|
||||||
|
final boolean flush,
|
||||||
|
final boolean savingDisabled,
|
||||||
|
final boolean close
|
||||||
|
) {
|
||||||
|
// noop, paper
|
||||||
}
|
}
|
||||||
}).get();
|
}).get();
|
||||||
freshWorld.noSave = true;
|
freshWorld.noSave = true;
|
||||||
@ -292,89 +208,6 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
if (paperConfigField != null) {
|
if (paperConfigField != null) {
|
||||||
paperConfigField.set(freshWorld, originalServerWorld.paperConfig());
|
paperConfigField.set(freshWorld, originalServerWorld.paperConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
ChunkGenerator originalGenerator = originalChunkProvider.getGenerator();
|
|
||||||
if (originalGenerator instanceof FlatLevelSource flatLevelSource) {
|
|
||||||
FlatLevelGeneratorSettings generatorSettingFlat = flatLevelSource.settings();
|
|
||||||
chunkGenerator = new FlatLevelSource(generatorSettingFlat);
|
|
||||||
} else if (originalGenerator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) {
|
|
||||||
Holder<NoiseGeneratorSettings> generatorSettingBaseSupplier = (Holder<NoiseGeneratorSettings>) generatorSettingBaseSupplierField.get(
|
|
||||||
originalGenerator);
|
|
||||||
BiomeSource biomeSource;
|
|
||||||
if (options.hasBiomeType()) {
|
|
||||||
|
|
||||||
biomeSource = new FixedBiomeSource(
|
|
||||||
DedicatedServer.getServer().registryAccess()
|
|
||||||
.registryOrThrow(BIOME).asHolderIdMap().byIdOrThrow(
|
|
||||||
WorldEditPlugin.getInstance().getBukkitImplAdapter().getInternalBiomeId(options.getBiomeType())
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
biomeSource = originalGenerator.getBiomeSource();
|
|
||||||
}
|
|
||||||
chunkGenerator = new NoiseBasedChunkGenerator(
|
|
||||||
biomeSource,
|
|
||||||
generatorSettingBaseSupplier
|
|
||||||
);
|
|
||||||
} else if (originalGenerator instanceof CustomChunkGenerator customChunkGenerator) {
|
|
||||||
chunkGenerator = customChunkGenerator.getDelegate();
|
|
||||||
} else {
|
|
||||||
LOGGER.error("Unsupported generator type {}", originalGenerator.getClass().getName());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (generator != null) {
|
|
||||||
chunkGenerator = new CustomChunkGenerator(freshWorld, chunkGenerator, generator);
|
|
||||||
generateConcurrent = generator.isParallelCapable();
|
|
||||||
}
|
|
||||||
// chunkGenerator.conf = freshWorld.spigotConfig; - Does not exist anymore, may need to be re-addressed
|
|
||||||
|
|
||||||
freshChunkProvider = new ServerChunkCache(
|
|
||||||
freshWorld,
|
|
||||||
session,
|
|
||||||
server.getFixerUpper(),
|
|
||||||
server.getStructureManager(),
|
|
||||||
server.executor,
|
|
||||||
chunkGenerator,
|
|
||||||
freshWorld.spigotConfig.viewDistance,
|
|
||||||
freshWorld.spigotConfig.simulationDistance,
|
|
||||||
server.forceSynchronousWrites(),
|
|
||||||
new RegenNoOpWorldLoadListener(),
|
|
||||||
(chunkCoordIntPair, state) -> {
|
|
||||||
},
|
|
||||||
() -> server.overworld().getDataStorage()
|
|
||||||
) {
|
|
||||||
// redirect to LevelChunks created in #createChunks
|
|
||||||
@Override
|
|
||||||
public ChunkAccess getChunk(int x, int z, ChunkStatus chunkstatus, boolean create) {
|
|
||||||
ChunkAccess chunkAccess = getChunkAt(x, z);
|
|
||||||
if (chunkAccess == null && create) {
|
|
||||||
chunkAccess = createChunk(getProtoChunkAt(x, z));
|
|
||||||
}
|
|
||||||
return chunkAccess;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (seed == originalOpts.seed() && !options.hasBiomeType()) {
|
|
||||||
// Optimisation for needless ring position calculation when the seed and biome is the same.
|
|
||||||
ChunkGeneratorStructureState state = (ChunkGeneratorStructureState) generatorStructureStateField.get(originalChunkProvider.chunkMap);
|
|
||||||
boolean hasGeneratedPositions = hasGeneratedPositionsField.getBoolean(state);
|
|
||||||
if (hasGeneratedPositions) {
|
|
||||||
Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> origPositions =
|
|
||||||
(Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>>) ringPositionsField.get(state);
|
|
||||||
Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> copy = new Object2ObjectArrayMap<>(
|
|
||||||
origPositions);
|
|
||||||
ChunkGeneratorStructureState newState = (ChunkGeneratorStructureState) generatorStructureStateField.get(freshChunkProvider.chunkMap);
|
|
||||||
ringPositionsField.set(newState, copy);
|
|
||||||
hasGeneratedPositionsField.setBoolean(newState, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
chunkSourceField.set(freshWorld, freshChunkProvider);
|
|
||||||
//let's start then
|
|
||||||
structureTemplateManager = server.getStructureManager();
|
|
||||||
threadedLevelLightEngine = new NoOpLightEngine(freshChunkProvider);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,7 +222,8 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
try {
|
try {
|
||||||
Fawe.instance().getQueueHandler().sync(() -> {
|
Fawe.instance().getQueueHandler().sync(() -> {
|
||||||
try {
|
try {
|
||||||
freshChunkProvider.close(false);
|
freshWorld.getChunkSource().getDataStorage().cache.clear();
|
||||||
|
freshWorld.getChunkSource().close(false);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@ -410,63 +244,20 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ProtoChunk createProtoChunk(int x, int z) {
|
|
||||||
return new FastProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, freshWorld,
|
|
||||||
this.freshWorld.registryAccess().registryOrThrow(BIOME), null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected LevelChunk createChunk(ProtoChunk protoChunk) {
|
|
||||||
return new LevelChunk(
|
|
||||||
freshWorld,
|
|
||||||
protoChunk,
|
|
||||||
null // we don't want to add entities
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ChunkStatusWrap getFullChunkStatus() {
|
|
||||||
return new ChunkStatusWrap(ChunkStatus.FULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<BlockPopulator> getBlockPopulators() {
|
|
||||||
return originalServerWorld.getWorld().getPopulators();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void populate(LevelChunk levelChunk, Random random, BlockPopulator blockPopulator) {
|
|
||||||
// BlockPopulator#populate has to be called synchronously for TileEntity access
|
|
||||||
TaskManager.taskManager().task(() -> {
|
|
||||||
final CraftWorld world = freshWorld.getWorld();
|
|
||||||
final Chunk chunk = world.getChunkAt(levelChunk.locX, levelChunk.locZ);
|
|
||||||
blockPopulator.populate(world, random, chunk);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IChunkCache<IChunkGet> initSourceQueueCache() {
|
protected IChunkCache<IChunkGet> initSourceQueueCache() {
|
||||||
return (chunkX, chunkZ) -> new PaperweightGetBlocks(freshWorld, chunkX, chunkZ) {
|
return new ChunkCache<>(BukkitAdapter.adapt(freshWorld.getWorld()));
|
||||||
@Override
|
|
||||||
public LevelChunk ensureLoaded(ServerLevel nmsWorld, int x, int z) {
|
|
||||||
return getChunkAt(x, z);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//util
|
//util
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void removeWorldFromWorldsMap() {
|
private void removeWorldFromWorldsMap() {
|
||||||
Fawe.instance().getQueueHandler().sync(() -> {
|
try {
|
||||||
try {
|
Map<String, org.bukkit.World> map = (Map<String, org.bukkit.World>) serverWorldsField.get(Bukkit.getServer());
|
||||||
Map<String, org.bukkit.World> map = (Map<String, org.bukkit.World>) serverWorldsField.get(Bukkit.getServer());
|
map.remove("faweregentempworld");
|
||||||
map.remove("faweregentempworld");
|
} catch (IllegalAccessException e) {
|
||||||
} catch (IllegalAccessException e) {
|
throw new RuntimeException(e);
|
||||||
throw new RuntimeException(e);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResourceKey<LevelStem> getWorldDimKey(org.bukkit.World.Environment env) {
|
private ResourceKey<LevelStem> getWorldDimKey(org.bukkit.World.Environment env) {
|
||||||
@ -483,11 +274,15 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateSpawnPos(ChunkPos spawnPos) {
|
public void updateSpawnPos(@NotNull ChunkPos spawnPos) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStatusChange(ChunkPos pos, @Nullable ChunkStatus status) {
|
public void onStatusChange(
|
||||||
|
final @NotNull ChunkPos pos,
|
||||||
|
@org.jetbrains.annotations.Nullable final ChunkStatus status
|
||||||
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -505,87 +300,4 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FastProtoChunk extends ProtoChunk {
|
|
||||||
|
|
||||||
public FastProtoChunk(
|
|
||||||
final ChunkPos pos,
|
|
||||||
final UpgradeData upgradeData,
|
|
||||||
final LevelHeightAccessor world,
|
|
||||||
final Registry<Biome> biomeRegistry,
|
|
||||||
@Nullable final BlendingData blendingData
|
|
||||||
) {
|
|
||||||
super(pos, upgradeData, world, biomeRegistry, blendingData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// avoid warning on paper
|
|
||||||
|
|
||||||
// compatibility with spigot
|
|
||||||
|
|
||||||
public boolean generateFlatBedrock() {
|
|
||||||
return generateFlatBedrock;
|
|
||||||
}
|
|
||||||
|
|
||||||
// no one will ever see the entities!
|
|
||||||
@Override
|
|
||||||
public List<CompoundTag> getEntities() {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class ChunkStatusWrap extends ChunkStatusWrapper<ChunkAccess> {
|
|
||||||
|
|
||||||
private final ChunkStatus chunkStatus;
|
|
||||||
|
|
||||||
public ChunkStatusWrap(ChunkStatus chunkStatus) {
|
|
||||||
this.chunkStatus = chunkStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int requiredNeighborChunkRadius() {
|
|
||||||
return chunkStatus.getRange();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String name() {
|
|
||||||
return chunkStatus.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<?> processChunk(List<ChunkAccess> accessibleChunks) {
|
|
||||||
return chunkStatus.generate(
|
|
||||||
Runnable::run, // TODO revisit, we might profit from this somehow?
|
|
||||||
freshWorld,
|
|
||||||
chunkGenerator,
|
|
||||||
structureTemplateManager,
|
|
||||||
threadedLevelLightEngine,
|
|
||||||
c -> CompletableFuture.completedFuture(Either.left(c)),
|
|
||||||
accessibleChunks
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A light engine that does nothing. As light is calculated after pasting anyway, we can avoid
|
|
||||||
* work this way.
|
|
||||||
*/
|
|
||||||
static class NoOpLightEngine extends ThreadedLevelLightEngine {
|
|
||||||
|
|
||||||
private static final ProcessorMailbox<Runnable> MAILBOX = ProcessorMailbox.create(task -> {
|
|
||||||
}, "fawe-no-op");
|
|
||||||
private static final ProcessorHandle<Message<Runnable>> HANDLE = ProcessorHandle.of("fawe-no-op", m -> {
|
|
||||||
});
|
|
||||||
|
|
||||||
public NoOpLightEngine(final ServerChunkCache chunkProvider) {
|
|
||||||
super(chunkProvider, chunkProvider.chunkMap, false, MAILBOX, HANDLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<ChunkAccess> lightChunk(final ChunkAccess chunk, final boolean excludeBlocks) {
|
|
||||||
return CompletableFuture.completedFuture(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,166 +4,74 @@ import com.fastasyncworldedit.bukkit.adapter.Regenerator;
|
|||||||
import com.fastasyncworldedit.core.Fawe;
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
import com.fastasyncworldedit.core.queue.IChunkCache;
|
import com.fastasyncworldedit.core.queue.IChunkCache;
|
||||||
import com.fastasyncworldedit.core.queue.IChunkGet;
|
import com.fastasyncworldedit.core.queue.IChunkGet;
|
||||||
import com.fastasyncworldedit.core.util.TaskManager;
|
import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.mojang.datafixers.util.Either;
|
|
||||||
import com.mojang.serialization.Lifecycle;
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R3.PaperweightGetBlocks;
|
|
||||||
import com.sk89q.worldedit.extent.Extent;
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
|
||||||
import com.sk89q.worldedit.regions.Region;
|
import com.sk89q.worldedit.regions.Region;
|
||||||
import com.sk89q.worldedit.util.io.file.SafeFiles;
|
import com.sk89q.worldedit.util.io.file.SafeFiles;
|
||||||
import com.sk89q.worldedit.world.RegenOptions;
|
import com.sk89q.worldedit.world.RegenOptions;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
|
|
||||||
import net.minecraft.core.Holder;
|
import net.minecraft.core.Holder;
|
||||||
import net.minecraft.core.Registry;
|
|
||||||
import net.minecraft.core.registries.Registries;
|
|
||||||
import net.minecraft.nbt.CompoundTag;
|
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.dedicated.DedicatedServer;
|
import net.minecraft.server.dedicated.DedicatedServer;
|
||||||
import net.minecraft.server.level.ChunkMap;
|
|
||||||
import net.minecraft.server.level.ChunkTaskPriorityQueueSorter.Message;
|
|
||||||
import net.minecraft.server.level.ServerChunkCache;
|
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.server.level.ThreadedLevelLightEngine;
|
|
||||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||||
import net.minecraft.util.thread.ProcessorHandle;
|
import net.minecraft.util.ProgressListener;
|
||||||
import net.minecraft.util.thread.ProcessorMailbox;
|
|
||||||
import net.minecraft.world.level.ChunkPos;
|
import net.minecraft.world.level.ChunkPos;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.LevelHeightAccessor;
|
|
||||||
import net.minecraft.world.level.LevelSettings;
|
import net.minecraft.world.level.LevelSettings;
|
||||||
import net.minecraft.world.level.biome.Biome;
|
import net.minecraft.world.level.biome.Biome;
|
||||||
import net.minecraft.world.level.biome.BiomeSource;
|
|
||||||
import net.minecraft.world.level.biome.FixedBiomeSource;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
|
||||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
|
||||||
import net.minecraft.world.level.chunk.UpgradeData;
|
|
||||||
import net.minecraft.world.level.dimension.LevelStem;
|
import net.minecraft.world.level.dimension.LevelStem;
|
||||||
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
|
||||||
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
||||||
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
|
|
||||||
import net.minecraft.world.level.levelgen.WorldOptions;
|
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||||
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
|
||||||
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
|
||||||
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
|
|
||||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
|
||||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Chunk;
|
import org.bukkit.World;
|
||||||
import org.bukkit.craftbukkit.v1_20_R3.CraftServer;
|
import org.bukkit.craftbukkit.v1_20_R3.CraftServer;
|
||||||
import org.bukkit.craftbukkit.v1_20_R3.CraftWorld;
|
import org.bukkit.craftbukkit.v1_20_R3.CraftWorld;
|
||||||
import org.bukkit.craftbukkit.v1_20_R3.generator.CustomChunkGenerator;
|
|
||||||
import org.bukkit.generator.BiomeProvider;
|
import org.bukkit.generator.BiomeProvider;
|
||||||
import org.bukkit.generator.BlockPopulator;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.OptionalLong;
|
import java.util.OptionalLong;
|
||||||
import java.util.Random;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import static net.minecraft.core.registries.Registries.BIOME;
|
import static net.minecraft.core.registries.Registries.BIOME;
|
||||||
|
|
||||||
public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, LevelChunk, PaperweightRegen.ChunkStatusWrap> {
|
public class PaperweightRegen extends Regenerator {
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
|
||||||
|
|
||||||
private static final Field serverWorldsField;
|
private static final Field serverWorldsField;
|
||||||
private static final Field paperConfigField;
|
private static final Field paperConfigField;
|
||||||
private static final Field flatBedrockField;
|
|
||||||
private static final Field generatorSettingFlatField;
|
|
||||||
private static final Field generatorSettingBaseSupplierField;
|
private static final Field generatorSettingBaseSupplierField;
|
||||||
private static final Field delegateField;
|
|
||||||
private static final Field chunkSourceField;
|
|
||||||
private static final Field generatorStructureStateField;
|
|
||||||
private static final Field ringPositionsField;
|
|
||||||
private static final Field hasGeneratedPositionsField;
|
|
||||||
|
|
||||||
//list of chunk stati in correct order without FULL
|
|
||||||
private static final Map<ChunkStatus, Concurrency> chunkStati = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
chunkStati.put(ChunkStatus.EMPTY, Concurrency.FULL); // empty: radius -1, does nothing
|
|
||||||
chunkStati.put(ChunkStatus.STRUCTURE_STARTS, Concurrency.NONE); // structure starts: uses unsynchronized maps
|
|
||||||
chunkStati.put(
|
|
||||||
ChunkStatus.STRUCTURE_REFERENCES,
|
|
||||||
Concurrency.FULL
|
|
||||||
); // structure refs: radius 8, but only writes to current chunk
|
|
||||||
chunkStati.put(ChunkStatus.BIOMES, Concurrency.FULL); // biomes: radius 0
|
|
||||||
chunkStati.put(ChunkStatus.NOISE, Concurrency.RADIUS); // noise: radius 8
|
|
||||||
chunkStati.put(ChunkStatus.SURFACE, Concurrency.NONE); // surface: radius 0, requires NONE
|
|
||||||
chunkStati.put(ChunkStatus.CARVERS, Concurrency.NONE); // carvers: radius 0, but RADIUS and FULL change results
|
|
||||||
/*chunkStati.put(
|
|
||||||
ChunkStatus.LIQUID_CARVERS,
|
|
||||||
Concurrency.NONE
|
|
||||||
); // liquid carvers: radius 0, but RADIUS and FULL change results*/
|
|
||||||
chunkStati.put(ChunkStatus.FEATURES, Concurrency.NONE); // features: uses unsynchronized maps
|
|
||||||
chunkStati.put(
|
|
||||||
ChunkStatus.LIGHT,
|
|
||||||
Concurrency.FULL
|
|
||||||
); // light: radius 1, but no writes to other chunks, only current chunk
|
|
||||||
chunkStati.put(ChunkStatus.SPAWN, Concurrency.FULL); // spawn: radius 0
|
|
||||||
// chunkStati.put(ChunkStatus.HEIGHTMAPS, Concurrency.FULL); // heightmaps: radius 0
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
serverWorldsField = CraftServer.class.getDeclaredField("worlds");
|
serverWorldsField = CraftServer.class.getDeclaredField("worlds");
|
||||||
serverWorldsField.setAccessible(true);
|
serverWorldsField.setAccessible(true);
|
||||||
|
|
||||||
Field tmpPaperConfigField;
|
Field tmpPaperConfigField;
|
||||||
Field tmpFlatBedrockField;
|
|
||||||
try { //only present on paper
|
try { //only present on paper
|
||||||
tmpPaperConfigField = Level.class.getDeclaredField("paperConfig");
|
tmpPaperConfigField = Level.class.getDeclaredField("paperConfig");
|
||||||
tmpPaperConfigField.setAccessible(true);
|
tmpPaperConfigField.setAccessible(true);
|
||||||
|
|
||||||
tmpFlatBedrockField = tmpPaperConfigField.getType().getDeclaredField("generateFlatBedrock");
|
|
||||||
tmpFlatBedrockField.setAccessible(true);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
tmpPaperConfigField = null;
|
tmpPaperConfigField = null;
|
||||||
tmpFlatBedrockField = null;
|
|
||||||
}
|
}
|
||||||
paperConfigField = tmpPaperConfigField;
|
paperConfigField = tmpPaperConfigField;
|
||||||
flatBedrockField = tmpFlatBedrockField;
|
|
||||||
|
|
||||||
generatorSettingBaseSupplierField = NoiseBasedChunkGenerator.class.getDeclaredField(Refraction.pickName(
|
generatorSettingBaseSupplierField = NoiseBasedChunkGenerator.class.getDeclaredField(Refraction.pickName(
|
||||||
"settings", "e"));
|
"settings", "e"));
|
||||||
generatorSettingBaseSupplierField.setAccessible(true);
|
generatorSettingBaseSupplierField.setAccessible(true);
|
||||||
|
|
||||||
generatorSettingFlatField = FlatLevelSource.class.getDeclaredField(Refraction.pickName("settings", "d"));
|
|
||||||
generatorSettingFlatField.setAccessible(true);
|
|
||||||
|
|
||||||
delegateField = CustomChunkGenerator.class.getDeclaredField("delegate");
|
|
||||||
delegateField.setAccessible(true);
|
|
||||||
|
|
||||||
chunkSourceField = ServerLevel.class.getDeclaredField(Refraction.pickName("chunkSource", "I"));
|
|
||||||
chunkSourceField.setAccessible(true);
|
|
||||||
|
|
||||||
generatorStructureStateField = ChunkMap.class.getDeclaredField(Refraction.pickName("chunkGeneratorState", "v"));
|
|
||||||
generatorStructureStateField.setAccessible(true);
|
|
||||||
|
|
||||||
ringPositionsField = ChunkGeneratorStructureState.class.getDeclaredField(Refraction.pickName("ringPositions", "g"));
|
|
||||||
ringPositionsField.setAccessible(true);
|
|
||||||
|
|
||||||
hasGeneratedPositionsField = ChunkGeneratorStructureState.class.getDeclaredField(
|
|
||||||
Refraction.pickName("hasGeneratedPositions", "h")
|
|
||||||
);
|
|
||||||
hasGeneratedPositionsField.setAccessible(true);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@ -171,43 +79,37 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
|
|
||||||
//runtime
|
//runtime
|
||||||
private ServerLevel originalServerWorld;
|
private ServerLevel originalServerWorld;
|
||||||
private ServerChunkCache originalChunkProvider;
|
|
||||||
private ServerLevel freshWorld;
|
private ServerLevel freshWorld;
|
||||||
private ServerChunkCache freshChunkProvider;
|
|
||||||
private LevelStorageSource.LevelStorageAccess session;
|
private LevelStorageSource.LevelStorageAccess session;
|
||||||
private StructureTemplateManager structureTemplateManager;
|
|
||||||
private ThreadedLevelLightEngine threadedLevelLightEngine;
|
|
||||||
private ChunkGenerator chunkGenerator;
|
|
||||||
|
|
||||||
private Path tempDir;
|
private Path tempDir;
|
||||||
|
|
||||||
private boolean generateFlatBedrock = false;
|
public PaperweightRegen(
|
||||||
|
World originalBukkitWorld,
|
||||||
public PaperweightRegen(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) {
|
Region region,
|
||||||
|
Extent target,
|
||||||
|
RegenOptions options
|
||||||
|
) {
|
||||||
super(originalBukkitWorld, region, target, options);
|
super(originalBukkitWorld, region, target, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void runTasks(final BooleanSupplier shouldKeepTicking) {
|
||||||
|
while (shouldKeepTicking.getAsBoolean()) {
|
||||||
|
if (!this.freshWorld.getChunkSource().pollTask()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean prepare() {
|
protected boolean prepare() {
|
||||||
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
||||||
originalChunkProvider = originalServerWorld.getChunkSource();
|
|
||||||
|
|
||||||
//flat bedrock? (only on paper)
|
|
||||||
if (paperConfigField != null) {
|
|
||||||
try {
|
|
||||||
generateFlatBedrock = flatBedrockField.getBoolean(paperConfigField.get(originalServerWorld));
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
||||||
chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c));
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected boolean initNewWorld() throws Exception {
|
protected boolean initNewWorld() throws Exception {
|
||||||
//world folder
|
//world folder
|
||||||
tempDir = java.nio.file.Files.createTempDirectory("FastAsyncWorldEditWorldGen");
|
tempDir = java.nio.file.Files.createTempDirectory("FastAsyncWorldEditWorldGen");
|
||||||
@ -253,8 +155,10 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
session,
|
session,
|
||||||
newWorldData,
|
newWorldData,
|
||||||
originalServerWorld.dimension(),
|
originalServerWorld.dimension(),
|
||||||
DedicatedServer.getServer().registryAccess().registry(Registries.LEVEL_STEM).orElseThrow()
|
new LevelStem(
|
||||||
.getOrThrow(levelStemResourceKey),
|
originalServerWorld.dimensionTypeRegistration(),
|
||||||
|
originalServerWorld.getChunkSource().getGenerator()
|
||||||
|
),
|
||||||
new RegenNoOpWorldLoadListener(),
|
new RegenNoOpWorldLoadListener(),
|
||||||
originalServerWorld.isDebug(),
|
originalServerWorld.isDebug(),
|
||||||
seed,
|
seed,
|
||||||
@ -272,17 +176,30 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(BooleanSupplier shouldKeepTicking) { //no ticking
|
public @NotNull Holder<Biome> getUncachedNoiseBiome(int biomeX, int biomeY, int biomeZ) {
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Holder<Biome> getUncachedNoiseBiome(int biomeX, int biomeY, int biomeZ) {
|
|
||||||
if (options.hasBiomeType()) {
|
if (options.hasBiomeType()) {
|
||||||
return singleBiome;
|
return singleBiome;
|
||||||
}
|
}
|
||||||
return PaperweightRegen.this.chunkGenerator.getBiomeSource().getNoiseBiome(
|
return super.getUncachedNoiseBiome(biomeX, biomeY, biomeZ);
|
||||||
biomeX, biomeY, biomeZ, getChunkSource().randomState().sampler()
|
}
|
||||||
);
|
|
||||||
|
@Override
|
||||||
|
public void save(
|
||||||
|
@org.jetbrains.annotations.Nullable final ProgressListener progressListener,
|
||||||
|
final boolean flush,
|
||||||
|
final boolean savingDisabled
|
||||||
|
) {
|
||||||
|
// noop, spigot
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(
|
||||||
|
@Nullable final ProgressListener progressListener,
|
||||||
|
final boolean flush,
|
||||||
|
final boolean savingDisabled,
|
||||||
|
final boolean close
|
||||||
|
) {
|
||||||
|
// noop, paper
|
||||||
}
|
}
|
||||||
}).get();
|
}).get();
|
||||||
freshWorld.noSave = true;
|
freshWorld.noSave = true;
|
||||||
@ -291,89 +208,6 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
if (paperConfigField != null) {
|
if (paperConfigField != null) {
|
||||||
paperConfigField.set(freshWorld, originalServerWorld.paperConfig());
|
paperConfigField.set(freshWorld, originalServerWorld.paperConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
ChunkGenerator originalGenerator = originalChunkProvider.getGenerator();
|
|
||||||
if (originalGenerator instanceof FlatLevelSource flatLevelSource) {
|
|
||||||
FlatLevelGeneratorSettings generatorSettingFlat = flatLevelSource.settings();
|
|
||||||
chunkGenerator = new FlatLevelSource(generatorSettingFlat);
|
|
||||||
} else if (originalGenerator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) {
|
|
||||||
Holder<NoiseGeneratorSettings> generatorSettingBaseSupplier = (Holder<NoiseGeneratorSettings>) generatorSettingBaseSupplierField.get(
|
|
||||||
originalGenerator);
|
|
||||||
BiomeSource biomeSource;
|
|
||||||
if (options.hasBiomeType()) {
|
|
||||||
|
|
||||||
biomeSource = new FixedBiomeSource(
|
|
||||||
DedicatedServer.getServer().registryAccess()
|
|
||||||
.registryOrThrow(BIOME).asHolderIdMap().byIdOrThrow(
|
|
||||||
WorldEditPlugin.getInstance().getBukkitImplAdapter().getInternalBiomeId(options.getBiomeType())
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
biomeSource = originalGenerator.getBiomeSource();
|
|
||||||
}
|
|
||||||
chunkGenerator = new NoiseBasedChunkGenerator(
|
|
||||||
biomeSource,
|
|
||||||
generatorSettingBaseSupplier
|
|
||||||
);
|
|
||||||
} else if (originalGenerator instanceof CustomChunkGenerator customChunkGenerator) {
|
|
||||||
chunkGenerator = customChunkGenerator.getDelegate();
|
|
||||||
} else {
|
|
||||||
LOGGER.error("Unsupported generator type {}", originalGenerator.getClass().getName());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (generator != null) {
|
|
||||||
chunkGenerator = new CustomChunkGenerator(freshWorld, chunkGenerator, generator);
|
|
||||||
generateConcurrent = generator.isParallelCapable();
|
|
||||||
}
|
|
||||||
// chunkGenerator.conf = freshWorld.spigotConfig; - Does not exist anymore, may need to be re-addressed
|
|
||||||
|
|
||||||
freshChunkProvider = new ServerChunkCache(
|
|
||||||
freshWorld,
|
|
||||||
session,
|
|
||||||
server.getFixerUpper(),
|
|
||||||
server.getStructureManager(),
|
|
||||||
server.executor,
|
|
||||||
chunkGenerator,
|
|
||||||
freshWorld.spigotConfig.viewDistance,
|
|
||||||
freshWorld.spigotConfig.simulationDistance,
|
|
||||||
server.forceSynchronousWrites(),
|
|
||||||
new RegenNoOpWorldLoadListener(),
|
|
||||||
(chunkCoordIntPair, state) -> {
|
|
||||||
},
|
|
||||||
() -> server.overworld().getDataStorage()
|
|
||||||
) {
|
|
||||||
// redirect to LevelChunks created in #createChunks
|
|
||||||
@Override
|
|
||||||
public ChunkAccess getChunk(int x, int z, ChunkStatus chunkstatus, boolean create) {
|
|
||||||
ChunkAccess chunkAccess = getChunkAt(x, z);
|
|
||||||
if (chunkAccess == null && create) {
|
|
||||||
chunkAccess = createChunk(getProtoChunkAt(x, z));
|
|
||||||
}
|
|
||||||
return chunkAccess;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (seed == originalOpts.seed() && !options.hasBiomeType()) {
|
|
||||||
// Optimisation for needless ring position calculation when the seed and biome is the same.
|
|
||||||
ChunkGeneratorStructureState state = (ChunkGeneratorStructureState) generatorStructureStateField.get(originalChunkProvider.chunkMap);
|
|
||||||
boolean hasGeneratedPositions = hasGeneratedPositionsField.getBoolean(state);
|
|
||||||
if (hasGeneratedPositions) {
|
|
||||||
Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> origPositions =
|
|
||||||
(Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>>) ringPositionsField.get(state);
|
|
||||||
Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> copy = new Object2ObjectArrayMap<>(
|
|
||||||
origPositions);
|
|
||||||
ChunkGeneratorStructureState newState = (ChunkGeneratorStructureState) generatorStructureStateField.get(freshChunkProvider.chunkMap);
|
|
||||||
ringPositionsField.set(newState, copy);
|
|
||||||
hasGeneratedPositionsField.setBoolean(newState, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
chunkSourceField.set(freshWorld, freshChunkProvider);
|
|
||||||
//let's start then
|
|
||||||
structureTemplateManager = server.getStructureManager();
|
|
||||||
threadedLevelLightEngine = new NoOpLightEngine(freshChunkProvider);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,7 +222,8 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
try {
|
try {
|
||||||
Fawe.instance().getQueueHandler().sync(() -> {
|
Fawe.instance().getQueueHandler().sync(() -> {
|
||||||
try {
|
try {
|
||||||
freshChunkProvider.close(false);
|
freshWorld.getChunkSource().getDataStorage().cache.clear();
|
||||||
|
freshWorld.getChunkSource().close(false);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@ -409,63 +244,20 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ProtoChunk createProtoChunk(int x, int z) {
|
|
||||||
return new FastProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, freshWorld,
|
|
||||||
this.freshWorld.registryAccess().registryOrThrow(BIOME), null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected LevelChunk createChunk(ProtoChunk protoChunk) {
|
|
||||||
return new LevelChunk(
|
|
||||||
freshWorld,
|
|
||||||
protoChunk,
|
|
||||||
null // we don't want to add entities
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ChunkStatusWrap getFullChunkStatus() {
|
|
||||||
return new ChunkStatusWrap(ChunkStatus.FULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<BlockPopulator> getBlockPopulators() {
|
|
||||||
return originalServerWorld.getWorld().getPopulators();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void populate(LevelChunk levelChunk, Random random, BlockPopulator blockPopulator) {
|
|
||||||
// BlockPopulator#populate has to be called synchronously for TileEntity access
|
|
||||||
TaskManager.taskManager().task(() -> {
|
|
||||||
final CraftWorld world = freshWorld.getWorld();
|
|
||||||
final Chunk chunk = world.getChunkAt(levelChunk.locX, levelChunk.locZ);
|
|
||||||
blockPopulator.populate(world, random, chunk);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IChunkCache<IChunkGet> initSourceQueueCache() {
|
protected IChunkCache<IChunkGet> initSourceQueueCache() {
|
||||||
return (chunkX, chunkZ) -> new PaperweightGetBlocks(freshWorld, chunkX, chunkZ) {
|
return new ChunkCache<>(BukkitAdapter.adapt(freshWorld.getWorld()));
|
||||||
@Override
|
|
||||||
public LevelChunk ensureLoaded(ServerLevel nmsWorld, int x, int z) {
|
|
||||||
return getChunkAt(x, z);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//util
|
//util
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void removeWorldFromWorldsMap() {
|
private void removeWorldFromWorldsMap() {
|
||||||
Fawe.instance().getQueueHandler().sync(() -> {
|
try {
|
||||||
try {
|
Map<String, org.bukkit.World> map = (Map<String, org.bukkit.World>) serverWorldsField.get(Bukkit.getServer());
|
||||||
Map<String, org.bukkit.World> map = (Map<String, org.bukkit.World>) serverWorldsField.get(Bukkit.getServer());
|
map.remove("faweregentempworld");
|
||||||
map.remove("faweregentempworld");
|
} catch (IllegalAccessException e) {
|
||||||
} catch (IllegalAccessException e) {
|
throw new RuntimeException(e);
|
||||||
throw new RuntimeException(e);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResourceKey<LevelStem> getWorldDimKey(org.bukkit.World.Environment env) {
|
private ResourceKey<LevelStem> getWorldDimKey(org.bukkit.World.Environment env) {
|
||||||
@ -482,11 +274,15 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateSpawnPos(ChunkPos spawnPos) {
|
public void updateSpawnPos(@NotNull ChunkPos spawnPos) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStatusChange(ChunkPos pos, @Nullable ChunkStatus status) {
|
public void onStatusChange(
|
||||||
|
final @NotNull ChunkPos pos,
|
||||||
|
@org.jetbrains.annotations.Nullable final ChunkStatus status
|
||||||
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -504,87 +300,4 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FastProtoChunk extends ProtoChunk {
|
|
||||||
|
|
||||||
public FastProtoChunk(
|
|
||||||
final ChunkPos pos,
|
|
||||||
final UpgradeData upgradeData,
|
|
||||||
final LevelHeightAccessor world,
|
|
||||||
final Registry<Biome> biomeRegistry,
|
|
||||||
@Nullable final BlendingData blendingData
|
|
||||||
) {
|
|
||||||
super(pos, upgradeData, world, biomeRegistry, blendingData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// avoid warning on paper
|
|
||||||
|
|
||||||
// compatibility with spigot
|
|
||||||
|
|
||||||
public boolean generateFlatBedrock() {
|
|
||||||
return generateFlatBedrock;
|
|
||||||
}
|
|
||||||
|
|
||||||
// no one will ever see the entities!
|
|
||||||
@Override
|
|
||||||
public List<CompoundTag> getEntities() {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class ChunkStatusWrap extends ChunkStatusWrapper<ChunkAccess> {
|
|
||||||
|
|
||||||
private final ChunkStatus chunkStatus;
|
|
||||||
|
|
||||||
public ChunkStatusWrap(ChunkStatus chunkStatus) {
|
|
||||||
this.chunkStatus = chunkStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int requiredNeighborChunkRadius() {
|
|
||||||
return chunkStatus.getRange();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String name() {
|
|
||||||
return chunkStatus.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<?> processChunk(List<ChunkAccess> accessibleChunks) {
|
|
||||||
return chunkStatus.generate(
|
|
||||||
Runnable::run, // TODO revisit, we might profit from this somehow?
|
|
||||||
freshWorld,
|
|
||||||
chunkGenerator,
|
|
||||||
structureTemplateManager,
|
|
||||||
threadedLevelLightEngine,
|
|
||||||
c -> CompletableFuture.completedFuture(Either.left(c)),
|
|
||||||
accessibleChunks
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A light engine that does nothing. As light is calculated after pasting anyway, we can avoid
|
|
||||||
* work this way.
|
|
||||||
*/
|
|
||||||
static class NoOpLightEngine extends ThreadedLevelLightEngine {
|
|
||||||
|
|
||||||
private static final ProcessorMailbox<Runnable> MAILBOX = ProcessorMailbox.create(task -> {
|
|
||||||
}, "fawe-no-op");
|
|
||||||
private static final ProcessorHandle<Message<Runnable>> HANDLE = ProcessorHandle.of("fawe-no-op", m -> {
|
|
||||||
});
|
|
||||||
|
|
||||||
public NoOpLightEngine(final ServerChunkCache chunkProvider) {
|
|
||||||
super(chunkProvider, chunkProvider.chunkMap, false, MAILBOX, HANDLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<ChunkAccess> lightChunk(final ChunkAccess chunk, final boolean excludeBlocks) {
|
|
||||||
return CompletableFuture.completedFuture(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,166 +4,73 @@ import com.fastasyncworldedit.bukkit.adapter.Regenerator;
|
|||||||
import com.fastasyncworldedit.core.Fawe;
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
import com.fastasyncworldedit.core.queue.IChunkCache;
|
import com.fastasyncworldedit.core.queue.IChunkCache;
|
||||||
import com.fastasyncworldedit.core.queue.IChunkGet;
|
import com.fastasyncworldedit.core.queue.IChunkGet;
|
||||||
import com.fastasyncworldedit.core.util.TaskManager;
|
import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.mojang.serialization.Lifecycle;
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R4.PaperweightGetBlocks;
|
|
||||||
import com.sk89q.worldedit.extent.Extent;
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
|
||||||
import com.sk89q.worldedit.regions.Region;
|
import com.sk89q.worldedit.regions.Region;
|
||||||
import com.sk89q.worldedit.util.io.file.SafeFiles;
|
import com.sk89q.worldedit.util.io.file.SafeFiles;
|
||||||
import com.sk89q.worldedit.world.RegenOptions;
|
import com.sk89q.worldedit.world.RegenOptions;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
|
|
||||||
import net.minecraft.core.Holder;
|
import net.minecraft.core.Holder;
|
||||||
import net.minecraft.core.Registry;
|
|
||||||
import net.minecraft.core.registries.Registries;
|
|
||||||
import net.minecraft.nbt.CompoundTag;
|
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.dedicated.DedicatedServer;
|
import net.minecraft.server.dedicated.DedicatedServer;
|
||||||
import net.minecraft.server.level.ChunkMap;
|
|
||||||
import net.minecraft.server.level.ChunkTaskPriorityQueueSorter.Message;
|
|
||||||
import net.minecraft.server.level.ServerChunkCache;
|
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.server.level.ThreadedLevelLightEngine;
|
|
||||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||||
import net.minecraft.util.thread.ProcessorHandle;
|
import net.minecraft.util.ProgressListener;
|
||||||
import net.minecraft.util.thread.ProcessorMailbox;
|
|
||||||
import net.minecraft.world.level.ChunkPos;
|
import net.minecraft.world.level.ChunkPos;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.LevelHeightAccessor;
|
|
||||||
import net.minecraft.world.level.LevelSettings;
|
import net.minecraft.world.level.LevelSettings;
|
||||||
import net.minecraft.world.level.biome.Biome;
|
import net.minecraft.world.level.biome.Biome;
|
||||||
import net.minecraft.world.level.biome.BiomeSource;
|
|
||||||
import net.minecraft.world.level.biome.FixedBiomeSource;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
|
||||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
|
||||||
import net.minecraft.world.level.chunk.UpgradeData;
|
|
||||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
|
||||||
import net.minecraft.world.level.chunk.status.WorldGenContext;
|
|
||||||
import net.minecraft.world.level.dimension.LevelStem;
|
import net.minecraft.world.level.dimension.LevelStem;
|
||||||
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
|
||||||
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
||||||
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
|
|
||||||
import net.minecraft.world.level.levelgen.WorldOptions;
|
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||||
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
|
||||||
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
|
||||||
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
|
|
||||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
|
||||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Chunk;
|
import org.bukkit.World;
|
||||||
import org.bukkit.craftbukkit.CraftServer;
|
import org.bukkit.craftbukkit.CraftServer;
|
||||||
import org.bukkit.craftbukkit.CraftWorld;
|
import org.bukkit.craftbukkit.CraftWorld;
|
||||||
import org.bukkit.craftbukkit.generator.CustomChunkGenerator;
|
|
||||||
import org.bukkit.generator.BiomeProvider;
|
import org.bukkit.generator.BiomeProvider;
|
||||||
import org.bukkit.generator.BlockPopulator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.OptionalLong;
|
import java.util.OptionalLong;
|
||||||
import java.util.Random;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import static net.minecraft.core.registries.Registries.BIOME;
|
import static net.minecraft.core.registries.Registries.BIOME;
|
||||||
|
|
||||||
public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, LevelChunk, PaperweightRegen.ChunkStatusWrap> {
|
public class PaperweightRegen extends Regenerator {
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
|
||||||
|
|
||||||
private static final Field serverWorldsField;
|
private static final Field serverWorldsField;
|
||||||
private static final Field paperConfigField;
|
private static final Field paperConfigField;
|
||||||
private static final Field flatBedrockField;
|
|
||||||
private static final Field generatorSettingFlatField;
|
|
||||||
private static final Field generatorSettingBaseSupplierField;
|
private static final Field generatorSettingBaseSupplierField;
|
||||||
private static final Field delegateField;
|
|
||||||
private static final Field chunkSourceField;
|
|
||||||
private static final Field generatorStructureStateField;
|
|
||||||
private static final Field ringPositionsField;
|
|
||||||
private static final Field hasGeneratedPositionsField;
|
|
||||||
|
|
||||||
//list of chunk stati in correct order without FULL
|
|
||||||
private static final Map<ChunkStatus, Concurrency> chunkStati = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
chunkStati.put(ChunkStatus.EMPTY, Concurrency.FULL); // empty: radius -1, does nothing
|
|
||||||
chunkStati.put(ChunkStatus.STRUCTURE_STARTS, Concurrency.NONE); // structure starts: uses unsynchronized maps
|
|
||||||
chunkStati.put(
|
|
||||||
ChunkStatus.STRUCTURE_REFERENCES,
|
|
||||||
Concurrency.NONE
|
|
||||||
); // structure refs: radius 8, but only writes to current chunk
|
|
||||||
chunkStati.put(ChunkStatus.BIOMES, Concurrency.NONE); // biomes: radius 0
|
|
||||||
chunkStati.put(ChunkStatus.NOISE, Concurrency.RADIUS); // noise: radius 8
|
|
||||||
chunkStati.put(ChunkStatus.SURFACE, Concurrency.NONE); // surface: radius 0, requires NONE
|
|
||||||
chunkStati.put(ChunkStatus.CARVERS, Concurrency.NONE); // carvers: radius 0, but RADIUS and FULL change results
|
|
||||||
chunkStati.put(ChunkStatus.FEATURES, Concurrency.NONE); // features: uses unsynchronized maps
|
|
||||||
chunkStati.put(
|
|
||||||
ChunkStatus.INITIALIZE_LIGHT,
|
|
||||||
Concurrency.FULL
|
|
||||||
); // initialize_light: radius 0
|
|
||||||
chunkStati.put(
|
|
||||||
ChunkStatus.LIGHT,
|
|
||||||
Concurrency.FULL
|
|
||||||
); // light: radius 1, but no writes to other chunks, only current chunk
|
|
||||||
chunkStati.put(ChunkStatus.SPAWN, Concurrency.NONE); // spawn: radius 0
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
serverWorldsField = CraftServer.class.getDeclaredField("worlds");
|
serverWorldsField = CraftServer.class.getDeclaredField("worlds");
|
||||||
serverWorldsField.setAccessible(true);
|
serverWorldsField.setAccessible(true);
|
||||||
|
|
||||||
Field tmpPaperConfigField;
|
Field tmpPaperConfigField;
|
||||||
Field tmpFlatBedrockField;
|
|
||||||
try { //only present on paper
|
try { //only present on paper
|
||||||
tmpPaperConfigField = Level.class.getDeclaredField("paperConfig");
|
tmpPaperConfigField = Level.class.getDeclaredField("paperConfig");
|
||||||
tmpPaperConfigField.setAccessible(true);
|
tmpPaperConfigField.setAccessible(true);
|
||||||
|
|
||||||
tmpFlatBedrockField = tmpPaperConfigField.getType().getDeclaredField("generateFlatBedrock");
|
|
||||||
tmpFlatBedrockField.setAccessible(true);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
tmpPaperConfigField = null;
|
tmpPaperConfigField = null;
|
||||||
tmpFlatBedrockField = null;
|
|
||||||
}
|
}
|
||||||
paperConfigField = tmpPaperConfigField;
|
paperConfigField = tmpPaperConfigField;
|
||||||
flatBedrockField = tmpFlatBedrockField;
|
|
||||||
|
|
||||||
generatorSettingBaseSupplierField = NoiseBasedChunkGenerator.class.getDeclaredField(Refraction.pickName(
|
generatorSettingBaseSupplierField = NoiseBasedChunkGenerator.class.getDeclaredField(Refraction.pickName(
|
||||||
"settings", "e"));
|
"settings", "e"));
|
||||||
generatorSettingBaseSupplierField.setAccessible(true);
|
generatorSettingBaseSupplierField.setAccessible(true);
|
||||||
|
|
||||||
generatorSettingFlatField = FlatLevelSource.class.getDeclaredField(Refraction.pickName("settings", "d"));
|
|
||||||
generatorSettingFlatField.setAccessible(true);
|
|
||||||
|
|
||||||
delegateField = CustomChunkGenerator.class.getDeclaredField("delegate");
|
|
||||||
delegateField.setAccessible(true);
|
|
||||||
|
|
||||||
chunkSourceField = ServerLevel.class.getDeclaredField(Refraction.pickName("chunkSource", "I"));
|
|
||||||
chunkSourceField.setAccessible(true);
|
|
||||||
|
|
||||||
generatorStructureStateField = ChunkMap.class.getDeclaredField(Refraction.pickName("chunkGeneratorState", "v"));
|
|
||||||
generatorStructureStateField.setAccessible(true);
|
|
||||||
|
|
||||||
ringPositionsField = ChunkGeneratorStructureState.class.getDeclaredField(Refraction.pickName("ringPositions", "g"));
|
|
||||||
ringPositionsField.setAccessible(true);
|
|
||||||
|
|
||||||
hasGeneratedPositionsField = ChunkGeneratorStructureState.class.getDeclaredField(
|
|
||||||
Refraction.pickName("hasGeneratedPositions", "h")
|
|
||||||
);
|
|
||||||
hasGeneratedPositionsField.setAccessible(true);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@ -171,44 +78,37 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
|
|
||||||
//runtime
|
//runtime
|
||||||
private ServerLevel originalServerWorld;
|
private ServerLevel originalServerWorld;
|
||||||
private ServerChunkCache originalChunkProvider;
|
|
||||||
private ServerLevel freshWorld;
|
private ServerLevel freshWorld;
|
||||||
private ServerChunkCache freshChunkProvider;
|
|
||||||
private LevelStorageSource.LevelStorageAccess session;
|
private LevelStorageSource.LevelStorageAccess session;
|
||||||
private StructureTemplateManager structureTemplateManager;
|
|
||||||
private ThreadedLevelLightEngine threadedLevelLightEngine;
|
|
||||||
private ChunkGenerator chunkGenerator;
|
|
||||||
private WorldGenContext worldGenContext;
|
|
||||||
|
|
||||||
private Path tempDir;
|
private Path tempDir;
|
||||||
|
|
||||||
private boolean generateFlatBedrock = false;
|
public PaperweightRegen(
|
||||||
|
World originalBukkitWorld,
|
||||||
public PaperweightRegen(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) {
|
Region region,
|
||||||
|
Extent target,
|
||||||
|
RegenOptions options
|
||||||
|
) {
|
||||||
super(originalBukkitWorld, region, target, options);
|
super(originalBukkitWorld, region, target, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void runTasks(final BooleanSupplier shouldKeepTicking) {
|
||||||
|
while (shouldKeepTicking.getAsBoolean()) {
|
||||||
|
if (!this.freshWorld.getChunkSource().pollTask()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean prepare() {
|
protected boolean prepare() {
|
||||||
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
||||||
originalChunkProvider = originalServerWorld.getChunkSource();
|
|
||||||
|
|
||||||
//flat bedrock? (only on paper)
|
|
||||||
if (paperConfigField != null) {
|
|
||||||
try {
|
|
||||||
generateFlatBedrock = flatBedrockField.getBoolean(paperConfigField.get(originalServerWorld));
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
||||||
chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c));
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected boolean initNewWorld() throws Exception {
|
protected boolean initNewWorld() throws Exception {
|
||||||
//world folder
|
//world folder
|
||||||
tempDir = java.nio.file.Files.createTempDirectory("FastAsyncWorldEditWorldGen");
|
tempDir = java.nio.file.Files.createTempDirectory("FastAsyncWorldEditWorldGen");
|
||||||
@ -254,8 +154,10 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
session,
|
session,
|
||||||
newWorldData,
|
newWorldData,
|
||||||
originalServerWorld.dimension(),
|
originalServerWorld.dimension(),
|
||||||
DedicatedServer.getServer().registryAccess().registry(Registries.LEVEL_STEM).orElseThrow()
|
new LevelStem(
|
||||||
.getOrThrow(levelStemResourceKey),
|
originalServerWorld.dimensionTypeRegistration(),
|
||||||
|
originalServerWorld.getChunkSource().getGenerator()
|
||||||
|
),
|
||||||
new RegenNoOpWorldLoadListener(),
|
new RegenNoOpWorldLoadListener(),
|
||||||
originalServerWorld.isDebug(),
|
originalServerWorld.isDebug(),
|
||||||
seed,
|
seed,
|
||||||
@ -272,20 +174,32 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
WorldEditPlugin.getInstance().getBukkitImplAdapter().getInternalBiomeId(options.getBiomeType())
|
WorldEditPlugin.getInstance().getBukkitImplAdapter().getInternalBiomeId(options.getBiomeType())
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tick(@NotNull BooleanSupplier shouldKeepTicking) { //no ticking
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Holder<Biome> getUncachedNoiseBiome(int biomeX, int biomeY, int biomeZ) {
|
public @NotNull Holder<Biome> getUncachedNoiseBiome(int biomeX, int biomeY, int biomeZ) {
|
||||||
if (options.hasBiomeType()) {
|
if (options.hasBiomeType()) {
|
||||||
return singleBiome;
|
return singleBiome;
|
||||||
}
|
}
|
||||||
return PaperweightRegen.this.chunkGenerator.getBiomeSource().getNoiseBiome(
|
return super.getUncachedNoiseBiome(biomeX, biomeY, biomeZ);
|
||||||
biomeX, biomeY, biomeZ, getChunkSource().randomState().sampler()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(
|
||||||
|
@org.jetbrains.annotations.Nullable final ProgressListener progressListener,
|
||||||
|
final boolean flush,
|
||||||
|
final boolean savingDisabled
|
||||||
|
) {
|
||||||
|
// noop, spigot
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(
|
||||||
|
@Nullable final ProgressListener progressListener,
|
||||||
|
final boolean flush,
|
||||||
|
final boolean savingDisabled,
|
||||||
|
final boolean close
|
||||||
|
) {
|
||||||
|
// noop, paper
|
||||||
|
}
|
||||||
}).get();
|
}).get();
|
||||||
freshWorld.noSave = true;
|
freshWorld.noSave = true;
|
||||||
removeWorldFromWorldsMap();
|
removeWorldFromWorldsMap();
|
||||||
@ -293,93 +207,6 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
if (paperConfigField != null) {
|
if (paperConfigField != null) {
|
||||||
paperConfigField.set(freshWorld, originalServerWorld.paperConfig());
|
paperConfigField.set(freshWorld, originalServerWorld.paperConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
ChunkGenerator originalGenerator = originalChunkProvider.getGenerator();
|
|
||||||
if (originalGenerator instanceof FlatLevelSource flatLevelSource) {
|
|
||||||
FlatLevelGeneratorSettings generatorSettingFlat = flatLevelSource.settings();
|
|
||||||
chunkGenerator = new FlatLevelSource(generatorSettingFlat);
|
|
||||||
} else if (originalGenerator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) {
|
|
||||||
Holder<NoiseGeneratorSettings> generatorSettingBaseSupplier = (Holder<NoiseGeneratorSettings>)
|
|
||||||
generatorSettingBaseSupplierField.get(noiseBasedChunkGenerator);
|
|
||||||
BiomeSource biomeSource;
|
|
||||||
if (options.hasBiomeType()) {
|
|
||||||
biomeSource = new FixedBiomeSource(
|
|
||||||
DedicatedServer.getServer().registryAccess()
|
|
||||||
.registryOrThrow(BIOME).asHolderIdMap().byIdOrThrow(
|
|
||||||
WorldEditPlugin.getInstance().getBukkitImplAdapter().getInternalBiomeId(options.getBiomeType())
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
biomeSource = originalGenerator.getBiomeSource();
|
|
||||||
}
|
|
||||||
chunkGenerator = new NoiseBasedChunkGenerator(
|
|
||||||
biomeSource,
|
|
||||||
generatorSettingBaseSupplier
|
|
||||||
);
|
|
||||||
} else if (originalGenerator instanceof CustomChunkGenerator customChunkGenerator) {
|
|
||||||
chunkGenerator = customChunkGenerator.getDelegate();
|
|
||||||
} else {
|
|
||||||
LOGGER.error("Unsupported generator type {}", originalGenerator.getClass().getName());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (generator != null) {
|
|
||||||
chunkGenerator = new CustomChunkGenerator(freshWorld, chunkGenerator, generator);
|
|
||||||
generateConcurrent = generator.isParallelCapable();
|
|
||||||
}
|
|
||||||
// chunkGenerator.conf = freshWorld.spigotConfig; - Does not exist anymore, may need to be re-addressed
|
|
||||||
|
|
||||||
freshChunkProvider = new ServerChunkCache(
|
|
||||||
freshWorld,
|
|
||||||
session,
|
|
||||||
server.getFixerUpper(),
|
|
||||||
server.getStructureManager(),
|
|
||||||
server.executor,
|
|
||||||
chunkGenerator,
|
|
||||||
freshWorld.spigotConfig.viewDistance,
|
|
||||||
freshWorld.spigotConfig.simulationDistance,
|
|
||||||
server.forceSynchronousWrites(),
|
|
||||||
new RegenNoOpWorldLoadListener(),
|
|
||||||
(chunkCoordIntPair, state) -> {
|
|
||||||
},
|
|
||||||
() -> server.overworld().getDataStorage()
|
|
||||||
) {
|
|
||||||
// redirect to LevelChunks created in #createChunks
|
|
||||||
@Override
|
|
||||||
public ChunkAccess getChunk(int x, int z, @NotNull ChunkStatus chunkstatus, boolean create) {
|
|
||||||
ChunkAccess chunkAccess = getChunkAt(x, z);
|
|
||||||
if (chunkAccess == null && create) {
|
|
||||||
chunkAccess = createChunk(getProtoChunkAt(x, z));
|
|
||||||
}
|
|
||||||
return chunkAccess;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (seed == originalOpts.seed() && !options.hasBiomeType()) {
|
|
||||||
// Optimisation for needless ring position calculation when the seed and biome is the same.
|
|
||||||
ChunkGeneratorStructureState state = (ChunkGeneratorStructureState) generatorStructureStateField.get(
|
|
||||||
originalChunkProvider.chunkMap);
|
|
||||||
boolean hasGeneratedPositions = hasGeneratedPositionsField.getBoolean(state);
|
|
||||||
if (hasGeneratedPositions) {
|
|
||||||
Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> origPositions =
|
|
||||||
(Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>>) ringPositionsField.get(state);
|
|
||||||
Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> copy = new Object2ObjectArrayMap<>(
|
|
||||||
origPositions);
|
|
||||||
ChunkGeneratorStructureState newState = (ChunkGeneratorStructureState) generatorStructureStateField.get(
|
|
||||||
freshChunkProvider.chunkMap);
|
|
||||||
ringPositionsField.set(newState, copy);
|
|
||||||
hasGeneratedPositionsField.setBoolean(newState, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
chunkSourceField.set(freshWorld, freshChunkProvider);
|
|
||||||
//let's start then
|
|
||||||
structureTemplateManager = server.getStructureManager();
|
|
||||||
threadedLevelLightEngine = new NoOpLightEngine(freshChunkProvider);
|
|
||||||
|
|
||||||
this.worldGenContext = new WorldGenContext(freshWorld, chunkGenerator, structureTemplateManager,
|
|
||||||
threadedLevelLightEngine
|
|
||||||
);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,7 +221,8 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
try {
|
try {
|
||||||
Fawe.instance().getQueueHandler().sync(() -> {
|
Fawe.instance().getQueueHandler().sync(() -> {
|
||||||
try {
|
try {
|
||||||
freshChunkProvider.close(false);
|
freshWorld.getChunkSource().getDataStorage().cache.clear();
|
||||||
|
freshWorld.getChunkSource().close(false);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@ -415,50 +243,9 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ProtoChunk createProtoChunk(int x, int z) {
|
|
||||||
return new FastProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, freshWorld,
|
|
||||||
this.freshWorld.registryAccess().registryOrThrow(BIOME), null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected LevelChunk createChunk(ProtoChunk protoChunk) {
|
|
||||||
return new LevelChunk(
|
|
||||||
freshWorld,
|
|
||||||
protoChunk,
|
|
||||||
null // we don't want to add entities
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ChunkStatusWrap getFullChunkStatus() {
|
|
||||||
return new ChunkStatusWrap(ChunkStatus.FULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<BlockPopulator> getBlockPopulators() {
|
|
||||||
return originalServerWorld.getWorld().getPopulators();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void populate(LevelChunk levelChunk, Random random, BlockPopulator blockPopulator) {
|
|
||||||
// BlockPopulator#populate has to be called synchronously for TileEntity access
|
|
||||||
TaskManager.taskManager().task(() -> {
|
|
||||||
final CraftWorld world = freshWorld.getWorld();
|
|
||||||
final Chunk chunk = world.getChunkAt(levelChunk.locX, levelChunk.locZ);
|
|
||||||
blockPopulator.populate(world, random, chunk);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IChunkCache<IChunkGet> initSourceQueueCache() {
|
protected IChunkCache<IChunkGet> initSourceQueueCache() {
|
||||||
return (chunkX, chunkZ) -> new PaperweightGetBlocks(freshWorld, chunkX, chunkZ) {
|
return new ChunkCache<>(BukkitAdapter.adapt(freshWorld.getWorld()));
|
||||||
@Override
|
|
||||||
public LevelChunk ensureLoaded(ServerLevel nmsWorld, int x, int z) {
|
|
||||||
return getChunkAt(x, z);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//util
|
//util
|
||||||
@ -512,83 +299,4 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FastProtoChunk extends ProtoChunk {
|
|
||||||
|
|
||||||
public FastProtoChunk(
|
|
||||||
final ChunkPos pos,
|
|
||||||
final UpgradeData upgradeData,
|
|
||||||
final LevelHeightAccessor world,
|
|
||||||
final Registry<Biome> biomeRegistry,
|
|
||||||
@Nullable final BlendingData blendingData
|
|
||||||
) {
|
|
||||||
super(pos, upgradeData, world, biomeRegistry, blendingData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// avoid warning on paper
|
|
||||||
|
|
||||||
@SuppressWarnings("unused") // compatibility with spigot
|
|
||||||
public boolean generateFlatBedrock() {
|
|
||||||
return generateFlatBedrock;
|
|
||||||
}
|
|
||||||
|
|
||||||
// no one will ever see the entities!
|
|
||||||
@Override
|
|
||||||
public @NotNull List<CompoundTag> getEntities() {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class ChunkStatusWrap extends ChunkStatusWrapper<ChunkAccess> {
|
|
||||||
|
|
||||||
private final ChunkStatus chunkStatus;
|
|
||||||
|
|
||||||
public ChunkStatusWrap(ChunkStatus chunkStatus) {
|
|
||||||
this.chunkStatus = chunkStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int requiredNeighborChunkRadius() {
|
|
||||||
return chunkStatus.getRange();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String name() {
|
|
||||||
return chunkStatus.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<?> processChunk(List<ChunkAccess> accessibleChunks) {
|
|
||||||
return chunkStatus.generate(
|
|
||||||
worldGenContext,
|
|
||||||
Runnable::run,
|
|
||||||
CompletableFuture::completedFuture,
|
|
||||||
accessibleChunks
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A light engine that does nothing. As light is calculated after pasting anyway, we can avoid
|
|
||||||
* work this way.
|
|
||||||
*/
|
|
||||||
static class NoOpLightEngine extends ThreadedLevelLightEngine {
|
|
||||||
|
|
||||||
private static final ProcessorMailbox<Runnable> MAILBOX = ProcessorMailbox.create(task -> {
|
|
||||||
}, "fawe-no-op");
|
|
||||||
private static final ProcessorHandle<Message<Runnable>> HANDLE = ProcessorHandle.of("fawe-no-op", m -> {
|
|
||||||
});
|
|
||||||
|
|
||||||
public NoOpLightEngine(final ServerChunkCache chunkProvider) {
|
|
||||||
super(chunkProvider, chunkProvider.chunkMap, false, MAILBOX, HANDLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull CompletableFuture<ChunkAccess> lightChunk(final @NotNull ChunkAccess chunk, final boolean excludeBlocks) {
|
|
||||||
return CompletableFuture.completedFuture(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import com.sk89q.worldedit.blocks.BaseItemStack;
|
|||||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_R1.nbt.PaperweightLazyCompoundTag;
|
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_R1.nbt.PaperweightLazyCompoundTag;
|
||||||
|
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_R1.regen.PaperweightRegen;
|
||||||
import com.sk89q.worldedit.entity.BaseEntity;
|
import com.sk89q.worldedit.entity.BaseEntity;
|
||||||
import com.sk89q.worldedit.extent.Extent;
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
|
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
|
||||||
@ -565,7 +566,7 @@ public final class PaperweightFaweAdapter extends FaweAdapter<net.minecraft.nbt.
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception {
|
public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception {
|
||||||
throw new UnsupportedOperationException("Regen support for 1.21 not yet implemented.");
|
return new PaperweightRegen(bukkitWorld, region, target, options).regenerate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,175 +1,76 @@
|
|||||||
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_R1.regen;
|
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_R1.regen;
|
||||||
|
|
||||||
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
|
|
||||||
import com.fastasyncworldedit.bukkit.adapter.Regenerator;
|
import com.fastasyncworldedit.bukkit.adapter.Regenerator;
|
||||||
import com.fastasyncworldedit.core.Fawe;
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
import com.fastasyncworldedit.core.queue.IChunkCache;
|
import com.fastasyncworldedit.core.queue.IChunkCache;
|
||||||
import com.fastasyncworldedit.core.queue.IChunkGet;
|
import com.fastasyncworldedit.core.queue.IChunkGet;
|
||||||
import com.fastasyncworldedit.core.util.TaskManager;
|
import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.mojang.serialization.Lifecycle;
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
import com.sk89q.worldedit.bukkit.adapter.Refraction;
|
||||||
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_R1.PaperweightGetBlocks;
|
|
||||||
import com.sk89q.worldedit.extent.Extent;
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
|
||||||
import com.sk89q.worldedit.regions.Region;
|
import com.sk89q.worldedit.regions.Region;
|
||||||
import com.sk89q.worldedit.util.io.file.SafeFiles;
|
import com.sk89q.worldedit.util.io.file.SafeFiles;
|
||||||
import com.sk89q.worldedit.world.RegenOptions;
|
import com.sk89q.worldedit.world.RegenOptions;
|
||||||
import io.papermc.lib.PaperLib;
|
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
|
|
||||||
import net.minecraft.core.Holder;
|
import net.minecraft.core.Holder;
|
||||||
import net.minecraft.core.Registry;
|
|
||||||
import net.minecraft.core.registries.Registries;
|
|
||||||
import net.minecraft.nbt.CompoundTag;
|
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.dedicated.DedicatedServer;
|
import net.minecraft.server.dedicated.DedicatedServer;
|
||||||
import net.minecraft.server.level.ChunkHolder;
|
|
||||||
import net.minecraft.server.level.ChunkMap;
|
|
||||||
import net.minecraft.server.level.ChunkTaskPriorityQueueSorter.Message;
|
|
||||||
import net.minecraft.server.level.GenerationChunkHolder;
|
|
||||||
import net.minecraft.server.level.ServerChunkCache;
|
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.server.level.ThreadedLevelLightEngine;
|
|
||||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||||
import net.minecraft.util.StaticCache2D;
|
import net.minecraft.util.ProgressListener;
|
||||||
import net.minecraft.util.thread.ProcessorHandle;
|
|
||||||
import net.minecraft.util.thread.ProcessorMailbox;
|
|
||||||
import net.minecraft.world.level.ChunkPos;
|
import net.minecraft.world.level.ChunkPos;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.LevelHeightAccessor;
|
|
||||||
import net.minecraft.world.level.LevelSettings;
|
import net.minecraft.world.level.LevelSettings;
|
||||||
import net.minecraft.world.level.biome.Biome;
|
import net.minecraft.world.level.biome.Biome;
|
||||||
import net.minecraft.world.level.biome.BiomeSource;
|
|
||||||
import net.minecraft.world.level.biome.FixedBiomeSource;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
|
||||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
|
||||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
|
||||||
import net.minecraft.world.level.chunk.UpgradeData;
|
|
||||||
import net.minecraft.world.level.chunk.status.ChunkPyramid;
|
|
||||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
|
||||||
import net.minecraft.world.level.chunk.status.WorldGenContext;
|
|
||||||
import net.minecraft.world.level.dimension.LevelStem;
|
import net.minecraft.world.level.dimension.LevelStem;
|
||||||
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
|
||||||
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
||||||
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
|
|
||||||
import net.minecraft.world.level.levelgen.WorldOptions;
|
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||||
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
|
||||||
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
|
||||||
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
|
|
||||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
|
||||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Chunk;
|
import org.bukkit.World;
|
||||||
import org.bukkit.craftbukkit.CraftServer;
|
import org.bukkit.craftbukkit.CraftServer;
|
||||||
import org.bukkit.craftbukkit.CraftWorld;
|
import org.bukkit.craftbukkit.CraftWorld;
|
||||||
import org.bukkit.craftbukkit.generator.CustomChunkGenerator;
|
|
||||||
import org.bukkit.generator.BiomeProvider;
|
import org.bukkit.generator.BiomeProvider;
|
||||||
import org.bukkit.generator.BlockPopulator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.OptionalLong;
|
import java.util.OptionalLong;
|
||||||
import java.util.Random;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import static net.minecraft.core.registries.Registries.BIOME;
|
import static net.minecraft.core.registries.Registries.BIOME;
|
||||||
|
|
||||||
public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, LevelChunk, PaperweightRegen.ChunkStatusWrap> {
|
public class PaperweightRegen extends Regenerator {
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
|
||||||
|
|
||||||
private static final Field serverWorldsField;
|
private static final Field serverWorldsField;
|
||||||
private static final Field paperConfigField;
|
private static final Field paperConfigField;
|
||||||
private static final Field flatBedrockField;
|
|
||||||
private static final Field generatorSettingFlatField;
|
|
||||||
private static final Field generatorSettingBaseSupplierField;
|
private static final Field generatorSettingBaseSupplierField;
|
||||||
private static final Field delegateField;
|
|
||||||
private static final Field chunkSourceField;
|
|
||||||
private static final Field generatorStructureStateField;
|
|
||||||
private static final Field ringPositionsField;
|
|
||||||
private static final Field hasGeneratedPositionsField;
|
|
||||||
|
|
||||||
//list of chunk stati in correct order without FULL
|
|
||||||
private static final Map<ChunkStatus, Concurrency> chunkStati = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
chunkStati.put(ChunkStatus.EMPTY, Concurrency.FULL); // empty: radius -1, does nothing
|
|
||||||
chunkStati.put(ChunkStatus.STRUCTURE_STARTS, Concurrency.NONE); // structure starts: uses unsynchronized maps
|
|
||||||
chunkStati.put(
|
|
||||||
ChunkStatus.STRUCTURE_REFERENCES,
|
|
||||||
Concurrency.NONE
|
|
||||||
); // structure refs: radius 8, but only writes to current chunk
|
|
||||||
chunkStati.put(ChunkStatus.BIOMES, Concurrency.NONE); // biomes: radius 0
|
|
||||||
chunkStati.put(ChunkStatus.NOISE, Concurrency.RADIUS); // noise: radius 8
|
|
||||||
chunkStati.put(ChunkStatus.SURFACE, Concurrency.NONE); // surface: radius 0, requires NONE
|
|
||||||
chunkStati.put(ChunkStatus.CARVERS, Concurrency.NONE); // carvers: radius 0, but RADIUS and FULL change results
|
|
||||||
chunkStati.put(ChunkStatus.FEATURES, Concurrency.NONE); // features: uses unsynchronized maps
|
|
||||||
chunkStati.put(
|
|
||||||
ChunkStatus.INITIALIZE_LIGHT,
|
|
||||||
Concurrency.FULL
|
|
||||||
); // initialize_light: radius 0
|
|
||||||
chunkStati.put(
|
|
||||||
ChunkStatus.LIGHT,
|
|
||||||
Concurrency.FULL
|
|
||||||
); // light: radius 1, but no writes to other chunks, only current chunk
|
|
||||||
chunkStati.put(ChunkStatus.SPAWN, Concurrency.NONE); // spawn: radius 0
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
serverWorldsField = CraftServer.class.getDeclaredField("worlds");
|
serverWorldsField = CraftServer.class.getDeclaredField("worlds");
|
||||||
serverWorldsField.setAccessible(true);
|
serverWorldsField.setAccessible(true);
|
||||||
|
|
||||||
Field tmpPaperConfigField;
|
Field tmpPaperConfigField;
|
||||||
Field tmpFlatBedrockField;
|
|
||||||
try { //only present on paper
|
try { //only present on paper
|
||||||
tmpPaperConfigField = Level.class.getDeclaredField("paperConfig");
|
tmpPaperConfigField = Level.class.getDeclaredField("paperConfig");
|
||||||
tmpPaperConfigField.setAccessible(true);
|
tmpPaperConfigField.setAccessible(true);
|
||||||
|
|
||||||
tmpFlatBedrockField = tmpPaperConfigField.getType().getDeclaredField("generateFlatBedrock");
|
|
||||||
tmpFlatBedrockField.setAccessible(true);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
tmpPaperConfigField = null;
|
tmpPaperConfigField = null;
|
||||||
tmpFlatBedrockField = null;
|
|
||||||
}
|
}
|
||||||
paperConfigField = tmpPaperConfigField;
|
paperConfigField = tmpPaperConfigField;
|
||||||
flatBedrockField = tmpFlatBedrockField;
|
|
||||||
|
|
||||||
generatorSettingBaseSupplierField = NoiseBasedChunkGenerator.class.getDeclaredField(Refraction.pickName(
|
generatorSettingBaseSupplierField = NoiseBasedChunkGenerator.class.getDeclaredField(Refraction.pickName(
|
||||||
"settings", "e"));
|
"settings", "e"));
|
||||||
generatorSettingBaseSupplierField.setAccessible(true);
|
generatorSettingBaseSupplierField.setAccessible(true);
|
||||||
|
|
||||||
generatorSettingFlatField = FlatLevelSource.class.getDeclaredField(Refraction.pickName("settings", "d"));
|
|
||||||
generatorSettingFlatField.setAccessible(true);
|
|
||||||
|
|
||||||
delegateField = CustomChunkGenerator.class.getDeclaredField("delegate");
|
|
||||||
delegateField.setAccessible(true);
|
|
||||||
|
|
||||||
chunkSourceField = ServerLevel.class.getDeclaredField(Refraction.pickName("chunkSource", "I"));
|
|
||||||
chunkSourceField.setAccessible(true);
|
|
||||||
|
|
||||||
generatorStructureStateField = ChunkMap.class.getDeclaredField(Refraction.pickName("chunkGeneratorState", "w"));
|
|
||||||
generatorStructureStateField.setAccessible(true);
|
|
||||||
|
|
||||||
ringPositionsField = ChunkGeneratorStructureState.class.getDeclaredField(Refraction.pickName("ringPositions", "g"));
|
|
||||||
ringPositionsField.setAccessible(true);
|
|
||||||
|
|
||||||
hasGeneratedPositionsField = ChunkGeneratorStructureState.class.getDeclaredField(
|
|
||||||
Refraction.pickName("hasGeneratedPositions", "h")
|
|
||||||
);
|
|
||||||
hasGeneratedPositionsField.setAccessible(true);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@ -177,47 +78,37 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
|
|
||||||
//runtime
|
//runtime
|
||||||
private ServerLevel originalServerWorld;
|
private ServerLevel originalServerWorld;
|
||||||
private ServerChunkCache originalChunkProvider;
|
|
||||||
private ServerLevel freshWorld;
|
private ServerLevel freshWorld;
|
||||||
private ServerChunkCache freshChunkProvider;
|
|
||||||
private LevelStorageSource.LevelStorageAccess session;
|
private LevelStorageSource.LevelStorageAccess session;
|
||||||
private StructureTemplateManager structureTemplateManager;
|
|
||||||
private ThreadedLevelLightEngine threadedLevelLightEngine;
|
|
||||||
private ChunkGenerator chunkGenerator;
|
|
||||||
private WorldGenContext worldGenContext;
|
|
||||||
|
|
||||||
private Path tempDir;
|
private Path tempDir;
|
||||||
|
|
||||||
private boolean generateFlatBedrock = false;
|
public PaperweightRegen(
|
||||||
|
World originalBukkitWorld,
|
||||||
public PaperweightRegen(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) {
|
Region region,
|
||||||
|
Extent target,
|
||||||
|
RegenOptions options
|
||||||
|
) {
|
||||||
super(originalBukkitWorld, region, target, options);
|
super(originalBukkitWorld, region, target, options);
|
||||||
if (PaperLib.isPaper()) {
|
}
|
||||||
throw new UnsupportedOperationException("Regeneration currently not support on Paper due to the new generation system");
|
|
||||||
|
@Override
|
||||||
|
protected void runTasks(final BooleanSupplier shouldKeepTicking) {
|
||||||
|
while (shouldKeepTicking.getAsBoolean()) {
|
||||||
|
if (!this.freshWorld.getChunkSource().pollTask()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean prepare() {
|
protected boolean prepare() {
|
||||||
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
|
||||||
originalChunkProvider = originalServerWorld.getChunkSource();
|
|
||||||
|
|
||||||
//flat bedrock? (only on paper)
|
|
||||||
if (paperConfigField != null) {
|
|
||||||
try {
|
|
||||||
generateFlatBedrock = flatBedrockField.getBoolean(paperConfigField.get(originalServerWorld));
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
seed = options.getSeed().orElse(originalServerWorld.getSeed());
|
||||||
chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c));
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected boolean initNewWorld() throws Exception {
|
protected boolean initNewWorld() throws Exception {
|
||||||
//world folder
|
//world folder
|
||||||
tempDir = java.nio.file.Files.createTempDirectory("FastAsyncWorldEditWorldGen");
|
tempDir = java.nio.file.Files.createTempDirectory("FastAsyncWorldEditWorldGen");
|
||||||
@ -263,8 +154,10 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
session,
|
session,
|
||||||
newWorldData,
|
newWorldData,
|
||||||
originalServerWorld.dimension(),
|
originalServerWorld.dimension(),
|
||||||
DedicatedServer.getServer().registryAccess().registry(Registries.LEVEL_STEM).orElseThrow()
|
new LevelStem(
|
||||||
.getOrThrow(levelStemResourceKey),
|
originalServerWorld.dimensionTypeRegistration(),
|
||||||
|
originalServerWorld.getChunkSource().getGenerator()
|
||||||
|
),
|
||||||
new RegenNoOpWorldLoadListener(),
|
new RegenNoOpWorldLoadListener(),
|
||||||
originalServerWorld.isDebug(),
|
originalServerWorld.isDebug(),
|
||||||
seed,
|
seed,
|
||||||
@ -281,20 +174,32 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
WorldEditPlugin.getInstance().getBukkitImplAdapter().getInternalBiomeId(options.getBiomeType())
|
WorldEditPlugin.getInstance().getBukkitImplAdapter().getInternalBiomeId(options.getBiomeType())
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tick(@NotNull BooleanSupplier shouldKeepTicking) { //no ticking
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Holder<Biome> getUncachedNoiseBiome(int biomeX, int biomeY, int biomeZ) {
|
public @NotNull Holder<Biome> getUncachedNoiseBiome(int biomeX, int biomeY, int biomeZ) {
|
||||||
if (options.hasBiomeType()) {
|
if (options.hasBiomeType()) {
|
||||||
return singleBiome;
|
return singleBiome;
|
||||||
}
|
}
|
||||||
return PaperweightRegen.this.chunkGenerator.getBiomeSource().getNoiseBiome(
|
return super.getUncachedNoiseBiome(biomeX, biomeY, biomeZ);
|
||||||
biomeX, biomeY, biomeZ, getChunkSource().randomState().sampler()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(
|
||||||
|
@Nullable final ProgressListener progressListener,
|
||||||
|
final boolean flush,
|
||||||
|
final boolean savingDisabled
|
||||||
|
) {
|
||||||
|
// noop, spigot
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(
|
||||||
|
@Nullable final ProgressListener progressListener,
|
||||||
|
final boolean flush,
|
||||||
|
final boolean savingDisabled,
|
||||||
|
final boolean close
|
||||||
|
) {
|
||||||
|
// noop, paper
|
||||||
|
}
|
||||||
}).get();
|
}).get();
|
||||||
freshWorld.noSave = true;
|
freshWorld.noSave = true;
|
||||||
removeWorldFromWorldsMap();
|
removeWorldFromWorldsMap();
|
||||||
@ -302,97 +207,6 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
if (paperConfigField != null) {
|
if (paperConfigField != null) {
|
||||||
paperConfigField.set(freshWorld, originalServerWorld.paperConfig());
|
paperConfigField.set(freshWorld, originalServerWorld.paperConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
ChunkGenerator originalGenerator = originalChunkProvider.getGenerator();
|
|
||||||
if (originalGenerator instanceof FlatLevelSource flatLevelSource) {
|
|
||||||
FlatLevelGeneratorSettings generatorSettingFlat = flatLevelSource.settings();
|
|
||||||
chunkGenerator = new FlatLevelSource(generatorSettingFlat);
|
|
||||||
} else if (originalGenerator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) {
|
|
||||||
Holder<NoiseGeneratorSettings> generatorSettingBaseSupplier = (Holder<NoiseGeneratorSettings>)
|
|
||||||
generatorSettingBaseSupplierField.get(noiseBasedChunkGenerator);
|
|
||||||
BiomeSource biomeSource;
|
|
||||||
if (options.hasBiomeType()) {
|
|
||||||
biomeSource = new FixedBiomeSource(
|
|
||||||
DedicatedServer.getServer().registryAccess()
|
|
||||||
.registryOrThrow(BIOME).asHolderIdMap().byIdOrThrow(
|
|
||||||
WorldEditPlugin.getInstance().getBukkitImplAdapter().getInternalBiomeId(options.getBiomeType())
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
biomeSource = originalGenerator.getBiomeSource();
|
|
||||||
}
|
|
||||||
chunkGenerator = new NoiseBasedChunkGenerator(
|
|
||||||
biomeSource,
|
|
||||||
generatorSettingBaseSupplier
|
|
||||||
);
|
|
||||||
} else if (originalGenerator instanceof CustomChunkGenerator customChunkGenerator) {
|
|
||||||
chunkGenerator = customChunkGenerator.getDelegate();
|
|
||||||
} else {
|
|
||||||
LOGGER.error("Unsupported generator type {}", originalGenerator.getClass().getName());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (generator != null) {
|
|
||||||
chunkGenerator = new CustomChunkGenerator(freshWorld, chunkGenerator, generator);
|
|
||||||
generateConcurrent = generator.isParallelCapable();
|
|
||||||
}
|
|
||||||
// chunkGenerator.conf = freshWorld.spigotConfig; - Does not exist anymore, may need to be re-addressed
|
|
||||||
|
|
||||||
freshChunkProvider = new ServerChunkCache(
|
|
||||||
freshWorld,
|
|
||||||
session,
|
|
||||||
server.getFixerUpper(),
|
|
||||||
server.getStructureManager(),
|
|
||||||
server.executor,
|
|
||||||
chunkGenerator,
|
|
||||||
freshWorld.spigotConfig.viewDistance,
|
|
||||||
freshWorld.spigotConfig.simulationDistance,
|
|
||||||
server.forceSynchronousWrites(),
|
|
||||||
new RegenNoOpWorldLoadListener(),
|
|
||||||
(chunkCoordIntPair, state) -> {
|
|
||||||
},
|
|
||||||
() -> server.overworld().getDataStorage()
|
|
||||||
) {
|
|
||||||
// redirect to LevelChunks created in #createChunks
|
|
||||||
@Override
|
|
||||||
public ChunkAccess getChunk(int x, int z, @NotNull ChunkStatus chunkstatus, boolean create) {
|
|
||||||
ChunkAccess chunkAccess = getChunkAt(x, z);
|
|
||||||
if (chunkAccess == null && create) {
|
|
||||||
chunkAccess = createChunk(getProtoChunkAt(x, z));
|
|
||||||
}
|
|
||||||
return chunkAccess;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (seed == originalOpts.seed() && !options.hasBiomeType()) {
|
|
||||||
// Optimisation for needless ring position calculation when the seed and biome is the same.
|
|
||||||
ChunkGeneratorStructureState state = (ChunkGeneratorStructureState) generatorStructureStateField.get(
|
|
||||||
originalChunkProvider.chunkMap);
|
|
||||||
boolean hasGeneratedPositions = hasGeneratedPositionsField.getBoolean(state);
|
|
||||||
if (hasGeneratedPositions) {
|
|
||||||
Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> origPositions =
|
|
||||||
(Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>>) ringPositionsField.get(state);
|
|
||||||
Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> copy = new Object2ObjectArrayMap<>(
|
|
||||||
origPositions);
|
|
||||||
ChunkGeneratorStructureState newState = (ChunkGeneratorStructureState) generatorStructureStateField.get(
|
|
||||||
freshChunkProvider.chunkMap);
|
|
||||||
ringPositionsField.set(newState, copy);
|
|
||||||
hasGeneratedPositionsField.setBoolean(newState, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
chunkSourceField.set(freshWorld, freshChunkProvider);
|
|
||||||
//let's start then
|
|
||||||
structureTemplateManager = server.getStructureManager();
|
|
||||||
threadedLevelLightEngine = new NoOpLightEngine(freshChunkProvider);
|
|
||||||
|
|
||||||
this.worldGenContext = new WorldGenContext(
|
|
||||||
freshWorld,
|
|
||||||
chunkGenerator,
|
|
||||||
structureTemplateManager,
|
|
||||||
threadedLevelLightEngine,
|
|
||||||
originalChunkProvider.chunkMap.worldGenContext.mainThreadMailBox()
|
|
||||||
);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,7 +221,8 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
try {
|
try {
|
||||||
Fawe.instance().getQueueHandler().sync(() -> {
|
Fawe.instance().getQueueHandler().sync(() -> {
|
||||||
try {
|
try {
|
||||||
freshChunkProvider.close(false);
|
freshWorld.getChunkSource().getDataStorage().cache.clear();
|
||||||
|
freshWorld.getChunkSource().close(false);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@ -428,50 +243,9 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ProtoChunk createProtoChunk(int x, int z) {
|
|
||||||
return new FastProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, freshWorld,
|
|
||||||
this.freshWorld.registryAccess().registryOrThrow(BIOME), null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected LevelChunk createChunk(ProtoChunk protoChunk) {
|
|
||||||
return new LevelChunk(
|
|
||||||
freshWorld,
|
|
||||||
protoChunk,
|
|
||||||
null // we don't want to add entities
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ChunkStatusWrap getFullChunkStatus() {
|
|
||||||
return new ChunkStatusWrap(ChunkStatus.FULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<BlockPopulator> getBlockPopulators() {
|
|
||||||
return originalServerWorld.getWorld().getPopulators();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void populate(LevelChunk levelChunk, Random random, BlockPopulator blockPopulator) {
|
|
||||||
// BlockPopulator#populate has to be called synchronously for TileEntity access
|
|
||||||
TaskManager.taskManager().task(() -> {
|
|
||||||
final CraftWorld world = freshWorld.getWorld();
|
|
||||||
final Chunk chunk = world.getChunkAt(levelChunk.locX, levelChunk.locZ);
|
|
||||||
blockPopulator.populate(world, random, chunk);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IChunkCache<IChunkGet> initSourceQueueCache() {
|
protected IChunkCache<IChunkGet> initSourceQueueCache() {
|
||||||
return (chunkX, chunkZ) -> new PaperweightGetBlocks(freshWorld, chunkX, chunkZ) {
|
return new ChunkCache<>(BukkitAdapter.adapt(freshWorld.getWorld()));
|
||||||
@Override
|
|
||||||
public LevelChunk ensureLoaded(ServerLevel nmsWorld, int x, int z) {
|
|
||||||
return getChunkAt(x, z);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//util
|
//util
|
||||||
@ -525,99 +299,4 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FastProtoChunk extends ProtoChunk {
|
|
||||||
|
|
||||||
public FastProtoChunk(
|
|
||||||
final ChunkPos pos,
|
|
||||||
final UpgradeData upgradeData,
|
|
||||||
final LevelHeightAccessor world,
|
|
||||||
final Registry<Biome> biomeRegistry,
|
|
||||||
@Nullable final BlendingData blendingData
|
|
||||||
) {
|
|
||||||
super(pos, upgradeData, world, biomeRegistry, blendingData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// avoid warning on paper
|
|
||||||
|
|
||||||
@SuppressWarnings("unused") // compatibility with spigot
|
|
||||||
public boolean generateFlatBedrock() {
|
|
||||||
return generateFlatBedrock;
|
|
||||||
}
|
|
||||||
|
|
||||||
// no one will ever see the entities!
|
|
||||||
@Override
|
|
||||||
public @NotNull List<CompoundTag> getEntities() {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class ChunkStatusWrap extends ChunkStatusWrapper<ChunkAccess> {
|
|
||||||
|
|
||||||
private final ChunkStatus chunkStatus;
|
|
||||||
|
|
||||||
public ChunkStatusWrap(ChunkStatus chunkStatus) {
|
|
||||||
this.chunkStatus = chunkStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int requiredNeighborChunkRadius() {
|
|
||||||
return ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FULL).getAccumulatedRadiusOf(chunkStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String name() {
|
|
||||||
return chunkStatus.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<?> processChunk(List<ChunkAccess> accessibleChunks) {
|
|
||||||
ChunkAccess chunkAccess = accessibleChunks.get(accessibleChunks.size() / 2);
|
|
||||||
int chunkX = chunkAccess.getPos().x;
|
|
||||||
int chunkZ = chunkAccess.getPos().z;
|
|
||||||
getProtoChunkAt(chunkX, chunkZ);
|
|
||||||
StaticCache2D<GenerationChunkHolder> neighbours = StaticCache2D
|
|
||||||
.create(
|
|
||||||
chunkX,
|
|
||||||
chunkZ,
|
|
||||||
requiredNeighborChunkRadius(),
|
|
||||||
(final int nx, final int nz) -> new ChunkHolder(new ChunkPos(nx, nz),
|
|
||||||
ChunkHolderManager.MAX_TICKET_LEVEL,
|
|
||||||
freshWorld,
|
|
||||||
threadedLevelLightEngine,
|
|
||||||
null,
|
|
||||||
freshChunkProvider.chunkMap
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return ChunkPyramid.GENERATION_PYRAMID.getStepTo(chunkStatus).apply(
|
|
||||||
worldGenContext,
|
|
||||||
neighbours,
|
|
||||||
chunkAccess
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A light engine that does nothing. As light is calculated after pasting anyway, we can avoid
|
|
||||||
* work this way.
|
|
||||||
*/
|
|
||||||
static class NoOpLightEngine extends ThreadedLevelLightEngine {
|
|
||||||
|
|
||||||
private static final ProcessorMailbox<Runnable> MAILBOX = ProcessorMailbox.create(task -> {
|
|
||||||
}, "fawe-no-op");
|
|
||||||
private static final ProcessorHandle<Message<Runnable>> HANDLE = ProcessorHandle.of("fawe-no-op", m -> {
|
|
||||||
});
|
|
||||||
|
|
||||||
public NoOpLightEngine(final ServerChunkCache chunkProvider) {
|
|
||||||
super(chunkProvider, chunkProvider.chunkMap, false, MAILBOX, HANDLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull CompletableFuture<ChunkAccess> lightChunk(final @NotNull ChunkAccess chunk, final boolean excludeBlocks) {
|
|
||||||
return CompletableFuture.completedFuture(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,67 +1,32 @@
|
|||||||
package com.fastasyncworldedit.bukkit.adapter;
|
package com.fastasyncworldedit.bukkit.adapter;
|
||||||
|
|
||||||
import com.fastasyncworldedit.core.configuration.Settings;
|
|
||||||
import com.fastasyncworldedit.core.queue.IChunkCache;
|
import com.fastasyncworldedit.core.queue.IChunkCache;
|
||||||
import com.fastasyncworldedit.core.queue.IChunkGet;
|
import com.fastasyncworldedit.core.queue.IChunkGet;
|
||||||
import com.fastasyncworldedit.core.queue.implementation.SingleThreadQueueExtent;
|
import com.fastasyncworldedit.core.queue.implementation.SingleThreadQueueExtent;
|
||||||
import com.fastasyncworldedit.core.util.MathMan;
|
import com.fastasyncworldedit.core.util.TaskManager;
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
|
||||||
import com.sk89q.worldedit.WorldEditException;
|
import com.sk89q.worldedit.WorldEditException;
|
||||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
import com.sk89q.worldedit.bukkit.BukkitWorld;
|
import com.sk89q.worldedit.bukkit.BukkitWorld;
|
||||||
import com.sk89q.worldedit.extent.Extent;
|
import com.sk89q.worldedit.extent.Extent;
|
||||||
import com.sk89q.worldedit.function.pattern.Pattern;
|
import com.sk89q.worldedit.function.pattern.Pattern;
|
||||||
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
|
||||||
import com.sk89q.worldedit.math.BlockVector2;
|
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
import com.sk89q.worldedit.regions.CuboidRegion;
|
|
||||||
import com.sk89q.worldedit.regions.Region;
|
import com.sk89q.worldedit.regions.Region;
|
||||||
import com.sk89q.worldedit.world.RegenOptions;
|
import com.sk89q.worldedit.world.RegenOptions;
|
||||||
import com.sk89q.worldedit.world.biome.BiomeType;
|
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
|
||||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
|
||||||
import it.unimi.dsi.fastutil.longs.LongList;
|
|
||||||
import jdk.jfr.Category;
|
|
||||||
import jdk.jfr.Event;
|
|
||||||
import jdk.jfr.Label;
|
|
||||||
import jdk.jfr.Name;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.bukkit.generator.BiomeProvider;
|
import org.bukkit.generator.BiomeProvider;
|
||||||
import org.bukkit.generator.BlockPopulator;
|
|
||||||
import org.bukkit.generator.WorldInfo;
|
import org.bukkit.generator.WorldInfo;
|
||||||
|
|
||||||
import java.util.AbstractList;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.Objects;
|
import java.util.function.BooleanSupplier;
|
||||||
import java.util.Random;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.ThreadFactory;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an abstract regeneration handler.
|
* Represents an abstract regeneration handler.
|
||||||
*
|
|
||||||
* @param <IChunkAccess> the type of the {@code IChunkAccess} of the current Minecraft implementation
|
|
||||||
* @param <ProtoChunk> the type of the {@code ProtoChunk} of the current Minecraft implementation
|
|
||||||
* @param <Chunk> the type of the {@code Chunk} of the current Minecraft implementation
|
|
||||||
* @param <ChunkStatus> the type of the {@code ChunkStatusWrapper} wrapping the {@code ChunkStatus} enum
|
|
||||||
*/
|
*/
|
||||||
public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess, Chunk extends IChunkAccess, ChunkStatus extends Regenerator.ChunkStatusWrapper<IChunkAccess>> {
|
public abstract class Regenerator {
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
|
||||||
|
|
||||||
protected final org.bukkit.World originalBukkitWorld;
|
protected final org.bukkit.World originalBukkitWorld;
|
||||||
protected final Region region;
|
protected final Region region;
|
||||||
@ -69,13 +34,8 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
protected final RegenOptions options;
|
protected final RegenOptions options;
|
||||||
|
|
||||||
//runtime
|
//runtime
|
||||||
protected final LinkedHashMap<ChunkStatus, Concurrency> chunkStatuses = new LinkedHashMap<>(); // TODO (j21): use SequencedMap
|
|
||||||
private final Long2ObjectLinkedOpenHashMap<ProtoChunk> protoChunks = new Long2ObjectLinkedOpenHashMap<>();
|
|
||||||
private final Long2ObjectOpenHashMap<Chunk> chunks = new Long2ObjectOpenHashMap<>();
|
|
||||||
protected boolean generateConcurrent = true;
|
|
||||||
protected long seed;
|
protected long seed;
|
||||||
private ExecutorService executor;
|
protected SingleThreadQueueExtent source;
|
||||||
private SingleThreadQueueExtent source;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes an abstract regeneration handler.
|
* Initializes an abstract regeneration handler.
|
||||||
@ -92,15 +52,6 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
this.options = options;
|
this.options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regenerates the selected {@code Region}.
|
* Regenerates the selected {@code Region}.
|
||||||
*
|
*
|
||||||
@ -122,16 +73,6 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
if (!generate()) {
|
|
||||||
cleanup0();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
cleanup0();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
copyToWorld();
|
copyToWorld();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -144,193 +85,26 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the {@code ProtoChunk} at the given chunk coordinates.
|
* Execute tasks on the main thread during regen.
|
||||||
*
|
|
||||||
* @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) {
|
protected abstract void runTasks(BooleanSupplier shouldKeepTicking);
|
||||||
return protoChunks.get(MathMan.pairInt(x, z));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
private void createSource() {
|
||||||
* 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 {
|
|
||||||
ThreadFactory factory = new ThreadFactoryBuilder()
|
|
||||||
.setNameFormat("FAWE Regenerator - %d")
|
|
||||||
.build();
|
|
||||||
if (generateConcurrent) {
|
|
||||||
//Using concurrent chunk generation
|
|
||||||
executor = Executors.newFixedThreadPool(Settings.settings().QUEUE.PARALLEL_THREADS, factory);
|
|
||||||
} else { // else using sequential chunk generation, concurrent not supported
|
|
||||||
executor = Executors.newSingleThreadExecutor(factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
//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
|
|
||||||
|
|
||||||
// to get the chunks we need to generate in the nth chunk status, we need to know how many chunks
|
|
||||||
// we need to generate in the n + 1 th chunk status. Summing up the margin solves that
|
|
||||||
LinkedHashMap<ChunkStatus, long[]> chunkCoordsForChunkStatus = new LinkedHashMap<>();
|
|
||||||
int borderSum = 1;
|
|
||||||
// TODO (j21): use SequencedMap#sequencedKeySet().reversed()
|
|
||||||
final List<ChunkStatus> reversedKeys = Lists.reverse(new ArrayList<>(chunkStatuses.keySet()));
|
|
||||||
for (final ChunkStatus status : reversedKeys) {
|
|
||||||
chunkCoordsForChunkStatus.put(status, getChunkCoordsRegen(region, borderSum));
|
|
||||||
borderSum += status.requiredNeighborChunkRadius();
|
|
||||||
}
|
|
||||||
|
|
||||||
//create chunks
|
|
||||||
// TODO (j21): use SequencedMap#firstEntry().getKey()
|
|
||||||
for (long xz : chunkCoordsForChunkStatus.get(chunkStatuses.keySet().iterator().next())) {
|
|
||||||
ProtoChunk chunk = createProtoChunk(MathMan.unpairIntX(xz), MathMan.unpairIntY(xz));
|
|
||||||
protoChunks.put(xz, chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
// a memory-efficient, lightweight "list" that calculates index -> ChunkAccess
|
|
||||||
// as needed when accessed
|
|
||||||
class LazyChunkList extends AbstractList<IChunkAccess> {
|
|
||||||
private final int size;
|
|
||||||
private final int minX;
|
|
||||||
private final int minZ;
|
|
||||||
private final int sizeSqrt;
|
|
||||||
|
|
||||||
LazyChunkList(int radius, int centerX, int centerZ) {
|
|
||||||
this.sizeSqrt = radius + 1 + radius; // length of one side
|
|
||||||
this.size = this.sizeSqrt * this.sizeSqrt;
|
|
||||||
this.minX = centerX - radius;
|
|
||||||
this.minZ = centerZ - radius;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public IChunkAccess get(final int index) {
|
|
||||||
Objects.checkIndex(index, size);
|
|
||||||
int absX = (index % sizeSqrt) + minX;
|
|
||||||
int absZ = (index / sizeSqrt) + minZ;
|
|
||||||
return protoChunks.get(MathMan.pairInt(absX, absZ));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@Label("Regeneration")
|
|
||||||
@Category("FAWE")
|
|
||||||
@Name("fawe.regen")
|
|
||||||
class RegenerationEvent extends Event {
|
|
||||||
private String chunkStatus;
|
|
||||||
private int chunksToProcess;
|
|
||||||
}
|
|
||||||
|
|
||||||
//run generation tasks excluding FULL chunk status
|
|
||||||
for (Map.Entry<ChunkStatus, Concurrency> entry : chunkStatuses.entrySet()) {
|
|
||||||
ChunkStatus chunkStatus = entry.getKey();
|
|
||||||
final RegenerationEvent event = new RegenerationEvent();
|
|
||||||
event.begin();
|
|
||||||
event.chunkStatus = chunkStatus.name();
|
|
||||||
int radius = Math.max(1, chunkStatus.requiredNeighborChunkRadius0());
|
|
||||||
|
|
||||||
long[] coords = chunkCoordsForChunkStatus.get(chunkStatus);
|
|
||||||
event.chunksToProcess = coords.length;
|
|
||||||
if (this.generateConcurrent && entry.getValue() == Concurrency.RADIUS) {
|
|
||||||
SequentialTasks<ConcurrentTasks<LongList>> tasks = getChunkStatusTaskRows(coords, radius);
|
|
||||||
for (ConcurrentTasks<LongList> para : tasks) {
|
|
||||||
List<Runnable> scheduled = new ArrayList<>(tasks.size());
|
|
||||||
for (LongList row : para) {
|
|
||||||
scheduled.add(() -> {
|
|
||||||
for (long xz : row) {
|
|
||||||
chunkStatus.processChunkSave(xz, new LazyChunkList(radius, MathMan.unpairIntX(xz),
|
|
||||||
MathMan.unpairIntY(xz)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
runAndWait(scheduled);
|
|
||||||
}
|
|
||||||
} else if (this.generateConcurrent && entry.getValue() == Concurrency.FULL) {
|
|
||||||
// every chunk can be processed individually
|
|
||||||
List<Runnable> scheduled = new ArrayList<>(coords.length);
|
|
||||||
for (long xz : coords) {
|
|
||||||
scheduled.add(() -> chunkStatus.processChunkSave(xz, new LazyChunkList(radius, MathMan.unpairIntX(xz),
|
|
||||||
MathMan.unpairIntY(xz))));
|
|
||||||
}
|
|
||||||
runAndWait(scheduled);
|
|
||||||
} else { // Concurrency.NONE or generateConcurrent == false
|
|
||||||
// run sequential but submit to different thread
|
|
||||||
// running regen on the main thread otherwise triggers async-only events on the main thread
|
|
||||||
executor.submit(() -> {
|
|
||||||
for (long xz : coords) {
|
|
||||||
chunkStatus.processChunkSave(xz, new LazyChunkList(radius, MathMan.unpairIntX(xz),
|
|
||||||
MathMan.unpairIntY(xz)));
|
|
||||||
}
|
|
||||||
}).get(); // wait until finished this step
|
|
||||||
}
|
|
||||||
event.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
//convert to proper chunks
|
|
||||||
// TODO (j21): use SequencedMap#firstEntry().getValue()
|
|
||||||
for (long xz : chunkCoordsForChunkStatus.values().iterator().next()) {
|
|
||||||
ProtoChunk proto = protoChunks.get(xz);
|
|
||||||
chunks.put(xz, createChunk(proto));
|
|
||||||
}
|
|
||||||
|
|
||||||
//final chunkstatus
|
|
||||||
ChunkStatus FULL = getFullChunkStatus();
|
|
||||||
// TODO (j21): use SequencedMap#firstEntry().getValue()
|
|
||||||
for (long xz : chunkCoordsForChunkStatus.values().iterator().next()) { //FULL.requiredNeighbourChunkRadius() == 0!
|
|
||||||
Chunk chunk = chunks.get(xz);
|
|
||||||
FULL.processChunkSave(xz, List.of(chunk));
|
|
||||||
}
|
|
||||||
|
|
||||||
//populate
|
|
||||||
List<BlockPopulator> populators = getBlockPopulators();
|
|
||||||
// TODO (j21): use SequencedMap#firstEntry().getValue()
|
|
||||||
for (long xz : chunkCoordsForChunkStatus.values().iterator().next()) {
|
|
||||||
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 = new SingleThreadQueueExtent(
|
||||||
BukkitWorld.HAS_MIN_Y ? originalBukkitWorld.getMinHeight() : 0,
|
BukkitWorld.HAS_MIN_Y ? originalBukkitWorld.getMinHeight() : 0,
|
||||||
BukkitWorld.HAS_MIN_Y ? originalBukkitWorld.getMaxHeight() : 256
|
BukkitWorld.HAS_MIN_Y ? originalBukkitWorld.getMaxHeight() : 256
|
||||||
);
|
);
|
||||||
source.init(target, initSourceQueueCache(), null);
|
source.init(target, initSourceQueueCache(), null);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void runAndWait(final List<Runnable> tasks) {
|
|
||||||
try {
|
|
||||||
List<Future<?>> futures = new ArrayList<>();
|
|
||||||
tasks.forEach(task -> futures.add(executor.submit(task)));
|
|
||||||
for (Future<?> future : futures) {
|
|
||||||
future.get();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.catching(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copyToWorld() {
|
private void copyToWorld() {
|
||||||
|
createSource();
|
||||||
|
final long timeoutPerTick = TimeUnit.MILLISECONDS.toNanos(10);
|
||||||
|
int taskId = TaskManager.taskManager().repeat(() -> {
|
||||||
|
final long startTime = System.nanoTime();
|
||||||
|
runTasks(() -> System.nanoTime() - startTime < timeoutPerTick);
|
||||||
|
}, 1);
|
||||||
//Setting Blocks
|
//Setting Blocks
|
||||||
boolean genbiomes = options.shouldRegenBiomes();
|
boolean genbiomes = options.shouldRegenBiomes();
|
||||||
boolean hasBiome = options.hasBiomeType();
|
boolean hasBiome = options.hasBiomeType();
|
||||||
@ -343,6 +117,7 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
} else if (genbiomes) {
|
} else if (genbiomes) {
|
||||||
target.setBlocks(region, new WithBiomePlacementPattern(vec -> source.getBiome(vec)));
|
target.setBlocks(region, new WithBiomePlacementPattern(vec -> source.getBiome(vec)));
|
||||||
}
|
}
|
||||||
|
TaskManager.taskManager().cancel(taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PlacementPattern implements Pattern {
|
private class PlacementPattern implements Pattern {
|
||||||
@ -382,9 +157,6 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
|
|
||||||
//functions to be implemented by sub class
|
//functions to be implemented by sub class
|
||||||
private void cleanup0() {
|
private void cleanup0() {
|
||||||
if (executor != null) {
|
|
||||||
executor.shutdownNow();
|
|
||||||
}
|
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,47 +188,6 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
*/
|
*/
|
||||||
protected abstract void cleanup();
|
protected abstract void cleanup();
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<BlockPopulator> 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<IChunkGet>} here. Use will need the {@code getChunkAt} function
|
* Implement the initialization an {@code IChunkCache<IChunkGet>} here. Use will need the {@code getChunkAt} function
|
||||||
*
|
*
|
||||||
@ -464,106 +195,6 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
*/
|
*/
|
||||||
protected abstract IChunkCache<IChunkGet> initSourceQueueCache();
|
protected abstract IChunkCache<IChunkGet> initSourceQueueCache();
|
||||||
|
|
||||||
//algorithms
|
|
||||||
private long[] getChunkCoordsRegen(Region region, int border) { //needs to be square num of chunks
|
|
||||||
BlockVector3 oldMin = region.getMinimumPoint();
|
|
||||||
BlockVector3 newMin = BlockVector3.at(
|
|
||||||
(oldMin.x() >> 4 << 4) - border * 16,
|
|
||||||
oldMin.y(),
|
|
||||||
(oldMin.z() >> 4 << 4) - border * 16
|
|
||||||
);
|
|
||||||
BlockVector3 oldMax = region.getMaximumPoint();
|
|
||||||
BlockVector3 newMax = BlockVector3.at(
|
|
||||||
(oldMax.x() >> 4 << 4) + (border + 1) * 16 - 1,
|
|
||||||
oldMax.y(),
|
|
||||||
(oldMax.z() >> 4 << 4) + (border + 1) * 16 - 1
|
|
||||||
);
|
|
||||||
Region adjustedRegion = new CuboidRegion(newMin, newMax);
|
|
||||||
return adjustedRegion.getChunks().stream()
|
|
||||||
.sorted(Comparator
|
|
||||||
.comparingInt(BlockVector2::z)
|
|
||||||
.thenComparingInt(BlockVector2::x)) //needed for RegionLimitedWorldAccess
|
|
||||||
.mapToLong(c -> MathMan.pairInt(c.x(), c.z()))
|
|
||||||
.toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 concurrently (ChunkStatus
|
|
||||||
* .requiredNeighborRadius)
|
|
||||||
* @return a list of chunkcoords rows that may be executed concurrently
|
|
||||||
*/
|
|
||||||
private SequentialTasks<ConcurrentTasks<LongList>> getChunkStatusTaskRows(
|
|
||||||
long[] allCoords,
|
|
||||||
int requiredNeighborChunkRadius
|
|
||||||
) {
|
|
||||||
int requiredNeighbors = Math.max(0, requiredNeighborChunkRadius);
|
|
||||||
|
|
||||||
final int coordsCount = allCoords.length;
|
|
||||||
long first = coordsCount == 0 ? 0 : allCoords[0];
|
|
||||||
long last = coordsCount == 0 ? 0 : allCoords[coordsCount - 1];
|
|
||||||
int minX = MathMan.unpairIntX(first);
|
|
||||||
int maxX = MathMan.unpairIntX(last);
|
|
||||||
int minZ = MathMan.unpairIntY(first);
|
|
||||||
int maxZ = MathMan.unpairIntY(last);
|
|
||||||
SequentialTasks<ConcurrentTasks<LongList>> tasks;
|
|
||||||
if (maxZ - minZ > maxX - minX) {
|
|
||||||
int numlists = Math.min(requiredNeighbors * 2 + 1, maxX - minX + 1);
|
|
||||||
|
|
||||||
Int2ObjectOpenHashMap<LongList> byX = new Int2ObjectOpenHashMap<>();
|
|
||||||
int expectedListLength = (coordsCount + 1) / (maxX - minX);
|
|
||||||
|
|
||||||
//init lists
|
|
||||||
for (int i = minX; i <= maxX; i++) {
|
|
||||||
byX.put(i, new LongArrayList(expectedListLength));
|
|
||||||
}
|
|
||||||
|
|
||||||
//sort into lists by x coord
|
|
||||||
for (long allCoord : allCoords) {
|
|
||||||
byX.get(MathMan.unpairIntX(allCoord)).add(allCoord);
|
|
||||||
}
|
|
||||||
|
|
||||||
//create parallel tasks
|
|
||||||
tasks = new SequentialTasks<>(numlists);
|
|
||||||
for (int offset = 0; offset < numlists; offset++) {
|
|
||||||
ConcurrentTasks<LongList> 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<LongList> byZ = new Int2ObjectOpenHashMap<>();
|
|
||||||
int expectedListLength = (coordsCount + 1) / (maxZ - minZ + 2);
|
|
||||||
|
|
||||||
//init lists
|
|
||||||
for (int i = minZ; i <= maxZ; i++) {
|
|
||||||
byZ.put(i, new LongArrayList(expectedListLength));
|
|
||||||
}
|
|
||||||
|
|
||||||
//sort into lists by x coord
|
|
||||||
for (long allCoord : allCoords) {
|
|
||||||
byZ.get(MathMan.unpairIntY(allCoord)).add(allCoord);
|
|
||||||
}
|
|
||||||
|
|
||||||
//create parallel tasks
|
|
||||||
tasks = new SequentialTasks<>(numlists);
|
|
||||||
for (int offset = 0; offset < numlists; offset++) {
|
|
||||||
ConcurrentTasks<LongList> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected BiomeProvider getBiomeProvider() {
|
protected BiomeProvider getBiomeProvider() {
|
||||||
if (options.hasBiomeType()) {
|
if (options.hasBiomeType()) {
|
||||||
return new SingleBiomeProvider();
|
return new SingleBiomeProvider();
|
||||||
@ -579,103 +210,6 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
|
|||||||
NONE
|
NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is used to wrap the ChunkStatus of the current Minecraft implementation and as the implementation to execute a chunk generation step.
|
|
||||||
*
|
|
||||||
* @param <IChunkAccess> the IChunkAccess class of the current Minecraft implementation
|
|
||||||
*/
|
|
||||||
public static abstract class ChunkStatusWrapper<IChunkAccess> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the required neighbor chunk radius the wrapped {@code ChunkStatus} requires.
|
|
||||||
*
|
|
||||||
* @return the radius of required neighbor chunks
|
|
||||||
*/
|
|
||||||
public abstract int requiredNeighborChunkRadius();
|
|
||||||
|
|
||||||
int requiredNeighborChunkRadius0() {
|
|
||||||
return Math.max(0, requiredNeighborChunkRadius());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 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 CompletableFuture<?> processChunk(List<IChunkAccess> accessibleChunks);
|
|
||||||
|
|
||||||
void processChunkSave(long xz, List<IChunkAccess> accessibleChunks) {
|
|
||||||
try {
|
|
||||||
processChunk(accessibleChunks).get();
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.error("Error while running {} on chunk {}/{}",
|
|
||||||
name(), MathMan.unpairIntX(xz), MathMan.unpairIntY(xz), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return name();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class SequentialTasks<T> extends Tasks<T> {
|
|
||||||
|
|
||||||
public SequentialTasks(int expectedSize) {
|
|
||||||
super(expectedSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ConcurrentTasks<T> extends Tasks<T> {
|
|
||||||
|
|
||||||
public ConcurrentTasks(int expectedSize) {
|
|
||||||
super(expectedSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Tasks<T> implements Iterable<T> {
|
|
||||||
|
|
||||||
private final List<T> tasks;
|
|
||||||
|
|
||||||
public Tasks(int expectedSize) {
|
|
||||||
tasks = new ArrayList<>(expectedSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(T task) {
|
|
||||||
tasks.add(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<T> list() {
|
|
||||||
return tasks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int size() {
|
|
||||||
return tasks.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterator<T> iterator() {
|
|
||||||
return tasks.iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return tasks.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SingleBiomeProvider extends BiomeProvider {
|
public class SingleBiomeProvider extends BiomeProvider {
|
||||||
|
|
||||||
private final org.bukkit.block.Biome biome = BukkitAdapter.adapt(options.getBiomeType());
|
private final org.bukkit.block.Biome biome = BukkitAdapter.adapt(options.getBiomeType());
|
||||||
|
Loading…
Reference in New Issue
Block a user