added WIP 'custom_dimension' to the API

This commit is contained in:
gnembon committed 2020-08-17 00:36:01 -03:00
1 parent e2c8b9cc41
commit 598219608b
5 files changed
+152

No files matched your search

+22
View File
@@ -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 <dim>`) 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.
@@ -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<RegistryKey<World>, ServerWorld> getCMWorlds();
}
@@ -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<RegistryKey<World>, ServerWorld> worlds;
@Override
public void forceTick(BooleanSupplier isAhead)
{
@@ -52,6 +58,12 @@ public abstract class MinecraftServer_scarpetMixin extends ReentrantThreadExecut
return session;
}
@Override
public Map<RegistryKey<World>, ServerWorld> getCMWorlds()
{
return worlds;
}
@Inject(method = "tick", at = @At(
value = "CONSTANT",
args = "stringValue=tallying"
@@ -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;
@@ -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<RegistryKey<World>> worldKeys = server.getWorldRegistryKeys();
for (RegistryKey<World> 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<Spawner> list = ImmutableList.of();
SimpleRegistry<DimensionOptions> 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<World> 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();
}