Inspired by 2020's April Fools' 20w14infinite Snapshot, this mod brings endless randomly generated dimensions into Minecraft.

block tag amendment

Lera 1ec53bc9 8d65ae56

+224 -29
+2 -1
common/src/main/java/net/lerariemann/infinity/InfinityMod.java
··· 21 21 public static Path configPath = Path.of("config", InfinityMod.MOD_ID); 22 22 public static Path utilPath = configPath.resolve(".util"); 23 23 public static Path invocationLock = configPath.resolve("modular/invocation.lock"); 24 + public static Path amendmentPath = configPath.resolve("amendments.json"); 24 25 25 26 public static Path rootConfigPathInJar; 26 27 public static RandomProvider provider; ··· 35 36 36 37 public static void init() { 37 38 rootConfigPathInJar = PlatformMethods.getRootConfigPath(); 38 - ConfigManager.updateInvocationLock(); 39 + ConfigManager.updateInvocationUnlock(); 39 40 ConfigManager.unpackDefaultConfigs(); 40 41 ModComponentTypes.registerComponentTypes(); 41 42 ModStatusEffects.registerModEffects();
+2 -8
common/src/main/java/net/lerariemann/infinity/mixin/core/MinecraftServerMixin.java
··· 4 4 import net.lerariemann.infinity.InfinityMod; 5 5 import net.lerariemann.infinity.util.PlatformMethods; 6 6 import net.lerariemann.infinity.access.MinecraftServerAccess; 7 + import net.lerariemann.infinity.util.config.ConfigManager; 7 8 import net.minecraft.network.QueryableServer; 8 9 import net.minecraft.registry.DynamicRegistryManager; 9 10 import net.minecraft.registry.RegistryKey; ··· 36 37 37 38 import java.io.IOException; 38 39 import java.nio.file.Files; 39 - import java.nio.file.Path; 40 40 import java.util.HashMap; 41 41 import java.util.List; 42 42 import java.util.Map; 43 43 import java.util.concurrent.Executor; 44 - 45 - import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; 46 44 47 45 @Mixin(MinecraftServer.class) 48 46 public abstract class MinecraftServerMixin extends ReentrantThreadExecutor<ServerTask> implements QueryableServer, ChunkErrorHandler, CommandOutput, AutoCloseable, MinecraftServerAccess { ··· 90 88 public void infinity$onInvocation() { 91 89 infinity$needsInvocation = false; 92 90 try { 93 - Path p = InfinityMod.invocationLock; 94 - if (!Files.exists(p)) { 95 - Files.createDirectories(p.getParent()); 96 - Files.copy(InfinityMod.rootConfigPathInJar.resolve(".util/invocation.lock"), p, REPLACE_EXISTING); 97 - } 91 + ConfigManager.updateInvocationLock(); 98 92 infinity$setDimensionProvider(); 99 93 InfinityMod.LOGGER.info("Invocation complete"); 100 94 } catch (IOException e) {
+29 -5
common/src/main/java/net/lerariemann/infinity/util/config/Amendment.java
··· 4 4 import net.lerariemann.infinity.InfinityMod; 5 5 import net.lerariemann.infinity.util.core.CommonIO; 6 6 import net.lerariemann.infinity.util.core.ConfigType; 7 + import net.minecraft.block.Block; 7 8 import net.minecraft.nbt.NbtCompound; 8 9 import net.minecraft.nbt.NbtElement; 9 10 import net.minecraft.nbt.NbtString; 11 + import net.minecraft.registry.Registries; 12 + import net.minecraft.registry.RegistryKey; 13 + import net.minecraft.registry.RegistryKeys; 14 + import net.minecraft.registry.tag.TagKey; 15 + import net.minecraft.util.Identifier; 10 16 11 17 import java.util.ArrayList; 12 18 import java.util.HashMap; ··· 25 31 else modSelector = new MatchingModSelector(mod); 26 32 27 33 ConfigType area = ConfigType.byName(data.getString("area")); 28 - Selector selector = switch(data.getString("selector")) { 34 + String selectorType = data.getString("selector"); 35 + Selector selector = switch(selectorType) { 29 36 case "all" -> new UniversalSelector(); 30 37 case "containing" -> new ContainingSelector(data.getString("containing")); 31 38 case "matching" -> new MatchingSelector(data.getString("matching")); 39 + case "matching_block_tag" -> new MatchingBlockTagSelector(data.getString("matching")); 32 40 case "matching_any" -> new MatchingAnySelector(data.getList("matching", NbtElement.STRING_TYPE) 33 41 .stream().map(e->(NbtString)e).map(NbtString::asString).toList()); 34 - default -> null; 42 + default -> { 43 + InfinityMod.LOGGER.warn("Unknown amendment selector type: {}", selectorType); 44 + yield null; 45 + } 35 46 }; 36 - Results results = switch (data.getString("results")) { 47 + String resultType = data.getString("results"); 48 + Results results = switch (resultType) { 37 49 case "set_value" -> new SetValue(data.getInt("value")); 38 50 case "erase" -> new SetValue(0); 39 51 case "set_field" -> new SetField(data.getString("field_name"), data.get("field")); 40 - default -> null; 52 + default -> { 53 + InfinityMod.LOGGER.warn("Unknown amendment result type: {}", resultType); 54 + yield null; 55 + } 41 56 }; 42 57 43 58 if (area == null || selector == null || results == null) return null; ··· 46 61 47 62 public static Map<ConfigType, List<Amendment>> getAmendmentList() { 48 63 Map<ConfigType, List<Amendment>> data = new HashMap<>(); 49 - NbtCompound rawData = CommonIO.read(InfinityMod.configPath.resolve("modular").resolve("amendments.json")); 64 + NbtCompound rawData = CommonIO.read(InfinityMod.amendmentPath); 50 65 AtomicInteger i = new AtomicInteger(); 51 66 for (NbtElement e : rawData.getList("elements", NbtElement.COMPOUND_TYPE)) { 52 67 Amendment amd = Amendment.of((NbtCompound)e); ··· 97 112 public record MatchingSelector(String key) implements Selector { 98 113 public boolean applies(String key) { 99 114 return key.equals(this.key); 115 + } 116 + } 117 + public record MatchingBlockTagSelector(String key) implements Selector { 118 + public boolean applies(String key) { 119 + Block b = Registries.BLOCK.get(RegistryKey.of(RegistryKeys.BLOCK, Identifier.of(key))); 120 + if (b != null) { 121 + return b.getDefaultState().isIn(TagKey.of(RegistryKeys.BLOCK, Identifier.of(this.key))); 122 + } 123 + return false; 100 124 } 101 125 } 102 126 public record MatchingAnySelector(List<String> lst) implements Selector {
+28 -3
common/src/main/java/net/lerariemann/infinity/util/config/ConfigManager.java
··· 6 6 import net.minecraft.nbt.NbtCompound; 7 7 import net.minecraft.nbt.NbtElement; 8 8 import net.minecraft.nbt.NbtList; 9 + import org.apache.commons.io.FileUtils; 9 10 import org.apache.logging.log4j.LogManager; 10 11 11 12 import java.io.File; 12 13 import java.io.IOException; 14 + import java.nio.charset.StandardCharsets; 13 15 import java.nio.file.Files; 14 16 import java.nio.file.Path; 15 17 import java.nio.file.Paths; ··· 29 31 /** If the game was last started in a version of the mod that uses a different modular config format, 30 32 * this deletes the old configs ensuring they will be regenerated in the correct format 31 33 * by {@link ConfigFactory} on next world load. */ 32 - static void updateInvocationLock() { 34 + static void updateInvocationUnlock() { 33 35 File invlock = InfinityMod.invocationLock.toFile(); 34 36 if (!invlock.exists()) return; 35 37 try { 36 - if (!compareVersions(InfinityMod.invocationLock, InfinityMod.rootConfigPathInJar.resolve( ".util/invocation.lock"))) return; 38 + int a = compareVersionsAndAmendments(InfinityMod.invocationLock, InfinityMod.rootConfigPathInJar.resolve( ".util/invocation.lock")); 39 + if (a == 0) return; 37 40 try (Stream<Path> files = Files.walk(configPath.resolve("modular"))) { 38 - InfinityMod.LOGGER.info("Deleting outdated modular configs"); 41 + InfinityMod.LOGGER.info("Deleting {} modular configs", a == 1 ? "outdated" : "amended"); 39 42 files.map(Path::toFile).filter(File::isFile).forEach(f -> { 40 43 if (!f.delete()) InfinityMod.LOGGER.info("Cannot delete file {}", f); 41 44 }); ··· 44 47 throw new RuntimeException(e); 45 48 } 46 49 } 50 + 51 + static void updateInvocationLock() throws IOException { 52 + Path inv = InfinityMod.invocationLock; 53 + if (!Files.exists(inv)) { 54 + Files.createDirectories(inv.getParent()); 55 + Files.copy(InfinityMod.rootConfigPathInJar.resolve(".util/invocation.lock"), tempFile, REPLACE_EXISTING); 56 + String s = FileUtils.readFileToString(tempFile.toFile(), StandardCharsets.UTF_8); 57 + s = s.replace("&0", String.valueOf(CommonIO.getAmendmentVersion(InfinityMod.amendmentPath.toFile()))); 58 + Files.writeString(inv, s, StandardCharsets.UTF_8); 59 + } 60 + } 47 61 48 62 static void unpackDefaultConfigs() { 49 63 try(Stream<Path> files = Files.walk(InfinityMod.rootConfigPathInJar)) { ··· 99 113 Files.copy(newFile, tempFile, REPLACE_EXISTING); 100 114 int version_new = CommonIO.getVersion(tempFile.toFile()); 101 115 return version_new > version_old; 116 + } 117 + 118 + static int compareVersionsAndAmendments(Path oldFile, Path newFile) throws IOException { 119 + int version_old = CommonIO.getVersion(oldFile.toFile()); 120 + int amendment_version_old = CommonIO.getAmendmentVersion(oldFile.toFile()); 121 + Files.copy(newFile, tempFile, REPLACE_EXISTING); 122 + int version_new = CommonIO.getVersion(tempFile.toFile()); 123 + int amendment_version_new = CommonIO.getAmendmentVersion(InfinityMod.amendmentPath.toFile()); 124 + if (version_new > version_old) return 1; 125 + if (amendment_version_new > amendment_version_old) return 2; 126 + return 0; 102 127 } 103 128 104 129 @Deprecated
+12 -3
common/src/main/java/net/lerariemann/infinity/util/core/CommonIO.java
··· 13 13 import java.nio.file.Paths; 14 14 import java.util.*; 15 15 16 + import static net.lerariemann.infinity.InfinityMod.LOGGER; 16 17 import static net.lerariemann.infinity.InfinityMod.configPath; 17 18 18 19 public interface CommonIO { ··· 46 47 } 47 48 48 49 static int getVersion(File file) { 50 + return getStaistic(file, "infinity_version"); 51 + } 52 + static int getAmendmentVersion(File file) { 53 + return getStaistic(file, "amendment_version"); 54 + } 55 + 56 + static int getStaistic(File file, String statname) { 49 57 String content; 50 58 try { 51 59 content = FileUtils.readFileToString(file, StandardCharsets.UTF_8); 52 - if (!content.contains("infinity_version")) return 0; 53 - int i = content.indexOf("infinity_version"); 60 + if (!content.contains(statname)) return 0; 61 + int i = content.indexOf(statname); 54 62 int end = content.indexOf(",", i); 55 63 if (end == -1) { 56 64 end = content.indexOf("\n", i); 57 65 } 58 66 return Integer.parseInt(content.substring(content.indexOf(" ", i)+1, end).trim()); 59 67 } catch (IOException e) { 60 - throw new RuntimeException(e); 68 + LOGGER.warn("No file found: {}", file); 69 + return 0; 61 70 } 62 71 } 63 72
+2 -1
common/src/main/resources/config/.util/invocation.lock
··· 1 - infinity_version: 2004001 1 + infinity_version: 2004002 2 + amendment_version: &0 2 3 Delete this file to regenerate modular configs automatically 3 4 (e.g. you may want to do this when adding new mods to the instance)
+2 -1
common/src/main/resources/config/modular/amendments.json common/src/main/resources/config/amendments.json
··· 1 1 { 2 + "amendment_version": 0, 2 3 "elements": [ 3 4 { 4 5 "area": "fluids", ··· 62 63 "results": "erase" 63 64 }, 64 65 { 65 - "area": "structures", 66 + "area": "blocks", 66 67 "mod": "oritech", 67 68 "selector": "containing", 68 69 "containing": "pipe",
+147 -7
docs/Invocation.mdx
··· 21 21 * status effects, 22 22 * block tags, 23 23 * structures plus some of their additional data, 24 - * biomes and surface rules for them. 24 + * biomes, 25 + * (in limited capacity) surface rules for biomes. 25 26 26 27 Blocks are auto-assigned tags needed for the generator to know which parts of worldgen are they usable for ("full", "laggy" etc., 27 28 see the page on [configuring the mod](Configuring-the-mod)), plus checked for different traits (being a flower, rotatable, dyeable etc.). ··· 38 39 ## Known issues with invocation 39 40 * Invocation only happens once per game folder. As such, if you add new mods to the instance after you already loaded worlds on it once, 40 41 invocation will not happen again. This especially stands for the default Minecraft launcher by Mojang, which by design only uses one. 41 - To force the invocation to happen again, you can either construct and activate the Transfinite Altar, or delete the file 42 - `config/infinity/modular/invocation.lock` and relaunch the game. 43 - * Note that such reinvocation will not touch any of the existing files, only add new ones. As such, it is useful by itself only when 44 - adding new mods to the instance. If you update one of the mods (including Infinite Dimensions!) to a newer version that brings 45 - more content, or *especially* if you remove mods, you have to purge the `/config/modular` folder entirely to do a successful reinvocation. 42 + To force the invocation to happen again, you need to delete the file `config/infinity/modular/invocation.lock` and relaunch the game. 43 + * Repeated invocation will not touch any of the existing files, only add new ones. As such, it is useful by itself only when 44 + adding new mods to the instance. If you update one of the mods to a newer version that brings more content, or *especially* if you remove mods, 45 + you have to purge the `/config/modular` folder entirely to do a successful reinvocation. 46 46 * Starting at version 2.1.0 of the mod, Infinite Dimensions will automatically delete all modular configs and issue reinvocation 47 47 if it detects that the version of those configs (set in the invocation lock) is outdated. If you make changes to your modular configs, 48 - do a backup of them before updating the mod! 48 + do a backup of them before updating the mod! 49 + 50 + ## Amendments 51 + Amendments are a system of automatically changing values in config files without changing them one-by-one. A more proper integration (for editing them 52 + in GUI) will be explored later, but so far it is possible to do them by editing files. 53 + 54 + Amendments are stored in `config/infinity/amendments.json` and only can affect modular configs. To change the list of active amendments, 55 + one must edit the amendments.json file _making sure to increase up the amendment_version statistic at the top_, then fully close and restart the game. 56 + 57 + Amendments consist of four distinct parts governing when and how the amendment will be passed: 58 + * Area. This is the scope of the amendment in terms of type of game content it affects: `"blocks"`, `"fluids"`, `"items"`, `"structures"` and `"mobs"` 59 + are the ones that would probably be most useful to a user, but there are other options too. 60 + * Mod. This cam be set to a mod ID (including `"minecraft"`) to only select content from this mod, or to `"all"` to skip the mod check. 61 + * Selector. This governs what content within the selected mod and area is affected by the amendment, and there are several types available: 62 + * `"all"` - amend everything (useful if you want to skip some mod entirely); 63 + * `"matching"` - amend a single entry, with it's key set as a value under a key `matching` (i.e., `"selector": "matching", "matching": "minecraft:lava"`); 64 + * `"matching_any"` - amend every entry in a list, provided under a key `matching`; 65 + * `"matching_block_tag"` - only works when `area` is set to `"blocks"` and amends all blocks under a tag. The tag ID is provided under `matching` without 66 + the leading `#` symbol. 67 + * `"containing"` - amends all entries the names of which contain a certain string of characters set under the `containing` field. 68 + * Results - what will the amendment do, the following options are available: 69 + * `"set_value"` - sets the weight of an element to a number provided under `value` (the default weight is 1); 70 + * `"set_field"` - useful e.g. for blocks if you want to mark them as laggy overriding the default algorithm that checks it; 71 + additional fields are `field_name` and `field` allowing you to add any field to the data amended content has; 72 + * `"erase"` - skip the entry entirely, effectively the same as setting its weight to 0. 73 + 74 + Let's break down the default list of ten amendments the mod comes with. Most of them are fixes for various mod compatibility issues: 75 + ``` 76 + { 77 + "area": "fluids", 78 + "mod": "minecraft", 79 + "selector": "matching", 80 + "matching": "minecraft:lava", 81 + "results": "set_value", 82 + "value": 2.0 83 + }, 84 + { 85 + "area": "fluids", 86 + "mod": "minecraft", 87 + "selector": "matching", 88 + "matching": "minecraft:water", 89 + "results": "set_value", 90 + "value": 5.0 91 + } 92 + ``` 93 + The first and second amendments modify the weights the two vanilla fluids have in the distribution of all fluids, making them 94 + a bit more common in modded scenarios. 95 + 96 + ``` 97 + { 98 + "area": "blocks", 99 + "mod": "minecraft", 100 + "selector": "matching_any", 101 + "matching": [ 102 + "minecraft:slime_block", 103 + "minecraft:honey_block", 104 + "minecraft:mangrove_roots" 105 + ], 106 + "results": "set_field", 107 + "field_name": "laggy", 108 + "field": true 109 + } 110 + ``` 111 + The third amendment marks some vanilla blocks as laggy. These are the blocks which can overwhelm the rendering algorithm when present in large blobs 112 + due to having unculled inner faces on their model. 113 + 114 + ``` 115 + { 116 + "area": "trees", 117 + "mod": "all", 118 + "selector": "containing", 119 + "containing": "bees", 120 + "results": "erase" 121 + } 122 + ``` 123 + The fourth amendment forbids generation of any trees that have beehives on them (conveniently, their IDs all have the word bees in them), 124 + as large amounts of those leads to large amounts of bees present which can lag the game. 125 + 126 + ``` 127 + { 128 + "area": "fluids", 129 + "mod": "modern_industrialization", 130 + "selector": "all", 131 + "results": "erase" 132 + } 133 + ``` 134 + The fifth amendment forbids all fluids from the Modern Industrialization mod from appearing, as they lack fluid physics and rendering due to not having 135 + been supposed to ever be placed in-world. 136 + 137 + ``` 138 + { 139 + "area": "mobs", 140 + "mod": "minecolonies", 141 + "selector": "all", 142 + "results": "erase" 143 + } 144 + ``` 145 + The sixth amendment prevents all mobs from Minecolonies from spawning in this mod's biomes as part of the natural spawning algorithm, 146 + as these error out the game if they do due to incomplete entity data. 147 + 148 + ``` 149 + { 150 + "area": "structures", 151 + "mod": "alexscaves", 152 + "selector": "all", 153 + "results": "erase" 154 + }, 155 + { 156 + "area": "structures", 157 + "mod": "aether", 158 + "selector": "containing", 159 + "containing": "dungeon", 160 + "results": "erase" 161 + } 162 + ``` 163 + The seventh and eighth amendments bans all structures from Alex's Caves (aka, the caves themselves) and Aether dungeons from generating in this mod's 164 + custom dimensions, as they sometimes throw errors when they do due to failing their inner checks when called to generate outside of their natural 165 + environment. 166 + 167 + ``` 168 + { 169 + "area": "structures", 170 + "mod": "oritech", 171 + "selector": "containing", 172 + "containing": "pipe", 173 + "results": "erase" 174 + } 175 + ``` 176 + The ninth amendment bans all pipe blocks from Oritech, because when these are placed during world generation, they try to update their shape 177 + and in the process poll the world for metrics which on this stage aren't yet present, resulting in a crash. 178 + 179 + ``` 180 + { 181 + "area": "blocks", 182 + "mod": "chipped", 183 + "selector": "all", 184 + "results": "set_value", 185 + "value": 0.1 186 + } 187 + ``` 188 + The tenth amendment reduces weights of all blocks from the Chipped mod down to 0.1, as there are thousands of them :D