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(); } /** * Return a {@link PatchFunction} that is called with the content of level.dat. *

* The function needs to return {@code true}, if changes were made to the data. * If an error occurs, the method should throw a {@link PatchDidiFailException} * * The default implementation of this method returns null. * * @return {@code true} if changes were applied and we need to save the data */ public PatchFunction getLevelDatPatcher() { return null; } /** * Return a {@link PatchFunction} that is called with the content from the * {@link ru.bclib.api.WorldDataAPI} for this Mod. * The function needs to return {@code true}, if changes were made to the data. * If an error occurs, the method should throw a {@link PatchDidiFailException} * * The default implementation of this method returns null. * * @return {@code true} if changes were applied and we need to save the data */ public PatchFunction getWorldDataPatcher() { return null; } /** * 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); } /** * Returns a list of paths,where your mod may IDs in your {@link ru.bclib.api.WorldDataAPI}-File. *

* {@link DataFixerAPI} will use information from the latest patch that returns a non-null-result. This list is used * to automatically fix changed IDs from all active patches (see {@link Patch#getIDReplacements()} *

* The end of the path can either be a {@link net.minecraft.nbt.StringTag}, a {@link net.minecraft.nbt.ListTag} or * a {@link CompoundTag}. If the Path contains a non-leaf {@link net.minecraft.nbt.ListTag}, all members of that * list will be processed. For example: *

	 *     - global +
	 *              | - key (String)
	 *              | - items (List) +
	 *                               | - { id (String) }
	 *                               | - { id (String) }
	 * 
* The path global.items.id will fix all id-entries in the items-list, while the path * global.key will only fix the key-entry. *

* if the leaf-entry (= the last part of the path, which would be items in global.items) is a * {@link CompoundTag}, the system will fix any id entry. If the {@link CompoundTag} contains an item * or tag.BlockEntityTag entry, the system will recursivley continue with those. If an items * or inventory-{@link net.minecraft.nbt.ListTag} was found, the system will continue recursivley with * every item of that list. *

* if the leaf-entry is a {@link net.minecraft.nbt.ListTag}, it is handle the same as a child items entry * of a {@link CompoundTag}. * * @return {@code null} if nothing changes or a list of Paths in your {@link ru.bclib.api.WorldDataAPI}-File. * Paths are dot-seperated (see {@link ru.bclib.api.WorldDataAPI#getCompoundTag(String, String)}). */ public List getWorldDataIDPaths() { return null; } }