From 8d5f2356849d322f04e46c20da34d6af58c26e20 Mon Sep 17 00:00:00 2001 From: Frank Bauer Date: Mon, 19 Jul 2021 19:54:16 +0200 Subject: [PATCH 1/5] Fixed hash-calculation for `ConfigKey` --- src/main/java/ru/bclib/config/ConfigKey.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/bclib/config/ConfigKey.java b/src/main/java/ru/bclib/config/ConfigKey.java index 88b2083b..ca1d7da0 100644 --- a/src/main/java/ru/bclib/config/ConfigKey.java +++ b/src/main/java/ru/bclib/config/ConfigKey.java @@ -1,5 +1,7 @@ package ru.bclib.config; +import java.util.Arrays; + import net.minecraft.resources.ResourceLocation; public class ConfigKey { @@ -34,7 +36,7 @@ public class ConfigKey { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + path.hashCode(); + result = prime * result + Arrays.hashCode(path); result = prime * result + entry.hashCode(); return result; } From fee7cbc93ad0ff6522abc3b768c7f3300d5ecb9e Mon Sep 17 00:00:00 2001 From: Frank Bauer Date: Mon, 19 Jul 2021 19:54:16 +0200 Subject: [PATCH 2/5] Added possibility to store configs at costum locations --- src/main/java/ru/bclib/config/Config.java | 10 ++++++-- .../java/ru/bclib/config/ConfigKeeper.java | 23 ++++++++++++------- .../java/ru/bclib/config/ConfigWriter.java | 22 +++++++++++++----- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/main/java/ru/bclib/config/Config.java b/src/main/java/ru/bclib/config/Config.java index a1df5824..9219589a 100644 --- a/src/main/java/ru/bclib/config/Config.java +++ b/src/main/java/ru/bclib/config/Config.java @@ -1,5 +1,7 @@ package ru.bclib.config; +import java.io.File; + import org.jetbrains.annotations.Nullable; import ru.bclib.BCLib; import ru.bclib.config.ConfigKeeper.BooleanEntry; @@ -13,9 +15,13 @@ public abstract class Config { protected final ConfigKeeper keeper; protected abstract void registerEntries(); + + public Config(String modID, String group){ + this(modID, group, null); + } - public Config(String modID, String group) { - this.keeper = new ConfigKeeper(modID, group); + protected Config(String modID, String group, File path) { + this.keeper = new ConfigKeeper(modID, group, path); this.registerEntries(); } diff --git a/src/main/java/ru/bclib/config/ConfigKeeper.java b/src/main/java/ru/bclib/config/ConfigKeeper.java index 841e7d27..0b5daf62 100644 --- a/src/main/java/ru/bclib/config/ConfigKeeper.java +++ b/src/main/java/ru/bclib/config/ConfigKeeper.java @@ -1,17 +1,20 @@ package ru.bclib.config; +import java.io.File; +import java.lang.reflect.Type; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.jetbrains.annotations.Nullable; + import com.google.common.collect.Maps; import com.google.common.reflect.TypeToken; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import net.minecraft.util.GsonHelper; -import org.jetbrains.annotations.Nullable; -import ru.bclib.util.JsonFactory; -import java.lang.reflect.Type; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Supplier; +import net.minecraft.util.GsonHelper; +import ru.bclib.util.JsonFactory; public final class ConfigKeeper { private final Map> configEntries = Maps.newHashMap(); @@ -21,7 +24,11 @@ public final class ConfigKeeper { private boolean changed = false; public ConfigKeeper(String modID, String group) { - this.writer = new ConfigWriter(modID, group); + this(modID, group, null); + } + + protected ConfigKeeper(String modID, String group, File path) { + this.writer = new ConfigWriter(modID, group, path); this.configObject = writer.load(); } diff --git a/src/main/java/ru/bclib/config/ConfigWriter.java b/src/main/java/ru/bclib/config/ConfigWriter.java index 5884641d..98f70826 100644 --- a/src/main/java/ru/bclib/config/ConfigWriter.java +++ b/src/main/java/ru/bclib/config/ConfigWriter.java @@ -1,13 +1,14 @@ package ru.bclib.config; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import net.fabricmc.loader.api.FabricLoader; -import ru.bclib.util.JsonFactory; - import java.io.File; import java.nio.file.Path; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import net.fabricmc.loader.api.FabricLoader; +import ru.bclib.util.JsonFactory; + public class ConfigWriter { private final static Path GAME_CONFIG_DIR = FabricLoader.getInstance().getConfigDir(); @@ -15,7 +16,16 @@ public class ConfigWriter { private JsonObject configObject; public ConfigWriter(String modID, String configFile) { - this.configFile = new File(new File(GAME_CONFIG_DIR.toFile(), modID), configFile + ".json"); + this(modID, configFile, null); + } + + public ConfigWriter(String modID, String configFile, File configFolder) { + this.configFile = new File( + (configFolder==null + ? GAME_CONFIG_DIR.resolve(modID).toFile() + : new File(configFolder, modID)), + configFile + ".json" + ); File parent = this.configFile.getParentFile(); if (!parent.exists()) { parent.mkdirs(); From 102c9ec0ccd8d4a1087373c9e5a7d5820ed7ca18 Mon Sep 17 00:00:00 2001 From: Frank Bauer Date: Mon, 19 Jul 2021 19:54:16 +0200 Subject: [PATCH 3/5] Added a `SessionConfig` class that can generate config files inside a world folder --- src/main/java/ru/bclib/config/PathConfig.java | 8 +++++- .../java/ru/bclib/config/SessionConfig.java | 25 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ru/bclib/config/SessionConfig.java diff --git a/src/main/java/ru/bclib/config/PathConfig.java b/src/main/java/ru/bclib/config/PathConfig.java index da3c1d49..1b09efcf 100644 --- a/src/main/java/ru/bclib/config/PathConfig.java +++ b/src/main/java/ru/bclib/config/PathConfig.java @@ -1,5 +1,7 @@ package ru.bclib.config; +import java.io.File; + import org.jetbrains.annotations.Nullable; import ru.bclib.config.ConfigKeeper.Entry; import ru.bclib.config.ConfigKeeper.FloatRange; @@ -8,7 +10,11 @@ import ru.bclib.config.ConfigKeeper.IntegerRange; public class PathConfig extends Config { public PathConfig(String modID, String group) { - super(modID, group); + this(modID, group, null); + } + + protected PathConfig(String modID, String group, File path) { + super(modID, group, path); } @Override diff --git a/src/main/java/ru/bclib/config/SessionConfig.java b/src/main/java/ru/bclib/config/SessionConfig.java new file mode 100644 index 00000000..8fb3b1c2 --- /dev/null +++ b/src/main/java/ru/bclib/config/SessionConfig.java @@ -0,0 +1,25 @@ +package ru.bclib.config; + +import java.io.File; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.storage.LevelStorageSource; +import ru.bclib.BCLib; + +public class SessionConfig extends PathConfig{ + private static File getWorldFolder(LevelStorageSource.LevelStorageAccess session, ServerLevel world){ + File dir = session.getDimensionPath(world.dimension()); + if (!new File(dir, "level.dat").exists()) { + dir = dir.getParentFile(); + } + return dir; + } + + public final File levelFolder; + + public SessionConfig(String modID, String group, LevelStorageSource.LevelStorageAccess session, ServerLevel world) { + super(modID, group, new File(getWorldFolder(session, world), BCLib.MOD_ID)); + + this.levelFolder = new File(getWorldFolder(session, world), BCLib.MOD_ID); + } +} From 51235429db3e173d7344df2d3abbcf8d5c6f0218 Mon Sep 17 00:00:00 2001 From: Frank Bauer Date: Mon, 19 Jul 2021 19:54:37 +0200 Subject: [PATCH 4/5] Added alternative `DataFixerAPI` --- src/main/java/ru/bclib/api/DataFixerAPI2.java | 289 ++++++++++++++++++ src/main/java/ru/bclib/config/Configs.java | 4 + 2 files changed, 293 insertions(+) create mode 100644 src/main/java/ru/bclib/api/DataFixerAPI2.java diff --git a/src/main/java/ru/bclib/api/DataFixerAPI2.java b/src/main/java/ru/bclib/api/DataFixerAPI2.java new file mode 100644 index 00000000..3f407b23 --- /dev/null +++ b/src/main/java/ru/bclib/api/DataFixerAPI2.java @@ -0,0 +1,289 @@ +package ru.bclib.api; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.jetbrains.annotations.NotNull; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.storage.RegionFile; +import ru.bclib.BCLib; +import ru.bclib.config.Configs; +import ru.bclib.config.PathConfig; +import ru.bclib.config.SessionConfig; +import ru.bclib.util.Logger; + +public class DataFixerAPI2 { + private static final Logger LOGGER = new Logger("DataFixerAPI"); + + public static void fixData(SessionConfig config) { + if (true || !Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_PATCH_CATEGORY, "applyPatches", true)){ + LOGGER.info("World Patches are disabled"); + return; + } + + final File dir = config.levelFolder; + Patch.MigrationData data = Patch.createMigrationData(config); + if (!data.hasAnyFixes()) { + LOGGER.info("Everything up to date"); + return; + } + + List regions = getAllRegions(dir, null); + regions.parallelStream().forEach((file) -> { + try { + LOGGER.info("Inspecting " + file); + boolean[] changed = new boolean[1]; + RegionFile region = new RegionFile(file, file.getParentFile(), true); + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + ChunkPos pos = new ChunkPos(x, z); + changed[0] = false; + if (region.hasChunk(pos)) { + DataInputStream input = region.getChunkDataInputStream(pos); + CompoundTag root = NbtIo.read(input); + // if ((root.toString().contains("betternether") || root.toString().contains("bclib")) && root.toString().contains("chest")) { + // NbtIo.write(root, new File(file.toString() + "-" + x + "-" + z + ".nbt")); + // } + input.close(); + + ListTag tileEntities = root.getCompound("Level").getList("TileEntities", 10); + fixItemArrayWithID(tileEntities, changed, data, true); + + ListTag sections = root.getCompound("Level").getList("Sections", 10); + sections.forEach((tag) -> { + ListTag palette = ((CompoundTag) tag).getList("Palette", 10); + palette.forEach((blockTag) -> { + CompoundTag blockTagCompound = ((CompoundTag) blockTag); + changed[0] = data.replaceStringFromIDs(blockTagCompound, "Name"); + }); + }); + if (changed[0]) { + LOGGER.warning("Writing '{}': {}/{}", file, x, z); + DataOutputStream output = region.getChunkDataOutputStream(pos); + NbtIo.write(root, output); + output.close(); + } + } + } + } + region.close(); + } + catch (Exception e) { + e.printStackTrace(); + } + }); + + data.markApplied(); + } + + private static boolean fixItemArrayWithID(ListTag items, boolean[] changed, Patch.MigrationData data, boolean recursive){ + items.forEach(inTag -> { + final CompoundTag tag = (CompoundTag) inTag; + + changed[0] |= data.replaceStringFromIDs(tag, "id"); + + if (recursive && tag.contains("Items")){ + changed[0] |= fixItemArrayWithID(tag.getList("Items", 10), changed, data, true); + } + }); + + return changed[0]; + } + + private static List getAllRegions(File dir, List list) { + if (list == null) { + list = new ArrayList<>(); + } + for (File file : dir.listFiles()) { + if (file.isDirectory()) { + getAllRegions(file, list); + } + else if (file.isFile() && file.getName().endsWith(".mca")) { + list.add(file); + } + } + return list; + } + + public abstract static class Patch { + private static List ALL = new ArrayList<>(10); + private final int level; + @NotNull + private final String modID; + + static List getALL() { + return ALL; + } + + /** + * register a new Patch + * @param patch A #Supplier that will instantiate the new Patch Object + */ + public static void registerPatch(Supplier patch){ + ALL.add(patch.get()); + } + + /** + * Returns the highest patch-level that is available for the given mod. If no patches were + * registerd for the mod, this will return 0 + * @param modID The ID of the mod you want to query + * @return The highest Patch-Level that was found + */ + public static int maxPatchLevel(@NotNull String modID){ + return ALL.stream() + .filter(p-> p.getModID().equals(modID)) + .mapToInt(p->p.level) + .max() + .orElse(0); + } + + /** + * Called by inheriting classes. + * + * Performs some sanity checks on the values and might throw a #RuntimeException if any + * inconsistencies are found. + * + * @param modID The ID of the Mod you want to register a patch for. This should be your + * ModID only. The ModID can not be {@code null} or an empty String. + * @param level The level of the Patch. This needs to be a non-zero positive value. + * Developers are responsible for registering their patches in the correct + * order (with increasing levels). You are not allowed to register a new + * Patch with a Patch-level lower or equal than + * {@link Patch#maxPatchLevel(String)} + */ + protected Patch(@NotNull String modID, int level) { + //Patchlevels need to be unique and registered in ascending order + if (modID==null || "".equals(modID)){ + throw new RuntimeException("[INTERNAL ERROR] Patches need a valid modID!"); + } + if (!ALL.stream().filter(p-> p.getModID().equals(modID)).noneMatch(p -> p.getLevel() >= level) || level <= 0){ + throw new RuntimeException("[INTERNAL ERROR] Patch-levels need to be created in ascending order beginning with 1."); + } + + + BCLib.LOGGER.info("Creating Patchlevel {} ({}, {})", level, ALL, ALL.stream().noneMatch(p -> p.getLevel() >= level)); + this.level = level; + this.modID = modID; + } + + @Override + public String toString() { + return "Patch{" + + "level=" + getModID() + ":" + getLevel() + + '}'; + } + + final public int getLevel() { + return level; + } + + /** + * The Mod-ID that registered this Patch + * @return The ID + */ + final public String getModID() { + return modID; + } + + + /** + * Return block data fixes. Fixes will be applied on world load if current patch-level for + * the linked mod is lower than the {@link #level}. + * + * The default implementation of this method returns an empty map. + * + * @return The returned Map should contain the replacements. All occurences of the + * {@code KeySet} are replaced with the associated value. + */ + public Map getIDReplacements(){ + return new HashMap(); + } + + /** + * Generates ready to use data for all currently registered patches. The list of + * patches is selected by the current patch-level of the world. + * + * A {@link #Patch} with a given {@link #level} is only included if the patch-level of the + * world is less + * @return a new {@link MigrationData} Object. + */ + static MigrationData createMigrationData(PathConfig config){ + return new MigrationData(config); + } + + static class MigrationData{ + final Set mods; + final Map idReplacements; + private final PathConfig config; + + private MigrationData(PathConfig config){ + this.config = config; + + this.mods = Collections.unmodifiableSet(Patch.getALL().stream().map(p -> p.modID).collect(Collectors.toSet())); + + HashMap replacements = new HashMap(); + for(String modID : mods){ + Patch.getALL().stream().filter(p -> p.modID.equals(modID)).forEach(patch -> { + if (currentPatchLevel(modID) < patch.level) { + replacements.putAll(patch.getIDReplacements()); + LOGGER.info("Applying " + patch); + } else { + LOGGER.info("Ignoring " + patch); + } + }); + } + + this.idReplacements = Collections.unmodifiableMap(replacements); + } + + final public void markApplied(){ + for(String modID : mods){ + LOGGER.info("Updating Patch-Level for '{}' from {} to {} -> {}", + modID, + currentPatchLevel(modID), + Patch.maxPatchLevel(modID), + config.setInt(Configs.MAIN_PATCH_CATEGORY, modID, Patch.maxPatchLevel(modID)) + ); + } + + config.saveChanges(); + } + + public int currentPatchLevel(@NotNull String modID){ + return config.getInt(Configs.MAIN_PATCH_CATEGORY, modID, 0); + } + + public boolean hasAnyFixes(){ + return idReplacements.size()>0; + } + + public boolean replaceStringFromIDs(@NotNull CompoundTag tag, @NotNull String key){ + if (!tag.contains(key)) return false; + + final String val = tag.getString(key); + final String replace = idReplacements.get(val); + + if (replace != null){ + LOGGER.warning("Replacing ID '{}' with '{}'.", val, replace); + tag.putString(key, replace); + return true; + } + + return false; + } + } + } +} diff --git a/src/main/java/ru/bclib/config/Configs.java b/src/main/java/ru/bclib/config/Configs.java index c847a620..bbfc86b2 100644 --- a/src/main/java/ru/bclib/config/Configs.java +++ b/src/main/java/ru/bclib/config/Configs.java @@ -3,9 +3,13 @@ package ru.bclib.config; import ru.bclib.BCLib; public class Configs { + public static final PathConfig MAIN_CONFIG = new PathConfig(BCLib.MOD_ID, "main"); + public static final String MAIN_PATCH_CATEGORY = "patches"; + public static final PathConfig RECIPE_CONFIG = new PathConfig(BCLib.MOD_ID, "recipes"); public static void save() { + MAIN_CONFIG.saveChanges(); RECIPE_CONFIG.saveChanges(); } } From 5d2bb2c66c51dabf51ef4c9ec985a4785d4ee53e Mon Sep 17 00:00:00 2001 From: Frank Bauer Date: Mon, 19 Jul 2021 19:54:37 +0200 Subject: [PATCH 5/5] Call new DataFixer-API --- .../ru/bclib/mixin/common/ServerLevelMixin.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/ru/bclib/mixin/common/ServerLevelMixin.java b/src/main/java/ru/bclib/mixin/common/ServerLevelMixin.java index af47e301..cffd3b7a 100644 --- a/src/main/java/ru/bclib/mixin/common/ServerLevelMixin.java +++ b/src/main/java/ru/bclib/mixin/common/ServerLevelMixin.java @@ -1,5 +1,10 @@ package ru.bclib.mixin.common; +import java.io.File; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.Supplier; + import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; @@ -16,14 +21,11 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import ru.bclib.BCLib; import ru.bclib.api.BiomeAPI; -import ru.bclib.api.DataFixerAPI; +import ru.bclib.api.DataFixerAPI2; import ru.bclib.api.WorldDataAPI; - -import java.io.File; -import java.util.List; -import java.util.concurrent.Executor; -import java.util.function.Supplier; +import ru.bclib.config.SessionConfig; @Mixin(ServerLevel.class) public abstract class ServerLevelMixin extends Level { @@ -49,7 +51,8 @@ public abstract class ServerLevelMixin extends Level { dir = dir.getParentFile(); } - DataFixerAPI.fixData(dir); + //DataFixerAPI.fixData(dir); + DataFixerAPI2.fixData(new SessionConfig(BCLib.MOD_ID, "patches", session, world)); WorldDataAPI.load(new File(dir, "data")); } }