Inspired by 2020's April Fools' 20w14infinite Snapshot, this mod brings endless randomly generated dimensions into Minecraft.
1package net.lerariemann.infinity.dimensions;
2
3import net.lerariemann.infinity.InfinityMod;
4import net.lerariemann.infinity.options.RandomInfinityOptions;
5import net.lerariemann.infinity.util.core.CommonIO;
6import net.lerariemann.infinity.util.InfinityMethods;
7import net.lerariemann.infinity.util.core.ConfigType;
8import net.lerariemann.infinity.util.core.NbtUtils;
9import net.lerariemann.infinity.util.core.RandomProvider;
10import net.minecraft.nbt.*;
11import net.minecraft.registry.Registry;
12import net.minecraft.registry.RegistryKey;
13import net.minecraft.registry.RegistryKeys;
14import net.minecraft.registry.tag.BiomeTags;
15import net.minecraft.registry.tag.TagKey;
16import net.minecraft.server.MinecraftServer;
17import net.minecraft.util.Identifier;
18import net.minecraft.util.WorldSavePath;
19import net.minecraft.util.math.MathHelper;
20import net.minecraft.world.biome.Biome;
21
22import java.io.IOException;
23import java.nio.file.Files;
24import java.nio.file.Paths;
25import java.util.*;
26
27public class RandomDimension {
28 public final long numericId;
29 public final RandomProvider PROVIDER;
30 public Identifier identifier;
31 public final Random random;
32 public int height;
33 public int min_y;
34 public int sea_level;
35 public boolean randomiseblocks;
36 public NbtCompound default_block;
37 public NbtCompound deepslate;
38 public NbtCompound default_fluid;
39 public List<NbtCompound> additional_blocks;
40 public List<String> vanilla_biomes;
41 public List<Long> random_biome_ids;
42 public List<RandomBiome> random_biomes;
43 public Map<String, NbtCompound> top_blocks;
44 public Map<String, List<String>> structure_ids;
45 public Map<String, NbtCompound> underwater;
46 public String type_alike;
47 public MinecraftServer server;
48 public NbtCompound data;
49 public RandomDimensionType type;
50
51 public RandomDimension(Identifier id, MinecraftServer server) {
52 this.server = server;
53 PROVIDER = InfinityMod.provider;
54 identifier = id;
55 numericId = InfinityMethods.getNumericFromId(identifier);
56 random = new Random(numericId);
57 initializeStorage();
58 /* Code for easter dimensions */
59 if (PROVIDER.easterizer.easterize(this)) {
60 wrap_up(true);
61 return;
62 }
63 /* Code for procedurally generated dimensions */
64 genBasics();
65 type = new RandomDimensionType(this);
66 data.putString("type", type.fullname);
67 data.put("generator", randomDimensionGenerator());
68 for (Long l: random_biome_ids) if (doesNotContain(RegistryKeys.BIOME, "biome_"+l)) {
69 RandomBiome b = new RandomBiome(l, this);
70 random_biomes.add(b);
71 addStructures(b);
72 }
73 writeTags(getRootPath());
74 wrap_up(false);
75 }
76
77 public String getName() {
78 return identifier.getPath();
79 }
80
81 public String getRootPath() {
82 return server.getSavePath(WorldSavePath.DATAPACKS).resolve(getName()).toString();
83 }
84
85 public String getStoragePath() {
86 return server.getSavePath(WorldSavePath.DATAPACKS).resolve(getName()).resolve("data").resolve(InfinityMod.MOD_ID).toString();
87 }
88
89 public void initializeStorage() {
90 data = new NbtCompound();
91 vanilla_biomes = new ArrayList<>();
92 random_biome_ids = new ArrayList<>();
93 random_biomes = new ArrayList<>();
94 top_blocks = new HashMap<>();
95 underwater = new HashMap<>();
96 structure_ids = new HashMap<>();
97 additional_blocks = new ArrayList<>();
98 }
99
100 public void genBasics() {
101 type_alike = PROVIDER.randomName(random, ConfigType.NOISE_PRESETS);
102 min_y = 16*MathHelper.clamp((int)Math.floor(random.nextExponential() * 2), isOverworldLike() ? -125 : -3, 0);
103 int avgHeight = MathHelper.clamp(RandomProvider.ruleInt("avgDimensionHeight"), 64, 1024);
104 int max_y = 16*MathHelper.clamp((int)Math.floor(random.nextGaussian(avgHeight/16.0, avgHeight/64.0)), isOverworldLike() ? 1 : 5, 125);
105 randomiseblocks = PROVIDER.roll(random, "randomise_blocks");
106 int sea_level_default = 63;
107 if (!isOverworldLike()) sea_level_default = switch(type_alike) {
108 case "minecraft:floating_islands" -> -64;
109 case "minecraft:end" -> 0;
110 case "minecraft:nether", "minecraft:caves" -> 32;
111 default -> 63;
112 };
113 sea_level = randomiseblocks ? (int)Math.floor(random.nextGaussian(sea_level_default, 8)) : sea_level_default;
114 max_y = Math.max(max_y, 16 * (int) (1 + Math.floor(sea_level / 16.0)));
115 height = max_y - min_y;
116 default_block = randomiseblocks ?
117 PROVIDER.randomElement(random, ConfigType.FULL_BLOCKS_WG) :
118 NbtUtils.nameToElement(getDefaultBlock("minecraft:stone"));
119 default_fluid = randomiseblocks ?
120 PROVIDER.randomElement(random, ConfigType.FLUIDS) :
121 NbtUtils.nameToFluid(getDefaultFluid());
122 deepslate = Arrays.stream((new String[]{"minecraft:overworld", "minecraft:amplified", "infinity:whack"})).toList().contains(type_alike) ?
123 NbtUtils.nameToElement("minecraft:deepslate") : default_block;
124 }
125
126 void wrap_up(boolean isEasterDim) {
127 if (!isEasterDim) (new DimensionData(this)).save();
128 (new RandomInfinityOptions(this, isEasterDim)).save();
129 CommonIO.write(data, getStoragePath() + "/dimension", getName() + ".json");
130 if (!(Paths.get(getRootPath() + "/pack.mcmeta")).toFile().exists()) CommonIO.write(packMcmeta(), getRootPath(), "pack.mcmeta");
131 }
132
133 String getDefaultBlock(String fallback) {
134 switch(type_alike) {
135 case "minecraft:end" -> {
136 return "minecraft:end_stone";
137 }
138 case "minecraft:nether" -> {
139 return "minecraft:netherrack";
140 }
141 default -> {
142 return fallback;
143 }
144 }
145 }
146 String getDefaultFluid() {
147 switch(type_alike) {
148 case "minecraft:end" -> {
149 return "minecraft:air";
150 }
151 case "minecraft:nether" -> {
152 return "minecraft:lava";
153 }
154 default -> {
155 return "minecraft:water";
156 }
157 }
158 }
159
160 public <T> boolean doesNotContain(RegistryKey<? extends Registry<T>> key, String name) {
161 return !(server.getRegistryManager().get(key).contains(RegistryKey.of(key, InfinityMethods.getId(name))));
162 }
163
164 boolean isOverworldLike() {
165 return (type_alike.equals("minecraft:overworld")) || (type_alike.equals("minecraft:large_biomes"))
166 || (type_alike.equals("minecraft:amplified")) || (type_alike.equals("infinity:whack"));
167 }
168
169 boolean hasCeiling() {
170 return ((type_alike.equals("minecraft:nether")) || (type_alike.equals("minecraft:caves")) || (type_alike.equals("infinity:tangled")));
171 }
172
173 NbtCompound packMcmeta() {
174 NbtCompound res = new NbtCompound();
175 NbtCompound pack = new NbtCompound();
176 pack.putInt("pack_format", 15);
177 pack.putString("description", "Dimension #" + numericId);
178 res.put("pack", pack);
179 return res;
180 }
181
182 NbtCompound randomDimensionGenerator() {
183 NbtCompound res = new NbtCompound();
184 String type = PROVIDER.randomName(random, ConfigType.GENERATOR_TYPES);
185 res.putString("type", type);
186 switch (type) {
187 case "minecraft:flat" -> {
188 res.put("settings", randomSuperflatSettings());
189 return res;
190 }
191 case "minecraft:noise" -> {
192 res.put("biome_source", randomBiomeSource());
193 res.putString("settings", randomNoiseSettings());
194 res.putLong("seed", numericId ^ server.getOverworld().getSeed());
195 return res;
196 }
197 default -> {
198 return res;
199 }
200 }
201 }
202
203 NbtCompound superflatLayer(int h, String block) {
204 NbtCompound res = new NbtCompound();
205 res.putInt("height", h);
206 res.putString("block", block);
207 return res;
208 }
209
210 NbtCompound randomSuperflatSettings() {
211 NbtCompound res = new NbtCompound();
212 NbtList layers = new NbtList();
213 String biome = randomBiome();
214 String block = "minecraft:air";
215 int layer_count = Math.min(64, 1 + (int) Math.floor(random.nextExponential() * 2));
216 int heightLeft = height;
217 for (int i = 0; i < layer_count; i++) {
218 int layerHeight = Math.min(heightLeft, 1 + (int) Math.floor(random.nextExponential() * 4));
219 heightLeft -= layerHeight;
220 block = PROVIDER.randomName(random, ConfigType.FULL_BLOCKS_WG);
221 layers.add(superflatLayer(layerHeight, block));
222 if (heightLeft <= 1) {
223 break;
224 }
225 }
226 if (random.nextBoolean()) {
227 block = PROVIDER.randomName(random, ConfigType.TOP_BLOCKS);
228 layers.add(superflatLayer(1, block));
229 }
230 res.putString("biome", biome);
231 res.put("layers", layers);
232 res.putBoolean("lakes", random.nextBoolean());
233 res.putBoolean("features", random.nextBoolean());
234 top_blocks.put(biome, NbtUtils.nameToElement(block));
235 underwater.put(biome, NbtUtils.nameToElement(block));
236 return res;
237 }
238
239 NbtCompound randomBiomeSource() {
240 NbtCompound res = new NbtCompound();
241 String type = PROVIDER.randomName(random, ConfigType.BIOME_SOURCE_TYPES);
242 res.putString("type",type);
243 switch (type) {
244 case "minecraft:the_end" -> {
245 return res;
246 }
247 case "minecraft:checkerboard" -> {
248 res.put("biomes", randomBiomesCheckerboard());
249 res.putInt("scale", Math.min(62, (int) Math.floor(random.nextExponential() * 2)));
250 return res;
251 }
252 case "minecraft:multi_noise" -> {
253 String preset = PROVIDER.randomName(random, ConfigType.MULTINOISE_PRESETS);
254 if (preset.equals("none") || hasCeiling()) res.put("biomes", randomBiomes());
255 else {
256 res.putString("preset", preset.replace("_", ":"));
257 addPresetBiomes(preset);
258 }
259 return res;
260 }
261 case "minecraft:fixed" -> res.putString("biome", randomBiome());
262 }
263 return res;
264 }
265
266 void addPresetBiomes(String preset) {
267 TagKey<Biome> tag = preset.equals("overworld") ? BiomeTags.IS_OVERWORLD : BiomeTags.IS_NETHER;
268 Registry<Biome> r = server.getRegistryManager().get(RegistryKeys.BIOME);
269 r.getKeys().forEach(key -> {
270 if (!Objects.equals(key.getValue().getNamespace(), "infinity")) {
271 if (r.get(key) != null && r.getEntry(r.get(key)).isIn(tag)) vanilla_biomes.add(key.getValue().toString());
272 }
273 });
274 }
275
276 int getBiomeCount() {
277 return random.nextInt(2, MathHelper.clamp(RandomProvider.ruleInt("maxBiomeCount"), 2, 10));
278 }
279
280 NbtList randomBiomesCheckerboard() {
281 NbtList res = new NbtList();
282 int biome_count = getBiomeCount();
283 for (int i = 0; i < biome_count; i++) {
284 res.add(NbtString.of(randomBiome()));
285 }
286 return res;
287 }
288 NbtList randomBiomes() {
289 NbtList res = new NbtList();
290 int biome_count = getBiomeCount();
291 for (int i = 0; i < biome_count; i++) {
292 NbtCompound element = new NbtCompound();
293 element.putString("biome", randomBiome());
294 element.put("parameters", randomMultiNoiseParameters());
295 res.add(element);
296 }
297 return res;
298 }
299
300 NbtCompound randomMultiNoiseParameters() {
301 NbtCompound res = new NbtCompound();
302 res.put("temperature", randomMultiNoiseParameter());
303 res.put("humidity", randomMultiNoiseParameter());
304 res.put("continentalness", randomMultiNoiseParameter());
305 res.put("erosion", randomMultiNoiseParameter());
306 res.put("weirdness", randomMultiNoiseParameter());
307 res.put("depth", randomMultiNoiseParameter());
308 res.put("offset", NbtDouble.of(random.nextDouble()));
309 return res;
310 }
311
312 NbtElement randomMultiNoiseParameter() {
313 if (random.nextBoolean()) {
314 NbtCompound res = new NbtCompound();
315 double a = (random.nextFloat()-0.5)*2;
316 double b = (random.nextFloat()-0.5)*2;
317 res.putFloat("min", (float)Math.min(a, b));
318 res.putFloat("max", (float)Math.max(a, b));
319 return res;
320 }
321 return NbtDouble.of((random.nextDouble()-0.5)*2);
322 }
323
324 String randomBiome() {
325 String biome;
326 if (!hasCeiling() && !PROVIDER.roll(random, "use_random_biome")) {
327 biome = PROVIDER.randomName(random, ConfigType.BIOMES);
328 vanilla_biomes.add(biome);
329 }
330 else {
331 long id = InfinityMethods.getRandomSeed(random);
332 random_biome_ids.add(id);
333 biome = "infinity:biome_" + id;
334 }
335 return biome;
336 }
337
338 String randomNoiseSettings() {
339 RandomNoisePreset preset = new RandomNoisePreset(this);
340 return preset.fullname;
341 }
342
343 void addStructures(RandomBiome b) {
344 int numstructures = random.nextInt(1, 5);
345 Set<String> temp = new HashSet<>();
346 for (int i = 0; i < numstructures; i++) {
347 addStructure(new RandomStructure(random.nextInt(), b), temp);
348 }
349 if (PROVIDER.roll( random, "text")) {
350 addStructure(new RandomText(random.nextInt(), b), temp);
351 }
352 }
353 void addStructure(RandomStructure s, Set<String> temp) {
354 if (!temp.contains(s.name)) {
355 temp.add(s.name);
356 s.save();
357 if (!structure_ids.containsKey(s.type)) structure_ids.put(s.type, new ArrayList<>());
358 structure_ids.get(s.type).add(s.fullname);
359 }
360 }
361
362 void writeTags(String rootPath) {
363 String path = rootPath + "/data/minecraft/tags/worldgen/structure";
364 try {
365 Files.createDirectories(Paths.get(path));
366 } catch (IOException e) {
367 throw new RuntimeException(e);
368 }
369 NbtCompound dictionary = CommonIO.read(InfinityMod.utilPath + "/structure_tags.json");
370 Map<String, NbtList> tags = new HashMap<>();
371 for (String s : structure_ids.keySet()) for (String ss : dictionary.getKeys()) if (s.contains(ss)) {
372 for (NbtElement e : (NbtList) Objects.requireNonNull(dictionary.get(ss))) {
373 String t = e.asString();
374 if (!tags.containsKey(t)) tags.put(t, new NbtList());
375 structure_ids.get(s).forEach(fullname -> tags.get(t).add(NbtString.of(fullname)));
376 }
377 }
378 for (String t : tags.keySet()) {
379 NbtCompound compound = new NbtCompound();
380 compound.putBoolean("replace", false);
381 compound.put("values", tags.get(t));
382 CommonIO.write(compound, path, t + ".json");
383 }
384 }
385}