Skip to content

Commit e2c3606

Browse files
committed
Move dimension forge-registration code into util class
1 parent fd4d693 commit e2c3606

File tree

2 files changed

+138
-128
lines changed

2 files changed

+138
-128
lines changed
Lines changed: 2 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,14 @@
11
package dev.compactmods.machines.command;
22

3-
import java.io.IOException;
4-
import java.nio.file.Files;
5-
import java.time.ZonedDateTime;
6-
import java.time.format.DateTimeFormatter;
7-
import java.util.concurrent.Executor;
8-
import com.google.common.collect.ImmutableList;
93
import com.mojang.brigadier.builder.ArgumentBuilder;
104
import com.mojang.brigadier.context.CommandContext;
115
import com.mojang.brigadier.exceptions.CommandSyntaxException;
12-
import com.mojang.serialization.JsonOps;
13-
import com.mojang.serialization.Lifecycle;
14-
import dev.compactmods.machines.CompactMachines;
156
import dev.compactmods.machines.core.Registration;
7+
import dev.compactmods.machines.util.DimensionUtil;
168
import dev.compactmods.machines.util.TranslationUtil;
179
import net.minecraft.ChatFormatting;
1810
import net.minecraft.commands.CommandSourceStack;
1911
import net.minecraft.commands.Commands;
20-
import net.minecraft.core.Registry;
21-
import net.minecraft.resources.RegistryReadOps;
22-
import net.minecraft.resources.RegistryResourceAccess;
23-
import net.minecraft.resources.ResourceKey;
24-
import net.minecraft.server.MinecraftServer;
25-
import net.minecraft.server.level.ServerLevel;
26-
import net.minecraft.server.level.progress.ChunkProgressListener;
27-
import net.minecraft.world.level.Level;
28-
import net.minecraft.world.level.border.BorderChangeListener;
29-
import net.minecraft.world.level.dimension.LevelStem;
30-
import net.minecraft.world.level.levelgen.WorldGenSettings;
31-
import net.minecraft.world.level.storage.DerivedLevelData;
32-
import net.minecraft.world.level.storage.LevelResource;
33-
import net.minecraft.world.level.storage.LevelStorageSource;
34-
import net.minecraft.world.level.storage.WorldData;
35-
import net.minecraftforge.common.MinecraftForge;
36-
import net.minecraftforge.event.world.WorldEvent;
37-
import net.minecraftforge.fml.loading.FMLEnvironment;
3812

