diff --git a/src/main/java/ru/bclib/BCLib.java b/src/main/java/ru/bclib/BCLib.java index e0936be0..d3174cae 100644 --- a/src/main/java/ru/bclib/BCLib.java +++ b/src/main/java/ru/bclib/BCLib.java @@ -5,6 +5,7 @@ import net.fabricmc.api.ModInitializer; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.resources.ResourceLocation; import ru.bclib.api.TagAPI; +import ru.bclib.api.WorldDataAPI; import ru.bclib.config.Configs; import ru.bclib.recipes.CraftingRecipes; import ru.bclib.registry.BaseBlockEntities; @@ -23,6 +24,7 @@ public class BCLib implements ModInitializer { BCLSurfaceBuilders.register(); TagAPI.init(); CraftingRecipes.init(); + WorldDataAPI.registerModCache(MOD_ID); Configs.save(); } diff --git a/src/main/java/ru/bclib/api/DataFixerAPI.java b/src/main/java/ru/bclib/api/DataFixerAPI.java deleted file mode 100644 index e2eaff5b..00000000 --- a/src/main/java/ru/bclib/api/DataFixerAPI.java +++ /dev/null @@ -1,152 +0,0 @@ -package ru.bclib.api; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import net.fabricmc.loader.api.FabricLoader; -import net.fabricmc.loader.api.ModContainer; -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 java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.util.Collection; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -public class DataFixerAPI { - private static final Map REPLACEMENT = Maps.newHashMap(); - private static final Map FIX_VERSIONS = Maps.newHashMap(); - - public static void fixData(File dir) { - REPLACEMENT.clear(); // API is not finished yet! - if (REPLACEMENT.isEmpty()) { - return; - } - - boolean shoudFix = false; - Collection mods = FabricLoader.getInstance().getAllMods(); - for (ModContainer mod : mods) { - String name = mod.getMetadata().getId(); - int preVersion = WorldDataAPI.getIntModVersion(name); - int version = getModVersion(mod.getMetadata().getVersion().toString()); - if (version > preVersion) { - int fixVersion = FIX_VERSIONS.getOrDefault(name, version); - shoudFix |= fixVersion < version && fixVersion >= preVersion; - } - } - ; - if (!shoudFix) { - return; - } - - List regions = getAllRegions(dir, null); - regions.parallelStream().forEach((file) -> { - try { - System.out.println("Fixing " + 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); - input.close(); - 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); - String name = blockTagCompound.getString("Name"); - String replace = REPLACEMENT.get(name); - if (replace != null) { - blockTagCompound.putString("Name", replace); - changed[0] = true; - } - }); - }); - if (changed[0]) { - System.out.println("Write!"); - DataOutputStream output = region.getChunkDataOutputStream(pos); - NbtIo.write(root, output); - output.close(); - } - } - } - } - region.close(); - } - catch (Exception e) { - e.printStackTrace(); - } - }); - } - - /** - * Register block data fix. Fix will be applied on world load if current mod version will be newer than specified one. - * - * @param modID - {@link String} mod id; - * @param modVersion - {@link String} mod version, should be in format: %d.%d.%d - * @param result - {@link String} new block name; - * @param names - array of {@link String}, old block names to convert. - */ - protected static void addFix(String modID, String modVersion, String result, String... names) { - FIX_VERSIONS.put(modID, getModVersion(modVersion)); - for (String name : names) { - REPLACEMENT.put(name, result); - } - } - - private static List getAllRegions(File dir, List list) { - if (list == null) { - list = Lists.newArrayList(); - } - for (File file : dir.listFiles()) { - if (file.isDirectory()) { - getAllRegions(file, list); - } - else if (file.isFile() && file.getName().endsWith(".mca")) { - list.add(file); - } - } - return list; - } - - /** - * Get mod version from string. String should be in format: %d.%d.%d - * - * @param version - {@link String} mod version. - * @return int mod version. - */ - public static int getModVersion(String version) { - if (version.isEmpty()) { - return 0; - } - try { - String[] values = version.split("\\."); - return Integer.parseInt(values[0]) << 12 | Integer.parseInt(values[1]) << 6 | Integer.parseInt(values[2]); - } - catch (Exception e) { - return 0; - } - } - - /** - * Get mod version from integer. String will be in format %d.%d.%d - * - * @param version - mod version in integer form. - * @return {@link String} mod version. - */ - public static String getModVersion(int version) { - int a = (version >> 12) & 63; - int b = (version >> 6) & 63; - int c = version & 63; - return String.format(Locale.ROOT, "%d.%d.%d", a, b, c); - } -} diff --git a/src/main/java/ru/bclib/api/DataFixerAPI2.java b/src/main/java/ru/bclib/api/DataFixerAPI2.java deleted file mode 100644 index fa660ead..00000000 --- a/src/main/java/ru/bclib/api/DataFixerAPI2.java +++ /dev/null @@ -1,299 +0,0 @@ -package ru.bclib.api; - -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 org.jetbrains.annotations.NotNull; -import ru.bclib.BCLib; -import ru.bclib.config.Configs; -import ru.bclib.config.PathConfig; -import ru.bclib.config.SessionConfig; -import ru.bclib.util.Logger; - -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; - -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/api/WorldDataAPI.java b/src/main/java/ru/bclib/api/WorldDataAPI.java index 842699c5..1ce22f35 100644 --- a/src/main/java/ru/bclib/api/WorldDataAPI.java +++ b/src/main/java/ru/bclib/api/WorldDataAPI.java @@ -7,6 +7,7 @@ import net.fabricmc.loader.api.ModContainer; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtIo; import ru.bclib.BCLib; +import ru.bclib.api.datafixer.DataFixerAPI; import java.io.File; import java.io.IOException; @@ -24,7 +25,6 @@ public class WorldDataAPI { MODS.stream().parallel().forEach(modID -> { File file = new File(dataDir, modID + ".nbt"); CompoundTag root = new CompoundTag(); - TAGS.put(modID, root); if (file.exists()) { try { root = NbtIo.readCompressed(file); @@ -38,7 +38,7 @@ public class WorldDataAPI { if (optional.isPresent()) { ModContainer modContainer = optional.get(); if (BCLib.isDevEnvironment()) { - root.putString("version", "63.63.63"); + root.putString("version", "255.255.9999"); } else { root.putString("version", modContainer.getMetadata().getVersion().toString()); @@ -46,6 +46,8 @@ public class WorldDataAPI { saveFile(modID); } } + + TAGS.put(modID, root); }); } diff --git a/src/main/java/ru/bclib/api/datafixer/DataFixerAPI.java b/src/main/java/ru/bclib/api/datafixer/DataFixerAPI.java new file mode 100644 index 00000000..74eae701 --- /dev/null +++ b/src/main/java/ru/bclib/api/datafixer/DataFixerAPI.java @@ -0,0 +1,183 @@ +package ru.bclib.api.datafixer; + +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.api.WorldDataAPI; +import ru.bclib.config.Configs; +import ru.bclib.util.Logger; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DataFixerAPI { + static final Logger LOGGER = new Logger("DataFixerAPI"); + + public static void fixData(File dir) { + if (!Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_PATCH_CATEGORY, "applyPatches", true)) { + LOGGER.info("World Patches are disabled"); + return; + } + + final CompoundTag patchConfig = WorldDataAPI.getCompoundTag(BCLib.MOD_ID, Configs.MAIN_PATCH_CATEGORY); + MigrationProfile data = Patch.createMigrationData(patchConfig); + if (!data.hasAnyFixes()) { + LOGGER.info("Everything up to date"); + return; + } + + List regions = getAllRegions(dir, null); + boolean[] allOk = {true}; + 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) { + allOk[0] = false; + e.printStackTrace(); + } + }); + + if (allOk[0]) { + data.markApplied(); + WorldDataAPI.saveFile(BCLib.MOD_ID); + } + } + + static CompoundTag patchConfTag = null; + static CompoundTag getPatchData(){ + if (patchConfTag==null) { + patchConfTag = WorldDataAPI.getCompoundTag(BCLib.MOD_ID, Configs.MAIN_PATCH_CATEGORY); + } + return patchConfTag; + } + + private static boolean fixItemArrayWithID(ListTag items, boolean[] changed, MigrationProfile 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; + } + + /** + * register a new Patch + * + * @param patch A #Supplier that will instantiate the new Patch Object + */ + public static void registerPatch(Supplier patch) { + Patch.getALL().add(patch.get()); + } + + /** + * Get mod version from string. String should be in format: %d.%d.%d + * + * @param version - {@link String} mod version. + * @return int mod version. + */ + public static int getModVersion(String version) { + if (version.isEmpty()) { + return 0; + } + try { + int res = 0; + final String semanticVersionPattern = "(\\d+)\\.(\\d+)\\.(\\d+)\\D*"; + final Matcher matcher = Pattern.compile(semanticVersionPattern) + .matcher(version); + if (matcher.find()) { + if (matcher.groupCount() > 0) res = (Integer.parseInt(matcher.group(1)) & 0xFF) << 22; + if (matcher.groupCount() > 1) res |= (Integer.parseInt(matcher.group(2)) & 0xFF) << 14; + if (matcher.groupCount() > 2) res |= Integer.parseInt(matcher.group(3)) & 0x3FFF; + } + + return res; + } + catch (Exception e) { + return 0; + } + } + + /** + * Get mod version from integer. String will be in format %d.%d.%d + * + * @param version - mod version in integer form. + * @return {@link String} mod version. + */ + public static String getModVersion(int version) { + int a = (version >> 22) & 0xFF; + int b = (version >> 14) & 0xFF; + int c = version & 0x3FFF; + return String.format(Locale.ROOT, "%d.%d.%d", a, b, c); + } + +} diff --git a/src/main/java/ru/bclib/api/datafixer/MigrationProfile.java b/src/main/java/ru/bclib/api/datafixer/MigrationProfile.java new file mode 100644 index 00000000..644b9d6c --- /dev/null +++ b/src/main/java/ru/bclib/api/datafixer/MigrationProfile.java @@ -0,0 +1,78 @@ +package ru.bclib.api.datafixer; + +import net.minecraft.nbt.CompoundTag; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +class MigrationProfile { + final Set mods; + final Map idReplacements; + private final CompoundTag config; + + MigrationProfile(CompoundTag 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()); + DataFixerAPI.LOGGER.info("Applying " + patch); + } + else { + DataFixerAPI.LOGGER.info("Ignoring " + patch); + } + }); + } + + this.idReplacements = Collections.unmodifiableMap(replacements); + } + + final public void markApplied() { + for (String modID : mods) { + DataFixerAPI.LOGGER.info("Updating Patch-Level for '{}' from {} to {}", modID, currentPatchLevel(modID), Patch.maxPatchLevel(modID)); + config.putString(modID, Patch.maxPatchVersion(modID)); + } + } + + public String currentPatchVersion(@NotNull String modID) { + if (!config.contains(modID)) return "0.0.0"; + return config.getString(modID); + } + + public int currentPatchLevel(@NotNull String modID) { + return DataFixerAPI.getModVersion(currentPatchVersion(modID)); + } + + 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) { + DataFixerAPI.LOGGER.warning("Replacing ID '{}' with '{}'.", val, replace); + tag.putString(key, replace); + return true; + } + + return false; + } +} diff --git a/src/main/java/ru/bclib/api/datafixer/Patch.java b/src/main/java/ru/bclib/api/datafixer/Patch.java new file mode 100644 index 00000000..1be18145 --- /dev/null +++ b/src/main/java/ru/bclib/api/datafixer/Patch.java @@ -0,0 +1,135 @@ +package ru.bclib.api.datafixer; + +import net.minecraft.nbt.CompoundTag; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public abstract class Patch { + private static List ALL = new ArrayList<>(10); + + /** + * The Patch-Level derived from {@link #version} + */ + public final int level; + + /** + * The Patch-Version string + */ + public final String version; + + /** + * The Mod-ID that registered this Patch + */ + + @NotNull + public final String modID; + + static List getALL() { + return ALL; + } + + /** + * Returns the highest Patch-Version that is available for the given mod. If no patches were + * registerd for the mod, this will return 0.0.0 + * + * @param modID The ID of the mod you want to query + * @return The highest Patch-Version that was found + */ + public static String maxPatchVersion(@NotNull String modID) { + return ALL.stream() + .filter(p -> p.modID + .equals(modID)) + .map(p -> p.version) + .reduce((p, c) -> c) + .orElse("0.0.0"); + } + + /** + * 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.modID + .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 version The mod-version that introduces the patch. This needs Semantic-Version String + * like x.x.x. Developers are responsible for registering their patches in the correct + * order (with increasing versions). You are not allowed to register a new + * Patch with a version lower or equal than + * {@link Patch#maxPatchVersion(String)} + */ + protected Patch(@NotNull String modID, String version) { + //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 (version == null || "".equals(version)) { + throw new RuntimeException("Invalid Mod-Version"); + } + + this.version = version; + this.level = DataFixerAPI.getModVersion(version); + if (!ALL.stream() + .filter(p -> p.modID + .equals(modID)) + .noneMatch(p -> p.level >= this.level) || this.level <= 0) { + throw new RuntimeException("[INTERNAL ERROR] Patch-levels need to be created in ascending order beginning with 1."); + } + + this.modID = modID; + } + + @Override + public String toString() { + return "Patch{" + modID + ':' +version + ':' + level + '}'; + } + + + /** + * 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 + * @param config The current patch-level configuration + * @return a new {@link MigrationProfile} Object. + */ + static MigrationProfile createMigrationData(CompoundTag config) { + return new MigrationProfile(config); + } + +} diff --git a/src/main/java/ru/bclib/config/SessionConfig.java b/src/main/java/ru/bclib/config/SessionConfig.java index 3c311417..8ceff397 100644 --- a/src/main/java/ru/bclib/config/SessionConfig.java +++ b/src/main/java/ru/bclib/config/SessionConfig.java @@ -18,8 +18,8 @@ public class SessionConfig extends PathConfig { 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)); + super(modID, group, new File(getWorldFolder(session, world), BCLib.MOD_ID+"-config")); - this.levelFolder = new File(getWorldFolder(session, world), BCLib.MOD_ID); + this.levelFolder = new File(getWorldFolder(session, world), BCLib.MOD_ID+"-config"); } } diff --git a/src/main/java/ru/bclib/mixin/common/ServerLevelMixin.java b/src/main/java/ru/bclib/mixin/common/ServerLevelMixin.java index eaf365c0..58aec99b 100644 --- a/src/main/java/ru/bclib/mixin/common/ServerLevelMixin.java +++ b/src/main/java/ru/bclib/mixin/common/ServerLevelMixin.java @@ -16,11 +16,9 @@ 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.DataFixerAPI2; import ru.bclib.api.WorldDataAPI; -import ru.bclib.config.SessionConfig; +import ru.bclib.api.datafixer.DataFixerAPI; import java.io.File; import java.util.List; @@ -51,8 +49,7 @@ public abstract class ServerLevelMixin extends Level { dir = dir.getParentFile(); } - //DataFixerAPI.fixData(dir); - DataFixerAPI2.fixData(new SessionConfig(BCLib.MOD_ID, "patches", session, world)); WorldDataAPI.load(new File(dir, "data")); + DataFixerAPI.fixData(dir); } }