From 598219608bb7eb87dfe4d6531710fbf67551ef32 Mon Sep 17 00:00:00 2001 From: gnembon Date: Mon, 17 Aug 2020 00:36:01 -0300 Subject: [PATCH] added WIP 'custom_dimension' to the API --- docs/scarpet/api/BlocksAndWorldAccess.md | 22 +++++ .../fakes/MinecraftServerInterface.java | 5 ++ .../mixins/MinecraftServer_scarpetMixin.java | 12 +++ .../java/carpet/script/CarpetExpression.java | 28 ++++++ .../java/carpet/script/utils/WorldTools.java | 85 +++++++++++++++++++ 5 files changed, 152 insertions(+) diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md index 7b30fc0b..6675bf01 100644 --- a/docs/scarpet/api/BlocksAndWorldAccess.md +++ b/docs/scarpet/api/BlocksAndWorldAccess.md @@ -730,3 +730,25 @@ All generated structures will retain their properties, like mob spawning, howeve itself has certain rules to spawn mobs, like plopping a nether fortress in the overworld will not spawn nether mobs, because nether mobs can spawn only in the nether, but plopped in the nether - will behave like a valid nether fortress. +### `custom_dimension(name, seed?)` + +Ensures the dimension with the given `'name'` is available and configured with the given seed. It merely sets the world +generator settings to the overworld, and the optional custom seed (or using current world seed, if not provided). + +If the dimension with this name already exists, returns `false` and does nothing. + +Created dimension with `custom_dimension` only exist till the game restart (same with the datapacks, if removed), but +all the world data should be saved. If custom dimension is re-created next time the app is loaded it will be using +the existing world content. This means that it is up to the programmer to make sure the custom dimensions settings +are stored in app data and restored when app reloads and wants to use previous worlds. Since vanilla game only keeps +track of world data, not the world settings, if the dimension hasn't been yet configured via `custom_dimension` and +the app hasn't yet initalized their dimension, the players will be positioned in the overworld at the same coordinates. + +List of custom dimensions (to be used in the likes of `/execute in `) is only send to the clients when joining the +game, meaning custom worlds created after a player has joined will not be suggested in vanilla commands, but running +vanilla commands on them will be successful. Its due to the fact that datapacks with dimensions are always loaded +with the game and assumed not changing. + +`custom_dimension` is experimental and considered a WIP. More customization options besides the seed will be added in +the future. + diff --git a/src/main/java/carpet/fakes/MinecraftServerInterface.java b/src/main/java/carpet/fakes/MinecraftServerInterface.java index 14348b88..08fa24ae 100644 --- a/src/main/java/carpet/fakes/MinecraftServerInterface.java +++ b/src/main/java/carpet/fakes/MinecraftServerInterface.java @@ -1,11 +1,16 @@ package carpet.fakes; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.registry.RegistryKey; +import net.minecraft.world.World; import net.minecraft.world.level.storage.LevelStorage; +import java.util.Map; import java.util.function.BooleanSupplier; public interface MinecraftServerInterface { void forceTick(BooleanSupplier sup); LevelStorage.Session getCMSession(); + Map, ServerWorld> getCMWorlds(); } diff --git a/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java b/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java index 59b55c40..3dfcdafb 100644 --- a/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java +++ b/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java @@ -4,8 +4,11 @@ import carpet.CarpetServer; import carpet.fakes.MinecraftServerInterface; import net.minecraft.server.MinecraftServer; import net.minecraft.server.ServerTask; +import net.minecraft.server.world.ServerWorld; import net.minecraft.util.Util; +import net.minecraft.util.registry.RegistryKey; import net.minecraft.util.thread.ReentrantThreadExecutor; +import net.minecraft.world.World; import net.minecraft.world.level.storage.LevelStorage; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -14,6 +17,7 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import java.util.Map; import java.util.function.BooleanSupplier; import static carpet.script.CarpetEventServer.Event.ENDER_TICK; @@ -38,6 +42,8 @@ public abstract class MinecraftServer_scarpetMixin extends ReentrantThreadExecut @Shadow @Final protected LevelStorage.Session session; + @Shadow @Final private Map, ServerWorld> worlds; + @Override public void forceTick(BooleanSupplier isAhead) { @@ -52,6 +58,12 @@ public abstract class MinecraftServer_scarpetMixin extends ReentrantThreadExecut return session; } + @Override + public Map, ServerWorld> getCMWorlds() + { + return worlds; + } + @Inject(method = "tick", at = @At( value = "CONSTANT", args = "stringValue=tallying" diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index c4346ff1..6f8760dd 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -26,6 +26,7 @@ import carpet.script.exception.ExitStatement; import carpet.script.exception.ExpressionException; import carpet.script.exception.InternalExpressionException; import carpet.script.utils.ShapeDispatcher; +import carpet.script.utils.WorldTools; import carpet.script.value.BlockValue; import carpet.script.value.EntityValue; import carpet.script.value.FormattedTextValue; @@ -1292,6 +1293,33 @@ public class CarpetExpression return (_c, _t) -> ret; }); + this.expr.addLazyFunction("custom_dimension", -1, (c, t, lv) -> + { + if (lv.size() == 0) throw new InternalExpressionException("'custom_dimension' requires at least one argument"); + CarpetContext cc = (CarpetContext)c; + String worldKey = lv.get(0).evalValue(c).getString(); + + Long seed = null; + if (lv.size() > 1) + { + String seedKey = lv.get(1).evalValue(c).getString(); + try + { + seed = Long.parseLong(seedKey); + } + catch (NumberFormatException ignored) + { + throw new InternalExpressionException("Incorrect number format for seed: " + seedKey); + } + } + + boolean success = WorldTools.createWorld(cc.s.getMinecraftServer(), worldKey, seed); + if (!success) return LazyValue.FALSE; + CarpetServer.settingsManager.notifyPlayersCommandsChanged(); + return LazyValue.TRUE; + }); + + this.expr.addLazyFunction("reset_chunk", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; diff --git a/src/main/java/carpet/script/utils/WorldTools.java b/src/main/java/carpet/script/utils/WorldTools.java index bec0c120..e03a805c 100644 --- a/src/main/java/carpet/script/utils/WorldTools.java +++ b/src/main/java/carpet/script/utils/WorldTools.java @@ -1,15 +1,36 @@ package carpet.script.utils; import carpet.fakes.MinecraftServerInterface; +import com.google.common.collect.ImmutableList; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.WorldGenerationProgressListener; import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; +import net.minecraft.util.Util; import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.registry.Registry; +import net.minecraft.util.registry.RegistryKey; +import net.minecraft.util.registry.SimpleRegistry; +import net.minecraft.world.World; +import net.minecraft.world.biome.source.BiomeAccess; +import net.minecraft.world.border.WorldBorderListener; import net.minecraft.world.chunk.ChunkStatus; +import net.minecraft.world.dimension.DimensionOptions; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.gen.GeneratorOptions; +import net.minecraft.world.gen.Spawner; +import net.minecraft.world.gen.chunk.ChunkGenerator; +import net.minecraft.world.level.ServerWorldProperties; +import net.minecraft.world.level.UnmodifiableLevelProperties; import net.minecraft.world.storage.RegionFile; import java.io.File; import java.io.IOException; import java.nio.file.Path; +import java.util.List; import java.util.Map; +import java.util.Random; +import java.util.Set; public class WorldTools { @@ -43,4 +64,68 @@ public class WorldTools catch (IOException ignored) { } return true; } + + public static boolean createWorld(MinecraftServer server, String worldKey, Long seed) + { + Identifier worldId = new Identifier(worldKey); + ServerWorld overWorld = server.getOverworld(); + + Set> worldKeys = server.getWorldRegistryKeys(); + for (RegistryKey worldRegistryKey : worldKeys) + { + if (worldRegistryKey.getValue().equals(worldId)) + { + // world with this id already exists + return false; + } + } + ServerWorldProperties serverWorldProperties = server.getSaveProperties().getMainWorldProperties(); + GeneratorOptions generatorOptions = server.getSaveProperties().getGeneratorOptions(); + boolean bl = generatorOptions.isDebugWorld(); + long l = generatorOptions.getSeed(); + long m = BiomeAccess.hashSeed(l); + List list = ImmutableList.of(); + SimpleRegistry simpleRegistry = generatorOptions.getDimensions(); + DimensionOptions dimensionOptions = simpleRegistry.get(DimensionOptions.OVERWORLD); + ChunkGenerator chunkGenerator2; + DimensionType dimensionType2; + if (dimensionOptions == null) { + dimensionType2 = (DimensionType)server.getRegistryManager().getDimensionTypes().getOrThrow(DimensionType.OVERWORLD_REGISTRY_KEY); + chunkGenerator2 = GeneratorOptions.createOverworldGenerator(server.getRegistryManager().get(Registry.BIOME_KEY), server.getRegistryManager().get(Registry.NOISE_SETTINGS_WORLDGEN), (new Random()).nextLong()); + } else { + dimensionType2 = dimensionOptions.getDimensionType(); + chunkGenerator2 = dimensionOptions.getChunkGenerator(); + } + + RegistryKey customWorld = RegistryKey.of(Registry.DIMENSION, worldId); + + chunkGenerator2 = GeneratorOptions.createOverworldGenerator(server.getRegistryManager().get(Registry.BIOME_KEY), server.getRegistryManager().get(Registry.NOISE_SETTINGS_WORLDGEN), (seed==null)?l:seed); + + ServerWorld serverWorld = new ServerWorld( + server, + Util.getMainWorkerExecutor(), + ((MinecraftServerInterface) server).getCMSession(), + new UnmodifiableLevelProperties(server.getSaveProperties(), serverWorldProperties), + customWorld, + dimensionType2, + NOOP_LISTENER, + chunkGenerator2, + bl, + (seed==null)?l:seed, + list, + false); + overWorld.getWorldBorder().addListener(new WorldBorderListener.WorldBorderSyncer(serverWorld.getWorldBorder())); + ((MinecraftServerInterface) server).getCMWorlds().put(customWorld, serverWorld); + return true; + } + + + private static class NoopWorldGenerationProgressListener implements WorldGenerationProgressListener + { + @Override public void start(ChunkPos spawnPos) { } + @Override public void setChunkStatus(ChunkPos pos, ChunkStatus status) { } + @Override public void stop() { } + } + + private static final WorldGenerationProgressListener NOOP_LISTENER = new NoopWorldGenerationProgressListener(); }