3913
public class ReaddDimensionCommand {
4014
public static ArgumentBuilder<CommandSourceStack, ?> register() {
@@ -51,113 +25,13 @@ private static int exec(CommandContext<CommandSourceStack> ctx) throws CommandSy
5125
if (compactLevel == null) {
5226
src.sendSuccess(TranslationUtil.command("level_not_found").withStyle(ChatFormatting.RED), false);
5327

54-
createAndRegisterWorldAndDimension(serv);
28+
DimensionUtil.createAndRegisterWorldAndDimension(serv);
5529
} else {
5630
src.sendSuccess(TranslationUtil.command("level_registered").withStyle(ChatFormatting.DARK_GREEN), false);
5731
}
5832

5933
return 0;
6034
}
6135

62-
@SuppressWarnings("deprecation") // because we call the forge internal method server#markWorldsDirty
63-
private static void createAndRegisterWorldAndDimension(final MinecraftServer server) {
64-
final var map = server.forgeGetWorldMap();
6536

66-
// get everything we need to create the dimension and the level
67-
final ServerLevel overworld = server.getLevel(Level.OVERWORLD);
68-
69-
// dimension keys have a 1:1 relationship with level keys, they have the same IDs as well
70-
final ResourceKey<LevelStem> dimensionKey = ResourceKey.create(Registry.LEVEL_STEM_REGISTRY, Registration.COMPACT_DIMENSION.location());
71-
72-
final var serverResources = server.getResourceManager();
73-
74-
// only back up level.dat in production
75-
if (FMLEnvironment.production && !doLevelFileBackup(server)) return;
76-
77-
var reg = server.registryAccess();
78-
var cmDimType = reg.registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY)
79-
.get(Registration.COMPACT_DIMENSION_DIM_TYPE);
80-
81-
var ops = RegistryReadOps.create(JsonOps.INSTANCE, serverResources, reg);
82-
83-
var resourceAccess = RegistryResourceAccess.forResourceManager(serverResources);
84-
var dims = resourceAccess.listResources(Registry.DIMENSION_REGISTRY);
85-
86-
var cmDim = dims.stream()
87-
.filter(d -> d.location().equals(Registration.COMPACT_DIMENSION.location()))
88-
.findFirst();
89-
90-
cmDim.ifPresent(lev -> {
91-
var parsed = resourceAccess.parseElement(ops, Registry.LEVEL_STEM_REGISTRY, dimensionKey, LevelStem.CODEC);
92-
93-
var stem = parsed.orElseThrow()
94-
.result().orElseThrow()
95-
.value();
96-
97-
// the int in create() here is radius of chunks to watch, 11 is what the server uses when it initializes worlds
98-
final ChunkProgressListener chunkProgressListener = server.progressListenerFactory.create(11);
99-
final Executor executor = server.executor;
100-
final LevelStorageSource.LevelStorageAccess anvilConverter = server.storageSource;
101-
final WorldData worldData = server.getWorldData();
102-
final WorldGenSettings worldGenSettings = worldData.worldGenSettings();
103-
final DerivedLevelData derivedLevelData = new DerivedLevelData(worldData, worldData.overworldData());
104-
105-
// now we have everything we need to create the dimension and the level
106-
// this is the same order server init creates levels:
107-
// the dimensions are already registered when levels are created, we'll do that first
108-
// then instantiate level, add border listener, add to map, fire world load event
109-
110-
// register the actual dimension
111-
worldGenSettings.dimensions().register(dimensionKey, stem, Lifecycle.experimental());
112-
113-
// create the world instance
114-
final ServerLevel newWorld = new ServerLevel(
115-
server,
116-
executor,
117-
anvilConverter,
118-
derivedLevelData,
119-
Registration.COMPACT_DIMENSION,
120-
cmDimType,
121-
chunkProgressListener,
122-
stem.generator(),
123-
worldGenSettings.isDebug(),
124-
net.minecraft.world.level.biome.BiomeManager.obfuscateSeed(worldGenSettings.seed()),
125-
ImmutableList.of(), // "special spawn list"
126-
false // "tick time", true for overworld, always false for nether, end, and json dimensions
127-
);
128-
129-
/*
130-
add world border listener, for parity with json dimensions
131-
the vanilla behaviour is that world borders exist in every dimension simultaneously with the same size and position
132-
these border listeners are automatically added to the overworld as worlds are loaded, so we should do that here too
133-
TODO if world-specific world borders are ever added, change it here too
134-
*/
135-
overworld.getWorldBorder().addListener(new BorderChangeListener.DelegateBorderChangeListener(newWorld.getWorldBorder()));
136-
137-
// register level
138-
map.put(Registration.COMPACT_DIMENSION, newWorld);
139-
140-
// update forge's world cache so the new level can be ticked
141-
server.markWorldsDirty();
142-
143-
// fire world load event
144-
MinecraftForge.EVENT_BUS.post(new WorldEvent.Load(newWorld));
145-
});
146-
}
147-
148-
private static boolean doLevelFileBackup(MinecraftServer server) {
149-
var levelRoot = server.getWorldPath(LevelResource.ROOT);
150-
var levelFile = server.getWorldPath(LevelResource.LEVEL_DATA_FILE);
151-
152-
var formatter = DateTimeFormatter.ofPattern("'cm4-level-'yyyyMMdd-HHmmss'.dat'");
153-
var timestamp = formatter.format(ZonedDateTime.now());
154-
try {
155-
Files.copy(levelFile, levelRoot.resolve(timestamp));
156-
} catch (IOException e) {
157-
CompactMachines.LOGGER.error("Failed to backup level.dat file before modification; canceling register dim attempt.");
158-
return false;
159-
}
160-
161-
return true;
162-
}
16337
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package dev.compactmods.machines.util;
2+
3+
import java.io.IOException;
4+
import java.nio.file.Files;
5+
import java.time.ZonedDateTime;
6+
import java.time.format.DateTimeFormatter;
7+
import java.util.concurrent.Executor;
8+
import com.google.common.collect.ImmutableList;
9+
import com.mojang.serialization.JsonOps;
10+
import com.mojang.serialization.Lifecycle;
11+
import dev.compactmods.machines.CompactMachines;
12+
import dev.compactmods.machines.core.Registration;
13+
import net.minecraft.core.Registry;
14+
import net.minecraft.resources.RegistryReadOps;
15+
import net.minecraft.resources.RegistryResourceAccess;
16+
import net.minecraft.resources.ResourceKey;
17+
import net.minecraft.server.MinecraftServer;
18+
import net.minecraft.server.level.ServerLevel;
19+
import net.minecraft.server.level.progress.ChunkProgressListener;
20+
import net.minecraft.world.level.Level;
21+
import net.minecraft.world.level.border.BorderChangeListener;
22+
import net.minecraft.world.level.dimension.LevelStem;
23+
import net.minecraft.world.level.levelgen.WorldGenSettings;
24+
import net.minecraft.world.level.storage.DerivedLevelData;
25+
import net.minecraft.world.level.storage.LevelResource;
26+
import net.minecraft.world.level.storage.LevelStorageSource;
27+
import net.minecraft.world.level.storage.WorldData;
28+
import net.minecraftforge.common.MinecraftForge;
29+
import net.minecraftforge.event.world.WorldEvent;
30+
import net.minecraftforge.fml.loading.FMLEnvironment;
31+
32+
public class DimensionUtil {
33+
34+
@SuppressWarnings("deprecation") // because we call the forge internal method server#markWorldsDirty
35+
public static void createAndRegisterWorldAndDimension(final MinecraftServer server) {
36+
final var map = server.forgeGetWorldMap();
37+
38+
// get everything we need to create the dimension and the level
39+
final ServerLevel overworld = server.getLevel(Level.OVERWORLD);
40+
41+
// dimension keys have a 1:1 relationship with level keys, they have the same IDs as well
42+
final ResourceKey<LevelStem> dimensionKey = ResourceKey.create(Registry.LEVEL_STEM_REGISTRY, Registration.COMPACT_DIMENSION.location());
43+
44+
final var serverResources = server.getResourceManager();
45+
46+
// only back up level.dat in production
47+
if (FMLEnvironment.production && !doLevelFileBackup(server)) return;
48+
49+
var reg = server.registryAccess();
50+
var cmDimType = reg.registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY)
51+
.get(Registration.COMPACT_DIMENSION_DIM_TYPE);
52+
53+
var ops = RegistryReadOps.create(JsonOps.INSTANCE, serverResources, reg);
54+
55+
var resourceAccess = RegistryResourceAccess.forResourceManager(serverResources);
56+
var dims = resourceAccess.listResources(Registry.DIMENSION_REGISTRY);
57+
58+
var cmDim = dims.stream()
59+
.filter(d -> d.location().equals(Registration.COMPACT_DIMENSION.location()))
60+
.findFirst();
61+
62+
cmDim.ifPresent(lev -> {
63+
var parsed = resourceAccess.parseElement(ops, Registry.LEVEL_STEM_REGISTRY, dimensionKey, LevelStem.CODEC);
64+
65+
var stem = parsed.orElseThrow()
66+
.result().orElseThrow()
67+
.value();
68+
69+
// the int in create() here is radius of chunks to watch, 11 is what the server uses when it initializes worlds
70+
final ChunkProgressListener chunkProgressListener = server.progressListenerFactory.create(11);
71+
final Executor executor = server.executor;
72+
final LevelStorageSource.LevelStorageAccess anvilConverter = server.storageSource;
73+
final WorldData worldData = server.getWorldData();
74+
final WorldGenSettings worldGenSettings = worldData.worldGenSettings();
75+
final DerivedLevelData derivedLevelData = new DerivedLevelData(worldData, worldData.overworldData());
76+
77+
// now we have everything we need to create the dimension and the level
78+
// this is the same order server init creates levels:
79+
// the dimensions are already registered when levels are created, we'll do that first
80+
// then instantiate level, add border listener, add to map, fire world load event
81+
82+
// register the actual dimension
83+
worldGenSettings.dimensions().register(dimensionKey, stem, Lifecycle.experimental());
84+
85+
// create the world instance
86+
final ServerLevel newWorld = new ServerLevel(
87+
server,
88+
executor,
89+
anvilConverter,
90+
derivedLevelData,
91+
Registration.COMPACT_DIMENSION,
92+
cmDimType,
93+
chunkProgressListener,
94+
stem.generator(),
95+
worldGenSettings.isDebug(),
96+
net.minecraft.world.level.biome.BiomeManager.obfuscateSeed(worldGenSettings.seed()),
97+
ImmutableList.of(), // "special spawn list"
98+
false // "tick time", true for overworld, always false for nether, end, and json dimensions
99+
);
100+
101+
/*
102+
add world border listener, for parity with json dimensions
103+
the vanilla behaviour is that world borders exist in every dimension simultaneously with the same size and position
104+
these border listeners are automatically added to the overworld as worlds are loaded, so we should do that here too
105+
TODO if world-specific world borders are ever added, change it here too
106+
*/
107+
overworld.getWorldBorder().addListener(new BorderChangeListener.DelegateBorderChangeListener(newWorld.getWorldBorder()));
108+
109+
// register level
110+
map.put(Registration.COMPACT_DIMENSION, newWorld);
111+
112+
// update forge's world cache so the new level can be ticked
113+
server.markWorldsDirty();
114+
115+
// fire world load event
116+
MinecraftForge.EVENT_BUS.post(new WorldEvent.Load(newWorld));
117+
});
118+
}
119+
120+
public static boolean doLevelFileBackup(MinecraftServer server) {
121+
var levelRoot = server.getWorldPath(LevelResource.ROOT);
122+
var levelFile = server.getWorldPath(LevelResource.LEVEL_DATA_FILE);
123+
124+
var formatter = DateTimeFormatter.ofPattern("'cm4-level-'yyyyMMdd-HHmmss'.dat'");
125+
var timestamp = formatter.format(ZonedDateTime.now());
126+
try {
127+
Files.copy(levelFile, levelRoot.resolve(timestamp));
128+
} catch (IOException e) {
129+
CompactMachines.LOGGER.error("Failed to backup level.dat file before modification; canceling register dim attempt.");
130+
return false;
131+
}
132+
133+
return true;
134+
}
135+
136+
}

0 commit comments

Comments
 (0)