Skip to content

Commit 22f9929

Browse files
committed
feat: move dynamic dimension registry out of the MinecraftServer mixin
1 parent e2e2c0e commit 22f9929

File tree

11 files changed

+405
-281
lines changed

11 files changed

+405
-281
lines changed

common/src/main/java/dev/galacticraft/dynamicdimensions/api/DynamicDimensionRegistry.java

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@
2222

2323
package dev.galacticraft.dynamicdimensions.api;
2424

25+
import dev.galacticraft.dynamicdimensions.impl.accessor.DynamicDimensionProvider;
26+
import net.minecraft.core.registries.Registries;
27+
import net.minecraft.resources.ResourceKey;
2528
import net.minecraft.resources.ResourceLocation;
2629
import net.minecraft.server.MinecraftServer;
2730
import net.minecraft.server.level.ServerLevel;
31+
import net.minecraft.world.level.Level;
2832
import net.minecraft.world.level.chunk.ChunkGenerator;
2933
import net.minecraft.world.level.dimension.DimensionType;
3034
import org.jetbrains.annotations.Contract;
@@ -40,15 +44,15 @@
4044
*/
4145
public interface DynamicDimensionRegistry {
4246
/**
43-
* Converts a Minecraft server instance into a dynamic dimension registry.
47+
* Obtains a dynamic dimension registry from a Minecraft server instance.
4448
*
4549
* @param server the current Minecraft server instance.
4650
* @return the server's dynamic dimension registry.
4751
* @since 0.5.0
4852
*/
4953
@Contract(value = "_ -> param1", pure = true)
5054
static @NotNull DynamicDimensionRegistry from(@NotNull MinecraftServer server) {
51-
return ((DynamicDimensionRegistry) server);
55+
return ((DynamicDimensionProvider) server).dynamicdimensions$registry();
5256
}
5357

5458
/**
@@ -57,7 +61,19 @@ public interface DynamicDimensionRegistry {
5761
* @param id the id of the dynamic dimension
5862
* @return whether a dynamic dimension exists with the given id
5963
*/
60-
boolean dynamicDimensionExists(@NotNull ResourceLocation id);
64+
@Deprecated(since = "0.9.0", forRemoval = true)
65+
default boolean dynamicDimensionExists(@NotNull ResourceLocation id) {
66+
return dynamicDimensionExists(ResourceKey.create(Registries.DIMENSION, id));
67+
}
68+
69+
/**
70+
* Returns whether a dynamic dimension exists with the given id
71+
*
72+
* @param key the id of the dynamic dimension
73+
* @return whether a dynamic dimension exists with the given id
74+
* @since 0.9.0
75+
*/
76+
boolean dynamicDimensionExists(@NotNull ResourceKey<Level> key);
6177

6278
/**
6379
* Returns whether any dimension, dimension type, or level stem is registered with the given id
@@ -74,7 +90,19 @@ public interface DynamicDimensionRegistry {
7490
* @return {@code true} if the dimension is dynamic and can be deleted, {@code false} otherwise.
7591
* @since 0.1.0
7692
*/
77-
boolean canDeleteDimension(@NotNull ResourceLocation id);
93+
@Deprecated(since = "0.9.0", forRemoval = true)
94+
default boolean canDeleteDimension(@NotNull ResourceLocation id) {
95+
return this.canDeleteDimension(ResourceKey.create(Registries.DIMENSION, id));
96+
}
97+
98+
/**
99+
* Returns whether a level and dimension with the given ID can be deleted.
100+
*
101+
* @param key The ID of the dimension.
102+
* @return {@code true} if the dimension is dynamic and can be deleted, {@code false} otherwise.
103+
* @since 0.9.0
104+
*/
105+
boolean canDeleteDimension(@NotNull ResourceKey<Level> key);
78106

79107
/**
80108
* Returns whether a level and dimension with the given ID can be created.
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
/*
2+
* Copyright (c) 2021-2025 Team Galacticraft
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*/
22+
23+
package dev.galacticraft.dynamicdimensions.impl;
24+
25+
import com.google.common.collect.ImmutableList;
26+
import com.mojang.serialization.DataResult;
27+
import dev.galacticraft.dynamicdimensions.api.DynamicDimensionRegistry;
28+
import dev.galacticraft.dynamicdimensions.api.PlayerRemover;
29+
import dev.galacticraft.dynamicdimensions.api.event.DynamicDimensionLoadCallback;
30+
import dev.galacticraft.dynamicdimensions.impl.accessor.DynamicDimensionProvider;
31+
import dev.galacticraft.dynamicdimensions.impl.accessor.PrimaryLevelDataAccessor;
32+
import dev.galacticraft.dynamicdimensions.impl.mixin.*;
33+
import dev.galacticraft.dynamicdimensions.impl.network.S2CPackets;
34+
import dev.galacticraft.dynamicdimensions.impl.registry.RegistryUtil;
35+
import net.minecraft.core.Holder;
36+
import net.minecraft.core.Registry;
37+
import net.minecraft.core.registries.Registries;
38+
import net.minecraft.nbt.CompoundTag;
39+
import net.minecraft.nbt.NbtOps;
40+
import net.minecraft.nbt.Tag;
41+
import net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket;
42+
import net.minecraft.resources.ResourceKey;
43+
import net.minecraft.resources.ResourceLocation;
44+
import net.minecraft.server.MinecraftServer;
45+
import net.minecraft.server.level.ServerLevel;
46+
import net.minecraft.server.level.ServerPlayer;
47+
import net.minecraft.tags.TagKey;
48+
import net.minecraft.tags.TagManager;
49+
import net.minecraft.tags.TagNetworkSerialization;
50+
import net.minecraft.world.level.Level;
51+
import net.minecraft.world.level.biome.BiomeManager;
52+
import net.minecraft.world.level.border.BorderChangeListener;
53+
import net.minecraft.world.level.chunk.ChunkGenerator;
54+
import net.minecraft.world.level.dimension.DimensionType;
55+
import net.minecraft.world.level.dimension.LevelStem;
56+
import net.minecraft.world.level.storage.DerivedLevelData;
57+
import net.minecraft.world.level.storage.WorldData;
58+
import org.jetbrains.annotations.NotNull;
59+
import org.jetbrains.annotations.Nullable;
60+
61+
import java.util.ArrayList;
62+
import java.util.List;
63+
import java.util.stream.Collectors;
64+
65+
public class DynamicDimensionRegistryImpl implements DynamicDimensionRegistry {
66+
private final @NotNull List<ResourceKey<Level>> dynamicDimensions = new ArrayList<>();
67+
private final MinecraftServer server;
68+
private final Registry<DimensionType> dimTypes;
69+
private final Registry<LevelStem> stems;
70+
71+
public DynamicDimensionRegistryImpl(MinecraftServer server) {
72+
this.server = server;
73+
this.dimTypes = server.registryAccess().registryOrThrow(Registries.DIMENSION_TYPE);
74+
this.stems = server.registries().compositeAccess().registryOrThrow(Registries.LEVEL_STEM);
75+
76+
DynamicDimensionLoadCallback.invoke(server, (id, chunkGenerator, type) -> {
77+
Constants.LOGGER.debug("Loading dynamic dimension '{}'", id);
78+
Holder.Reference<DimensionType> ref = RegistryUtil.registerUnfreeze(this.dimTypes, id, type);
79+
RegistryUtil.registerUnfreeze(this.stems, id, new LevelStem(ref, chunkGenerator));
80+
this.dynamicDimensions.add(ResourceKey.create(Registries.DIMENSION, id));
81+
});
82+
83+
Constants.LOGGER.info("Loaded {} dynamic dimensions", this.dynamicDimensions.size());
84+
85+
((PrimaryLevelDataAccessor) server.getWorldData()).dynamicDimensions$setDynamicList(this.dynamicDimensions);
86+
}
87+
88+
@Override
89+
public @Nullable ServerLevel createDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator generator, @NotNull DimensionType type) {
90+
return this.createDynamicLevel(id, generator, type, true);
91+
}
92+
93+
@Override
94+
public @Nullable ServerLevel loadDynamicDimension(@NotNull ResourceLocation id, @NotNull ChunkGenerator generator, @NotNull DimensionType type) {
95+
return this.createDynamicLevel(id, generator, type, false);
96+
}
97+
98+
@Override
99+
public boolean dynamicDimensionExists(@NotNull ResourceKey<Level> key) {
100+
return this.dynamicDimensions.contains(key) || ((DynamicDimensionProvider) this.server).dynamicdimensions$isIdPendingCreation(key);
101+
}
102+
103+
@Override
104+
public boolean anyDimensionExists(@NotNull ResourceLocation id) {
105+
return this.server.levelKeys().contains(ResourceKey.create(Registries.DIMENSION, id))
106+
|| this.dimTypes.containsKey(id)
107+
|| this.stems.containsKey(id);
108+
}
109+
110+
@Override
111+
public boolean canDeleteDimension(@NotNull ResourceKey<Level> key) {
112+
return this.dynamicDimensionExists(key);
113+
}
114+
115+
@Override
116+
public boolean canCreateDimension(@NotNull ResourceLocation id) {
117+
return !this.anyDimensionExists(id) && !this.dynamicDimensionExists(ResourceKey.create(Registries.DIMENSION, id));
118+
}
119+
120+
@Override
121+
public boolean deleteDynamicDimension(@NotNull ResourceLocation id, @Nullable PlayerRemover remover) {
122+
if (remover == null) remover = PlayerRemover.DEFAULT;
123+
ResourceKey<Level> key = ResourceKey.create(Registries.DIMENSION, id);
124+
if (!this.canDeleteDimension(key)) return false;
125+
126+
((DynamicDimensionProvider) this.server).dynamicdimensions$removeLevel(key, remover, true);
127+
128+
return true;
129+
}
130+
131+
@Override
132+
public boolean unloadDynamicDimension(@NotNull ResourceLocation id, @Nullable PlayerRemover remover) {
133+
if (remover == null) remover = PlayerRemover.DEFAULT;
134+
ResourceKey<Level> key = ResourceKey.create(Registries.DIMENSION, id);
135+
if (!this.canDeleteDimension(key)) return false;
136+
137+
((DynamicDimensionProvider) this.server).dynamicdimensions$removeLevel(key, remover, false);
138+
return true;
139+
}
140+
141+
private @Nullable ServerLevel createDynamicLevel(@NotNull ResourceLocation id, @NotNull ChunkGenerator generator, @NotNull DimensionType type, boolean deleteData) {
142+
ResourceKey<Level> key = ResourceKey.create(Registries.DIMENSION, id);
143+
if (!this.canCreateDimension(id)) return null;
144+
Constants.LOGGER.debug("Attempting to create dynamic dimension '{}'", id);
145+
146+
if (this.dimTypes.stream().anyMatch(t -> t == type)) {
147+
return null;
148+
}
149+
150+
final DataResult<Tag> encodedType = DimensionType.DIRECT_CODEC.encode(type, NbtOps.INSTANCE, new CompoundTag());
151+
if (encodedType.error().isPresent()) {
152+
Constants.LOGGER.error("Failed to encode dimension type! {}", encodedType.error().get().message());
153+
return null;
154+
}
155+
156+
final CompoundTag serializedType = (CompoundTag) encodedType.result().orElseThrow();
157+
158+
if (deleteData) ((DynamicDimensionProvider) this.server).dynamicdimensions$deleteLevelData(key);
159+
return this.createDynamicLevel(id, generator, type, serializedType, key);
160+
}
161+
162+
private @NotNull ServerLevel createDynamicLevel(@NotNull ResourceLocation id, @NotNull ChunkGenerator generator, @NotNull DimensionType type, CompoundTag serializedType, ResourceKey<Level> key) {
163+
final WorldData worldData = this.server.getWorldData();
164+
final ServerLevel overworld = this.server.overworld();
165+
166+
final Holder.Reference<DimensionType> typeHolder = RegistryUtil.registerUnfreeze(this.dimTypes, id, type);
167+
assert typeHolder.isBound() : "Registered dimension type not bound?!";
168+
169+
final LevelStem stem = new LevelStem(typeHolder, generator);
170+
RegistryUtil.registerUnfreeze(this.stems, id, stem); // todo: look into whether stem registration is necessary
171+
172+
final DerivedLevelData data = new DerivedLevelData(worldData, worldData.overworldData()); //todo: do we want separate data?
173+
final ServerLevel level = new ServerLevel(
174+
this.server,
175+
((MinecraftServerAccessor) this.server).getExecutor(),
176+
((MinecraftServerAccessor) this.server).getStorageSource(),
177+
data,
178+
key,
179+
stem,
180+
((MinecraftServerAccessor) this.server).getProgressListenerFactory().create(10),
181+
worldData.isDebugWorld(),
182+
BiomeManager.obfuscateSeed(worldData.worldGenOptions().seed()),
183+
ImmutableList.of(),
184+
false,
185+
null
186+
);
187+
overworld.getWorldBorder().addListener(new BorderChangeListener.DelegateBorderChangeListener(level.getWorldBorder()));
188+
level.getChunkSource().setSimulationDistance(((DistanceManagerAccessor) ((ServerChunkCacheAccessor) overworld.getChunkSource()).getDistanceManager()).getSimulationDistance());
189+
level.getChunkSource().setViewDistance(((ChunkMapAccessor) overworld.getChunkSource().chunkMap).getViewDistance());
190+
191+
((DynamicDimensionProvider) this.server).dynamicdimensions$registerLevel(level);
192+
193+
194+
for (ServerPlayer player : this.server.getPlayerList().getPlayers()) {
195+
S2CPackets.sendCreateDimension(player, key.location(), serializedType);
196+
}
197+
this.reloadDimensionTags();
198+
return level;
199+
}
200+
201+
private void reloadDimensionTags() {
202+
for (TagManager.LoadResult<?> result : ((ReloadableServerResourcesAccessor) ((MinecraftServerAccessor)this.server).getResources().managers()).getTagManager().getResult()) {
203+
if (result.key() == Registries.DIMENSION_TYPE) {
204+
this.dimTypes.resetTags();
205+
//noinspection unchecked - we know that the registry is a registry of dimension types as the key is correct
206+
this.dimTypes.bindTags(((TagManager.LoadResult<DimensionType>) result).tags().entrySet()
207+
.stream()
208+
.collect(Collectors.toUnmodifiableMap(entry -> TagKey.create(Registries.DIMENSION_TYPE, entry.getKey()), entry -> entry.getValue().stream().toList())));
209+
break;
210+
}
211+
}
212+
this.server.getPlayerList().broadcastAll(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(this.server.registries())));
213+
}
214+
215+
public void remove(ResourceKey<Level> key) {
216+
this.dynamicDimensions.remove(key);
217+
}
218+
219+
public void add(ResourceKey<Level> key) {
220+
this.dynamicDimensions.add(key);
221+
}
222+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) 2021-2025 Team Galacticraft
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*/
22+
23+
package dev.galacticraft.dynamicdimensions.impl.accessor;
24+
25+
import dev.galacticraft.dynamicdimensions.api.DynamicDimensionRegistry;
26+
import dev.galacticraft.dynamicdimensions.api.PlayerRemover;
27+
import net.minecraft.resources.ResourceKey;
28+
import net.minecraft.server.level.ServerLevel;
29+
import net.minecraft.world.level.Level;
30+
import org.jetbrains.annotations.NotNull;
31+
import org.jetbrains.annotations.Nullable;
32+
33+
public interface DynamicDimensionProvider {
34+
void dynamicdimensions$removeLevel(ResourceKey<Level> key, @Nullable PlayerRemover removalMode, boolean removeFiles);
35+
36+
void dynamicdimensions$deleteLevelData(ResourceKey<Level> key);
37+
38+
boolean dynamicdimensions$isIdPendingCreation(@NotNull ResourceKey<Level> key);
39+
40+
void dynamicdimensions$registerLevel(ServerLevel level);
41+
42+
@NotNull DynamicDimensionRegistry dynamicdimensions$registry();
43+
}

