mirror of
https://github.com/EngineHub/WorldEdit.git
synced 2025-02-17 13:20:23 +08:00
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:
parent
cc46de951b
commit
bf6cd1ea08
@ -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
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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.");
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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.
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
"MixinServerPlayerEntity",
|
||||
"MixinMinecraftServer",
|
||||
"AccessorClientSettingsC2SPacket",
|
||||
"AccessorLevelProperties",
|
||||
"AccessorServerChunkManager"
|
||||
],
|
||||
"server": [
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user