Regeneration Options (#1415)

* Add RegenOptions to the API

* Integerate regen options into //regen

* Rename isRegenBiomes to shouldRegenBiomes

* Go through the EditSession for setting biomes

* Respect 3D biome support in regen

* Add RegenOptions implementation for 1.16 Bukkit adapter.

Co-authored-by: wizjany <wizjany@gmail.com>
This commit is contained in:
Octavia Togami 2020-07-03 22:17:34 -07:00 committed by GitHub
parent cc46de951b
commit bf6cd1ea08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 331 additions and 39 deletions

View File

@ -4,4 +4,4 @@ version=7.2.0-SNAPSHOT
org.gradle.jvmargs=-Xmx1512M
loom.version=0.4.30
mixin.version=0.8.11
mixin.version=0.8+build.18

View File

@ -38,6 +38,7 @@
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.util.TreeGenerator;
import com.sk89q.worldedit.world.AbstractWorld;
import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockStateHolder;
@ -210,11 +211,11 @@ public int getBlockLightLevel(BlockVector3 pt) {
}
@Override
public boolean regenerate(Region region, EditSession editSession) {
public boolean regenerate(Region region, EditSession editSession, RegenOptions options) {
BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter();
try {
if (adapter != null) {
return adapter.regenerate(getWorld(), region, editSession);
return adapter.regenerate(getWorld(), region, editSession, options);
} else {
throw new UnsupportedOperationException("Missing BukkitImplAdapater for this version.");
}

View File

@ -32,6 +32,7 @@
import com.sk89q.worldedit.util.SideEffect;
import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.world.DataFixer;
import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockType;
@ -220,9 +221,10 @@ default OptionalInt getInternalBlockStateId(BlockState state) {
* @param world the world to regen in
* @param region the region to regen
* @param session the session to use for setting blocks
* @param options the regeneration options
* @return true on success, false on failure
*/
default boolean regenerate(World world, Region region, EditSession session) {
default boolean regenerate(World world, Region region, EditSession session, RegenOptions options) {
throw new UnsupportedOperationException("This adapter does not support regeneration.");
}
}

View File

@ -38,6 +38,7 @@
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.util.TreeGenerator;
import com.sk89q.worldedit.world.AbstractWorld;
import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
@ -106,7 +107,7 @@ public void simulateBlockMine(BlockVector3 position) {
}
@Override
public boolean regenerate(Region region, EditSession editSession) {
public boolean regenerate(Region region, EditSession editSession, RegenOptions options) {
return false;
}

View File

@ -55,11 +55,12 @@
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.regions.RegionOperationException;
import com.sk89q.worldedit.util.TreeGenerator.TreeType;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.util.formatting.component.TextUtils;
import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.World;
import org.enginehub.piston.annotation.Command;
import org.enginehub.piston.annotation.CommandContainer;
import org.enginehub.piston.annotation.param.Arg;
@ -406,13 +407,21 @@ public int stack(Actor actor, World world, EditSession editSession, LocalSession
)
@CommandPermissions("worldedit.regen")
@Logging(REGION)
public void regenerateChunk(Actor actor, World world, LocalSession session,
EditSession editSession, @Selection Region region) throws WorldEditException {
void regenerate(Actor actor, World world, LocalSession session, EditSession editSession,
@Selection Region region,
@Arg(desc = "The seed to regenerate with, otherwise uses world seed", def = "")
Long seed,
@Switch(name = 'b', desc = "Regenerate biomes as well")
boolean regenBiomes) {
Mask mask = session.getMask();
boolean success;
try {
session.setMask(null);
success = world.regenerate(region, editSession);
RegenOptions options = RegenOptions.builder()
.seed(seed)
.regenBiomes(regenBiomes)
.build();
success = world.regenerate(region, editSession, options);
} finally {
session.setMask(mask);
}

View File

@ -115,7 +115,7 @@ public void simulateBlockMine(BlockVector3 position) {
}
@Override
public boolean regenerate(Region region, EditSession editSession) {
public boolean regenerate(Region region, EditSession editSession, RegenOptions options) {
return false;
}

View File

@ -0,0 +1,103 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.world;
import com.google.auto.value.AutoValue;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.regions.Region;
import java.util.OptionalLong;
import javax.annotation.Nullable;
/**
* Regeneration options for {@link World#regenerate(Region, EditSession, RegenOptions)}.
*/
@AutoValue
public abstract class RegenOptions {
/**
* Creates a new options builder.
*
* @return the builder
*/
public static Builder builder() {
return new AutoValue_RegenOptions.Builder().seed(OptionalLong.empty()).regenBiomes(false);
}
@AutoValue.Builder
public abstract static class Builder {
/**
* Sets the seed to regenerate with. Defaults to {@code null}.
*
* <p>
* Use {@code null} to use the world's current seed.
* </p>
*
* @param seed the seed to regenerate with
* @return this builder
*/
public final Builder seed(@Nullable Long seed) {
return seed(seed == null ? OptionalLong.empty() : OptionalLong.of(seed));
}
// AV doesn't like us using @Nullable Long for some reason
abstract Builder seed(OptionalLong seed);
/**
* Turn on or off applying the biomes from the regenerated chunk. Defaults to {@code false}.
*
* @param regenBiomes {@code true} to apply biomes
* @return this builder
*/
public abstract Builder regenBiomes(boolean regenBiomes);
/**
* Build the options object.
*
* @return the options object
*/
public abstract RegenOptions build();
}
RegenOptions() {
}
/**
* The seed to regenerate with.
*
* <p>
* {@link OptionalLong#empty()} if the world's original seed should be used.
* </p>
*/
public abstract OptionalLong getSeed();
abstract boolean isRegenBiomes();
/**
* Whether biomes should be regenerated.
*/
public final boolean shouldRegenBiomes() {
return isRegenBiomes();
}
}

View File

@ -27,6 +27,7 @@
import com.sk89q.worldedit.extension.platform.Platform;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.internal.util.NonAbstractForCompatibility;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.Vector3;
@ -211,7 +212,25 @@ default boolean notifyAndLightBlock(BlockVector3 position, BlockState previousTy
* @param editSession the {@link EditSession}
* @return true if re-generation was successful
*/
boolean regenerate(Region region, EditSession editSession);
default boolean regenerate(Region region, EditSession editSession) {
return regenerate(region, editSession, RegenOptions.builder().build());
}
/**
* Regenerate an area.
*
* @param region the region
* @param editSession the {@link EditSession}
* @param options the regeneration options
* @return true if regeneration was successful
*/
@NonAbstractForCompatibility(
delegateName = "regenerate",
delegateParams = { Region.class, EditSession.class }
)
default boolean regenerate(Region region, EditSession editSession, RegenOptions options) {
return regenerate(region, editSession);
}
/**
* Generate a tree at the given position.

View File

@ -25,6 +25,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.mojang.serialization.Dynamic;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
@ -36,6 +37,7 @@
import com.sk89q.worldedit.fabric.internal.ExtendedMinecraftServer;
import com.sk89q.worldedit.fabric.internal.FabricWorldNativeAccess;
import com.sk89q.worldedit.fabric.internal.NBTConverter;
import com.sk89q.worldedit.fabric.mixin.AccessorLevelProperties;
import com.sk89q.worldedit.fabric.mixin.AccessorServerChunkManager;
import com.sk89q.worldedit.internal.Constants;
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
@ -49,6 +51,7 @@
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.util.TreeGenerator.TreeType;
import com.sk89q.worldedit.world.AbstractWorld;
import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
@ -58,10 +61,12 @@
import com.sk89q.worldedit.world.weather.WeatherTypes;
import net.minecraft.block.Block;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.datafixer.NbtOps;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.ItemEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemUsageContext;
import net.minecraft.nbt.Tag;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.world.ServerChunkManager;
import net.minecraft.server.world.ServerWorld;
@ -72,6 +77,7 @@
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.World;
import net.minecraft.world.WorldProperties;
import net.minecraft.world.biome.source.BiomeAccessType;
@ -81,6 +87,8 @@
import net.minecraft.world.chunk.ChunkManager;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.chunk.WorldChunk;
import net.minecraft.world.dimension.DimensionOptions;
import net.minecraft.world.gen.GeneratorOptions;
import net.minecraft.world.gen.feature.ConfiguredFeature;
import net.minecraft.world.gen.feature.DefaultBiomeFeatures;
import net.minecraft.world.gen.feature.Feature;
@ -88,7 +96,6 @@
import net.minecraft.world.level.storage.LevelStorage;
import org.apache.commons.io.FileUtils;
import javax.annotation.Nullable;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.file.Files;
@ -96,6 +103,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -106,6 +114,8 @@
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
@ -202,6 +212,10 @@ public boolean fullySupports3DBiomes() {
public BiomeType getBiome(BlockVector3 position) {
checkNotNull(position);
Chunk chunk = getWorld().getChunk(position.getX() >> 4, position.getZ() >> 4);
return getBiomeInChunk(position, chunk);
}
private BiomeType getBiomeInChunk(BlockVector3 position, Chunk chunk) {
BiomeArray biomeArray = checkNotNull(chunk.getBiomeArray());
return FabricAdapter.adapt(biomeArray.getBiomeForNoiseGen(position.getX() >> 2, position.getY() >> 2, position.getZ() >> 2));
}
@ -211,10 +225,7 @@ public boolean setBiome(BlockVector3 position, BiomeType biome) {
checkNotNull(position);
checkNotNull(biome);
Chunk chunk = getWorld().getChunk(position.getBlockX() >> 4, position.getBlockZ() >> 4, ChunkStatus.FULL, false);
if (chunk == null) {
return false;
}
Chunk chunk = getWorld().getChunk(position.getBlockX() >> 4, position.getBlockZ() >> 4);
MutableBiomeArray biomeArray = MutableBiomeArray.inject(checkNotNull(chunk.getBiomeArray()));
biomeArray.setBiome(position.getX(), position.getY(), position.getZ(), FabricAdapter.adapt(biome));
chunk.setShouldSave(true);
@ -273,7 +284,7 @@ public void simulateBlockMine(BlockVector3 position) {
}
@Override
public boolean regenerate(Region region, EditSession editSession) {
public boolean regenerate(Region region, EditSession editSession, RegenOptions options) {
// Don't even try to regen if it's going to fail.
ChunkManager provider = getWorld().getChunkManager();
if (!(provider instanceof ServerChunkManager)) {
@ -281,7 +292,7 @@ public boolean regenerate(Region region, EditSession editSession) {
}
try {
doRegen(region, editSession);
doRegen(region, editSession, options);
} catch (Exception e) {
throw new IllegalStateException("Regen failed", e);
}
@ -289,7 +300,7 @@ public boolean regenerate(Region region, EditSession editSession) {
return true;
}
private void doRegen(Region region, EditSession editSession) throws Exception {
private void doRegen(Region region, EditSession editSession, RegenOptions options) throws Exception {
Path tempDir = Files.createTempDirectory("WorldEditWorldGen");
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
@ -300,34 +311,72 @@ private void doRegen(Region region, EditSession editSession) throws Exception {
LevelStorage levelStorage = LevelStorage.create(tempDir);
try (LevelStorage.Session session = levelStorage.createSession("WorldEditTempGen")) {
ServerWorld originalWorld = (ServerWorld) getWorld();
long seed = options.getSeed().orElse(originalWorld.getSeed());
AccessorLevelProperties levelProperties = (AccessorLevelProperties)
originalWorld.getServer().getSaveProperties();
GeneratorOptions originalOpts = levelProperties.getGeneratorOptions();
GeneratorOptions newOpts = GeneratorOptions.CODEC
.encodeStart(NbtOps.INSTANCE, originalOpts)
.flatMap(tag ->
GeneratorOptions.CODEC.parse(
recursivelySetSeed(new Dynamic<>(NbtOps.INSTANCE, tag), seed, new HashSet<>())
)
)
.result()
.orElseThrow(() -> new IllegalStateException("Unable to map GeneratorOptions"));
levelProperties.setGeneratorOptions(newOpts);
RegistryKey<World> worldRegKey = originalWorld.getRegistryKey();
DimensionOptions dimGenOpts = newOpts.getDimensionMap().get(worldRegKey.getValue());
checkNotNull(dimGenOpts, "No DimensionOptions for %s", worldRegKey);
try (ServerWorld serverWorld = new ServerWorld(
originalWorld.getServer(), Util.getServerWorkerExecutor(), session,
((ServerWorldProperties) originalWorld.getLevelProperties()),
originalWorld.getRegistryKey(),
worldRegKey,
originalWorld.getDimensionRegistryKey(),
originalWorld.getDimension(),
new WorldEditGenListener(),
originalWorld.getChunkManager().getChunkGenerator(),
dimGenOpts.getChunkGenerator(),
originalWorld.isDebugWorld(),
originalWorld.getSeed(),
seed,
// No spawners are needed for this world.
ImmutableList.of(),
// This controls ticking, we don't need it so set it to false.
false
)) {
regenForWorld(region, editSession, serverWorld);
regenForWorld(region, editSession, serverWorld, options);
// drive the server executor until all tasks are popped off
while (originalWorld.getServer().runTask()) {
Thread.yield();
}
} finally {
levelProperties.setGeneratorOptions(originalOpts);
}
} finally {
FileUtils.deleteDirectory(tempDir.toFile());
}
}
private void regenForWorld(Region region, EditSession editSession, ServerWorld serverWorld) throws MaxChangedBlocksException {
@SuppressWarnings("unchecked")
private Dynamic<Tag> recursivelySetSeed(Dynamic<Tag> dynamic, long seed, Set<Dynamic<Tag>> seen) {
if (!seen.add(dynamic)) {
return dynamic;
}
return dynamic.updateMapValues(pair -> {
if (pair.getFirst().asString("").equals("seed")) {
return pair.mapSecond(v -> v.createLong(seed));
}
if (pair.getSecond().getValue() instanceof net.minecraft.nbt.CompoundTag) {
return pair.mapSecond(v -> recursivelySetSeed((Dynamic<Tag>) v, seed, seen));
}
return pair;
});
}
private void regenForWorld(Region region, EditSession editSession, ServerWorld serverWorld,
RegenOptions options) throws MaxChangedBlocksException {
List<CompletableFuture<Chunk>> chunkLoadings = submitChunkLoadTasks(region, serverWorld);
// drive executor until loading finishes
@ -361,6 +410,13 @@ private void regenForWorld(Region region, EditSession editSession, ServerWorld s
state = state.toBaseBlock(NBTConverter.fromNative(tag));
}
editSession.setBlock(vec, state);
if (options.shouldRegenBiomes()) {
if (!editSession.fullySupports3DBiomes()) {
vec = vec.withY(0);
}
editSession.setBiome(vec, getBiomeInChunk(vec, chunk));
}
}
}

View File

@ -0,0 +1,37 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.fabric.mixin;
import net.minecraft.world.SaveProperties;
import net.minecraft.world.gen.GeneratorOptions;
import net.minecraft.world.level.LevelProperties;
import net.minecraft.world.level.ServerWorldProperties;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(LevelProperties.class)
public interface AccessorLevelProperties extends SaveProperties {
@Accessor("field_25425")
@Mutable
void setGeneratorOptions(GeneratorOptions options);
}

View File

@ -7,6 +7,7 @@
"MixinServerPlayerEntity",
"MixinMinecraftServer",
"AccessorClientSettingsC2SPacket",
"AccessorLevelProperties",
"AccessorServerChunkManager"
],
"server": [

View File

@ -25,6 +25,8 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.mojang.serialization.Codec;
import com.mojang.serialization.Dynamic;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
@ -49,6 +51,7 @@
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.util.TreeGenerator.TreeType;
import com.sk89q.worldedit.world.AbstractWorld;
import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
@ -63,14 +66,18 @@
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemUseContext;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.INBT;
import net.minecraft.nbt.NBTDynamicOps;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Hand;
import net.minecraft.util.RegistryKey;
import net.minecraft.util.Util;
import net.minecraft.util.concurrent.ThreadTaskExecutor;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.Dimension;
import net.minecraft.world.World;
import net.minecraft.world.biome.BiomeContainer;
import net.minecraft.world.biome.ColumnFuzzedBiomeMagnifier;
@ -81,14 +88,15 @@
import net.minecraft.world.chunk.IChunk;
import net.minecraft.world.gen.feature.ConfiguredFeature;
import net.minecraft.world.gen.feature.Feature;
import net.minecraft.world.gen.settings.DimensionGeneratorSettings;
import net.minecraft.world.server.ServerChunkProvider;
import net.minecraft.world.server.ServerWorld;
import net.minecraft.world.storage.IServerWorldInfo;
import net.minecraft.world.storage.IWorldInfo;
import net.minecraft.world.storage.SaveFormat;
import net.minecraft.world.storage.ServerWorldInfo;
import org.apache.commons.io.FileUtils;
import javax.annotation.Nullable;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.file.Files;
@ -96,6 +104,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -106,6 +115,8 @@
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
@ -205,6 +216,10 @@ public BiomeType getBiome(BlockVector3 position) {
checkNotNull(position);
IChunk chunk = getWorld().getChunk(position.getBlockX() >> 4, position.getBlockZ() >> 4);
return getBiomeInChunk(position, chunk);
}
private BiomeType getBiomeInChunk(BlockVector3 position, IChunk chunk) {
BiomeContainer biomes = checkNotNull(chunk.getBiomes());
return ForgeAdapter.adapt(biomes.getNoiseBiome(position.getX() >> 2, position.getY() >> 2, position.getZ() >> 2));
}
@ -214,11 +229,8 @@ public boolean setBiome(BlockVector3 position, BiomeType biome) {
checkNotNull(position);
checkNotNull(biome);
IChunk chunk = getWorld().getChunk(position.getBlockX() >> 4, position.getBlockZ() >> 4, ChunkStatus.FULL, false);
BiomeContainer container = chunk == null ? null : chunk.getBiomes();
if (chunk == null || container == null) {
return false;
}
IChunk chunk = getWorld().getChunk(position.getBlockX() >> 4, position.getBlockZ() >> 4);
BiomeContainer container = checkNotNull(chunk.getBiomes());
int idx = BiomeMath.computeBiomeIndex(position.getX(), position.getY(), position.getZ());
container.biomes[idx] = ForgeAdapter.adapt(biome);
chunk.setModified(true);
@ -282,7 +294,7 @@ public void simulateBlockMine(BlockVector3 position) {
// For unmapped regen names, see Fabric!
@Override
public boolean regenerate(Region region, EditSession editSession) {
public boolean regenerate(Region region, EditSession editSession, RegenOptions options) {
// Don't even try to regen if it's going to fail.
AbstractChunkProvider provider = getWorld().getChunkProvider();
if (!(provider instanceof ServerChunkProvider)) {
@ -290,7 +302,7 @@ public boolean regenerate(Region region, EditSession editSession) {
}
try {
doRegen(region, editSession);
doRegen(region, editSession, options);
} catch (Exception e) {
throw new IllegalStateException("Regen failed", e);
}
@ -298,7 +310,7 @@ public boolean regenerate(Region region, EditSession editSession) {
return true;
}
private void doRegen(Region region, EditSession editSession) throws Exception {
private void doRegen(Region region, EditSession editSession, RegenOptions options) throws Exception {
Path tempDir = Files.createTempDirectory("WorldEditWorldGen");
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
@ -309,34 +321,74 @@ private void doRegen(Region region, EditSession editSession) throws Exception {
SaveFormat levelStorage = SaveFormat.func_237269_a_(tempDir);
try (SaveFormat.LevelSave session = levelStorage.func_237274_c_("WorldEditTempGen")) {
ServerWorld originalWorld = (ServerWorld) getWorld();
long seed = options.getSeed().orElse(originalWorld.getSeed());
ServerWorldInfo levelProperties =
(ServerWorldInfo) originalWorld.getServer().func_240793_aU_();
DimensionGeneratorSettings originalOpts = levelProperties.field_237343_c_;
Codec<DimensionGeneratorSettings> dimCodec = DimensionGeneratorSettings.field_236201_a_;
DimensionGeneratorSettings newOpts = dimCodec
.encodeStart(NBTDynamicOps.INSTANCE, originalOpts)
.flatMap(tag ->
dimCodec.parse(
recursivelySetSeed(new Dynamic<>(NBTDynamicOps.INSTANCE, tag), seed, new HashSet<>())
)
)
.result()
.orElseThrow(() -> new IllegalStateException("Unable to map GeneratorOptions"));
levelProperties.field_237343_c_ = newOpts;
RegistryKey<World> worldRegKey = originalWorld.func_234923_W_();
Dimension dimGenOpts = newOpts.func_236224_e_()
.getOrDefault(worldRegKey.func_240901_a_());
checkNotNull(dimGenOpts, "No DimensionOptions for %s", worldRegKey);
try (ServerWorld serverWorld = new ServerWorld(
originalWorld.getServer(), Util.getServerExecutor(), session,
((IServerWorldInfo) originalWorld.getWorldInfo()),
originalWorld.func_234923_W_(),
worldRegKey,
originalWorld.func_234922_V_(),
originalWorld.func_230315_m_(),
new WorldEditGenListener(),
originalWorld.getChunkProvider().getChunkGenerator(),
dimGenOpts.func_236064_c_(),
originalWorld.func_234925_Z_(),
originalWorld.getSeed(),
seed,
// No spawners are needed for this world.
ImmutableList.of(),
// This controls ticking, we don't need it so set it to false.
false
)) {
regenForWorld(region, editSession, serverWorld);
regenForWorld(region, editSession, serverWorld, options);
// drive the server executor until all tasks are popped off
while (originalWorld.getServer().driveOne()) {
Thread.yield();
}
} finally {
levelProperties.field_237343_c_ = originalOpts;
}
} finally {
FileUtils.deleteDirectory(tempDir.toFile());
}
}
private void regenForWorld(Region region, EditSession editSession, ServerWorld serverWorld) throws MaxChangedBlocksException {
@SuppressWarnings("unchecked")
private Dynamic<INBT> recursivelySetSeed(Dynamic<INBT> dynamic, long seed, Set<Dynamic<INBT>> seen) {
if (!seen.add(dynamic)) {
return dynamic;
}
return dynamic.updateMapValues(pair -> {
if (pair.getFirst().asString("").equals("seed")) {
return pair.mapSecond(v -> v.createLong(seed));
}
if (pair.getSecond().getValue() instanceof CompoundNBT) {
return pair.mapSecond(v -> recursivelySetSeed((Dynamic<INBT>) v, seed, seen));
}
return pair;
});
}
private void regenForWorld(Region region, EditSession editSession, ServerWorld serverWorld,
RegenOptions options) throws MaxChangedBlocksException {
List<CompletableFuture<IChunk>> chunkLoadings = submitChunkLoadTasks(region, serverWorld);
// drive executor until loading finishes
@ -370,6 +422,13 @@ private void regenForWorld(Region region, EditSession editSession, ServerWorld s
state = state.toBaseBlock(NBTConverter.fromNative(tag));
}
editSession.setBlock(vec, state);
if (options.shouldRegenBiomes()) {
if (!editSession.fullySupports3DBiomes()) {
vec = vec.withY(0);
}
editSession.setBiome(vec, getBiomeInChunk(vec, chunk));
}
}
}

View File

@ -1,7 +1,10 @@
public net.minecraft.server.MinecraftServer field_211151_aa # serverTime
# For regen
public net.minecraft.server.MinecraftServer field_71310_m # anvilConverterForAnvilFile (really the save session field)
public net.minecraft.world.server.ServerChunkProvider field_217243_i # executor
# getChunkFuture (name from fabric)
public net.minecraft.world.server.ServerChunkProvider func_217233_c(IILnet/minecraft/world/chunk/ChunkStatus;Z)Ljava/util/concurrent/CompletableFuture;
public net.minecraft.entity.player.ServerPlayerEntity field_71148_cg # language
public net.minecraft.world.biome.BiomeContainer field_227054_f_ # biomes
public-f net.minecraft.world.storage.ServerWorldInfo field_237343_c_ # this is the DimensionGeneratorSettings for SWI

View File

@ -35,6 +35,7 @@
import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.world.AbstractWorld;
import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockStateHolder;
@ -182,7 +183,7 @@ public boolean notifyAndLightBlock(BlockVector3 position, com.sk89q.worldedit.wo
}
@Override
public boolean regenerate(Region region, EditSession editSession) {
public boolean regenerate(Region region, EditSession editSession, RegenOptions options) {
return false;
}