common/src/main/java/dev/galacticraft/dynamicdimensions/impl/command/DynamicDimensionsCommands.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ public static void register(@NotNull CommandDispatcher<CommandSourceStack> dispa
141141
ServerLevel levelToDelete = DimensionArgument.getDimension(ctx, "id");
142142
ResourceKey<Level> key = levelToDelete.dimension();
143143
ResourceLocation id = key.location();
144-
if (!((DynamicDimensionRegistry) ctx.getSource().getServer()).canDeleteDimension(id)) {
144+
if (!((DynamicDimensionRegistry) ctx.getSource().getServer()).canDeleteDimension(key)) {
145145
throw CANNOT_DELETE.create();
146146
}
147147
((DynamicDimensionRegistry) ctx.getSource().getServer()).unloadDynamicDimension(id, null);
@@ -153,7 +153,7 @@ public static void register(@NotNull CommandDispatcher<CommandSourceStack> dispa
153153
ServerLevel levelToDelete = DimensionArgument.getDimension(ctx, "id");
154154
ResourceKey<Level> key = levelToDelete.dimension();
155155
ResourceLocation id = key.location();
156-
if (!((DynamicDimensionRegistry) ctx.getSource().getServer()).canDeleteDimension(id)) {
156+
if (!((DynamicDimensionRegistry) ctx.getSource().getServer()).canDeleteDimension(key)) {
157157
throw CANNOT_DELETE.create();
158158
}
159159
((DynamicDimensionRegistry) ctx.getSource().getServer()).deleteDynamicDimension(id, null);

0 commit comments

Comments
 (0)