added WIP 'custom_dimension' to the API
This commit is contained in:
1 parent
e2c8b9cc41
commit
598219608b
5 files changed
+152
No files matched your search
@@ -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,
|
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.
|
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;
|
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 net.minecraft.world.level.storage.LevelStorage;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
public interface MinecraftServerInterface
|
public interface MinecraftServerInterface
|
||||||
{
|
{
|
||||||
void forceTick(BooleanSupplier sup);
|
void forceTick(BooleanSupplier sup);
|
||||||
LevelStorage.Session getCMSession();
|
LevelStorage.Session getCMSession();
|
||||||
|
Map<RegistryKey<World>, ServerWorld> getCMWorlds();
|
||||||
}
|
}
|
||||||
@@ -4,8 +4,11 @@ import carpet.CarpetServer;
|
|||||||
import carpet.fakes.MinecraftServerInterface;
|
import carpet.fakes.MinecraftServerInterface;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.ServerTask;
|
import net.minecraft.server.ServerTask;
|
||||||
|
import net.minecraft.server.world.ServerWorld;
|
||||||
import net.minecraft.util.Util;
|
import net.minecraft.util.Util;
|
||||||
|
import net.minecraft.util.registry.RegistryKey;
|
||||||
import net.minecraft.util.thread.ReentrantThreadExecutor;
|
import net.minecraft.util.thread.ReentrantThreadExecutor;
|
||||||
|
import net.minecraft.world.World;
|
||||||
import net.minecraft.world.level.storage.LevelStorage;
|
import net.minecraft.world.level.storage.LevelStorage;
|
||||||
import org.spongepowered.asm.mixin.Final;
|
import org.spongepowered.asm.mixin.Final;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
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.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
import static carpet.script.CarpetEventServer.Event.ENDER_TICK;
|
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 protected LevelStorage.Session session;
|
||||||
|
|
||||||
|
@Shadow @Final private Map<RegistryKey<World>, ServerWorld> worlds;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void forceTick(BooleanSupplier isAhead)
|
public void forceTick(BooleanSupplier isAhead)
|
||||||
{
|
{
|
||||||
@@ -52,6 +58,12 @@ public abstract class MinecraftServer_scarpetMixin extends ReentrantThreadExecut
|
|||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<RegistryKey<World>, ServerWorld> getCMWorlds()
|
||||||
|
{
|
||||||
|
return worlds;
|
||||||
|
}
|
||||||
|
|
||||||
@Inject(method = "tick", at = @At(
|
@Inject(method = "tick", at = @At(
|
||||||
value = "CONSTANT",
|
value = "CONSTANT",
|
||||||
args = "stringValue=tallying"
|
args = "stringValue=tallying"
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import carpet.script.exception.ExitStatement;
|
|||||||
import carpet.script.exception.ExpressionException;
|
import carpet.script.exception.ExpressionException;
|
||||||
import carpet.script.exception.InternalExpressionException;
|
import carpet.script.exception.InternalExpressionException;
|
||||||
import carpet.script.utils.ShapeDispatcher;
|
import carpet.script.utils.ShapeDispatcher;
|
||||||
|
import carpet.script.utils.WorldTools;
|
||||||
import carpet.script.value.BlockValue;
|
import carpet.script.value.BlockValue;
|
||||||
import carpet.script.value.EntityValue;
|
import carpet.script.value.EntityValue;
|
||||||
import carpet.script.value.FormattedTextValue;
|
import carpet.script.value.FormattedTextValue;
|
||||||
@@ -1292,6 +1293,33 @@ public class CarpetExpression
|
|||||||
return (_c, _t) -> ret;
|
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) ->
|
this.expr.addLazyFunction("reset_chunk", -1, (c, t, lv) ->
|
||||||
{
|
{
|
||||||
CarpetContext cc = (CarpetContext)c;
|
CarpetContext cc = (CarpetContext)c;
|
||||||
|
|||||||
@@ -1,15 +1,36 @@
|
|||||||
package carpet.script.utils;
|
package carpet.script.utils;
|
||||||
|
|
||||||
import carpet.fakes.MinecraftServerInterface;
|
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.server.world.ServerWorld;
|
||||||
|
import net.minecraft.util.Identifier;
|
||||||
|
import net.minecraft.util.Util;
|
||||||
import net.minecraft.util.math.ChunkPos;
|
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.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 net.minecraft.world.storage.RegionFile;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class WorldTools
|
public class WorldTools
|
||||||
{
|
{
|
||||||
@@ -43,4 +64,68 @@ public class WorldTools
|
|||||||
catch (IOException ignored) { }
|
catch (IOException ignored) { }
|
||||||
return true;
|
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();
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user