*WIP*: Merge commit 'ce4cb8974f' into 1.18

This commit is contained in:
Frank 2021-11-13 19:30:01 +01:00
commit c6a7a1d4f7
76 changed files with 2754 additions and 738 deletions

View file

@ -1,28 +1,42 @@
package ru.bclib.api;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.biome.v1.NetherBiomes;
import net.fabricmc.fabric.impl.biome.NetherBiomeData;
import net.fabricmc.fabric.impl.biome.TheEndBiomeData;
import net.fabricmc.fabric.impl.biome.InternalBiomeData;
import net.fabricmc.fabric.mixin.biome.modification.GenerationSettingsAccessor;
import net.minecraft.client.Minecraft;
import net.minecraft.core.Registry;
import net.minecraft.data.BuiltinRegistries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biome.ClimateParameters;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.biome.Climate;
import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature;
import org.jetbrains.annotations.Nullable;
import ru.bclib.util.MHelper;
import ru.bclib.world.biomes.BCLBiome;
import ru.bclib.world.biomes.FabricBiomesData;
import ru.bclib.world.features.BCLFeature;
import ru.bclib.world.generator.BiomePicker;
import ru.bclib.world.structures.BCLStructureFeature;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
public class BiomeAPI {
/**
@ -39,6 +53,9 @@ public class BiomeAPI {
private static final Map<Biome, BCLBiome> CLIENT = Maps.newHashMap();
private static Registry<Biome> biomeRegistry;
private static final Map<ResourceKey, List<BiConsumer<ResourceLocation, Biome>>> MODIFICATIONS = Maps.newHashMap();
private static final Set<ResourceLocation> MODIFIED_BIOMES = Sets.newHashSet();
public static final BCLBiome NETHER_WASTES_BIOME = registerNetherBiome(getFromRegistry(Biomes.NETHER_WASTES));
public static final BCLBiome CRIMSON_FOREST_BIOME = registerNetherBiome(getFromRegistry(Biomes.CRIMSON_FOREST));
public static final BCLBiome WARPED_FOREST_BIOME = registerNetherBiome(getFromRegistry(Biomes.WARPED_FOREST));
@ -325,4 +342,143 @@ public class BiomeAPI {
private static boolean pickerHasBiome(BiomePicker picker, ResourceLocation key) {
return picker.getBiomes().stream().filter(biome -> biome.getID().equals(key)).findFirst().isPresent();
}
/**
* Registers new biome modification for specified dimension. Will work both for mod and datapack biomes.
* @param dimensionID {@link ResourceLocation} dimension ID, example: Level.OVERWORLD or "minecraft:overworld".
* @param modification {@link BiConsumer} with {@link ResourceKey} biome ID and {@link Biome} parameters.
*/
public static void registerBiomeModification(ResourceKey dimensionID, BiConsumer<ResourceLocation, Biome> modification) {
List<BiConsumer<ResourceLocation, Biome>> modifications = MODIFICATIONS.get(dimensionID);
if (modifications == null) {
modifications = Lists.newArrayList();
MODIFICATIONS.put(dimensionID, modifications);
}
modifications.add(modification);
}
/**
* Registers new biome modification for the Overworld. Will work both for mod and datapack biomes.
* @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
*/
public static void registerOverworldBiomeModification(BiConsumer<ResourceLocation, Biome> modification) {
registerBiomeModification(Level.OVERWORLD, modification);
}
/**
* Registers new biome modification for the Nether. Will work both for mod and datapack biomes.
* @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
*/
public static void registerNetherBiomeModification(BiConsumer<ResourceLocation, Biome> modification) {
registerBiomeModification(Level.NETHER, modification);
}
/**
* Registers new biome modification for the End. Will work both for mod and datapack biomes.
* @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
*/
public static void registerEndBiomeModification(BiConsumer<ResourceLocation, Biome> modification) {
registerBiomeModification(Level.END, modification);
}
/**
* Will apply biome modifications to world, internal usage only.
* @param level
*/
public static void applyModifications(ServerLevel level) {
List<BiConsumer<ResourceLocation, Biome>> modifications = MODIFICATIONS.get(level.dimension());
if (modifications == null) {
return;
}
BiomeSource source = level.getChunkSource().getGenerator().getBiomeSource();
List<Biome> biomes = source.possibleBiomes();
biomes.forEach(biome -> {
ResourceLocation biomeID = getBiomeID(biome);
boolean modify = isDatapackBiome(biomeID);
if (!modify && !MODIFIED_BIOMES.contains(biomeID)) {
MODIFIED_BIOMES.add(biomeID);
modify = true;
}
if (modify) {
modifications.forEach(consumer -> {
consumer.accept(biomeID, biome);
});
}
});
}
/**
* Adds new features to existing biome.
* @param biome {@link Biome} to add features in.
* @param feature {@link ConfiguredFeature} to add.
* @param step a {@link Decoration} step for the feature.
*/
public static void addBiomeFeature(Biome biome, ConfiguredFeature feature, Decoration step) {
GenerationSettingsAccessor accessor = (GenerationSettingsAccessor) biome.getGenerationSettings();
List<List<Supplier<ConfiguredFeature<?, ?>>>> biomeFeatures = getMutableList(accessor.fabric_getFeatures());
int index = step.ordinal();
if (biomeFeatures.size() < index) {
for (int i = biomeFeatures.size(); i <= index; i++) {
biomeFeatures.add(Lists.newArrayList());
}
}
List<Supplier<ConfiguredFeature<?, ?>>> list = getMutableList(biomeFeatures.get(index));
list.add(() -> feature);
accessor.fabric_setFeatures(biomeFeatures);
}
/**
* Adds new features to existing biome.
* @param biome {@link Biome} to add features in.
* @param features array of {@link BCLFeature} to add.
*/
public static void addBiomeFeatures(Biome biome, BCLFeature... features) {
GenerationSettingsAccessor accessor = (GenerationSettingsAccessor) biome.getGenerationSettings();
List<List<Supplier<ConfiguredFeature<?, ?>>>> biomeFeatures = getMutableList(accessor.fabric_getFeatures());
for (BCLFeature feature: features) {
int index = feature.getFeatureStep().ordinal();
if (biomeFeatures.size() < index) {
for (int i = biomeFeatures.size(); i <= index; i++) {
biomeFeatures.add(Lists.newArrayList());
}
}
List<Supplier<ConfiguredFeature<?, ?>>> list = getMutableList(biomeFeatures.get(index));
list.add(feature::getFeatureConfigured);
}
accessor.fabric_setFeatures(biomeFeatures);
}
/**
* Adds new structure feature to existing biome.
* @param biome {@link Biome} to add structure feature in.
* @param structure {@link ConfiguredStructureFeature} to add.
*/
public static void addBiomeStructure(Biome biome, ConfiguredStructureFeature structure) {
GenerationSettingsAccessor accessor = (GenerationSettingsAccessor) biome.getGenerationSettings();
List<Supplier<ConfiguredStructureFeature<?, ?>>> biomeStructures = getMutableList(accessor.fabric_getStructureFeatures());
biomeStructures.add(() -> structure);
accessor.fabric_setStructureFeatures(biomeStructures);
}
/**
* Adds new structure features to existing biome.
* @param biome {@link Biome} to add structure features in.
* @param structures array of {@link BCLStructureFeature} to add.
*/
public static void addBiomeStructures(Biome biome, BCLStructureFeature... structures) {
GenerationSettingsAccessor accessor = (GenerationSettingsAccessor) biome.getGenerationSettings();
List<Supplier<ConfiguredStructureFeature<?, ?>>> biomeStructures = getMutableList(accessor.fabric_getStructureFeatures());
for (BCLStructureFeature structure: structures) {
biomeStructures.add(structure::getFeatureConfigured);
}
accessor.fabric_setStructureFeatures(biomeStructures);
}
private static <T extends Object> List<T> getMutableList(List<T> input) {
if (input instanceof ImmutableList) {
return Lists.newArrayList(input);
}
return input;
}
}

View file

@ -34,9 +34,16 @@ public class TagAPI {
public static final Tag.Named<Block> BLOCK_END_GROUND = makeBlockTag(BCLib.MOD_ID, "end_ground");
public static final Tag.Named<Block> BLOCK_CHEST = makeCommonBlockTag("chest");
public static final Tag.Named<Block> BLOCK_WOODEN_CHEST = makeCommonBlockTag("wooden_chests");
public static final Tag.Named<Block> BLOCK_BARREL = makeCommonBlockTag("barrel");
public static final Tag.Named<Block> BLOCK_WOODEN_BARREL = makeCommonBlockTag("wooden_barrels");
public static final Tag.Named<Block> BLOCK_END_STONES = makeCommonBlockTag("end_stones");
public static final Tag.Named<Block> BLOCK_NETHER_STONES = makeCommonBlockTag("nether_stones");
public static final Tag.Named<Block> BLOCK_WORKBENCHES = makeCommonBlockTag("workbenches");
public static final Tag.Named<Block> BLOCK_NETHER_PORTAL_FRAME = makeCommonBlockTag("nether_pframe");
public static final Tag.Named<Block> BLOCK_WORKBENCHES = makeCommonBlockTag("workbench");
public static final Tag.Named<Block> BLOCK_SAPLINGS = makeCommonBlockTag("saplings");
public static final Tag.Named<Block> BLOCK_LEAVES = makeCommonBlockTag("leaves");
public static final Tag.Named<Block> BLOCK_IMMOBILE = makeCommonBlockTag("immobile");
public static final Tag.Named<Block> BLOCK_DRAGON_IMMUNE = getMCBlockTag("dragon_immune");
@ -47,11 +54,19 @@ public class TagAPI {
// Item Tags
public static final Tag.Named<Item> ITEM_CHEST = makeCommonItemTag("chest");
public static final Tag.Named<Item> ITEM_WOODEN_CHEST = makeCommonItemTag("wooden_chests");
public static final Tag.Named<Item> ITEM_BARREL = makeCommonItemTag("barrel");
public static final Tag.Named<Item> ITEM_WOODEN_BARREL = makeCommonItemTag("wooden_barrels");
public static final Tag.Named<Item> ITEM_IRON_INGOTS = makeCommonItemTag("iron_ingots");
public static final Tag.Named<Item> ITEM_FURNACES = makeCommonItemTag("furnaces");
public static final Tag.Named<Item> ITEM_WORKBENCHES = makeCommonItemTag("workbenches");
public static final Tag.Named<Item> ITEM_WORKBENCHES = makeCommonItemTag("workbench");
public final static Tag.Named<Item> ITEM_HAMMERS = makeCommonItemTag("hammers");
public static final Tag.Named<Item> ITEM_SAPLINGS = makeCommonItemTag("saplings");
public static final Tag.Named<Item> ITEM_LEAVES = makeCommonItemTag("leaves");
public static final Tag.Named<Item> ITEM_SHEARS = getMCItemTag("shears");
public static final Tag.Named<Item> ITEM_COMMON_SHEARS = makeCommonItemTag("shears");
/**
* Get or create {@link Tag.Named}.
*
@ -122,6 +137,18 @@ public class TagAPI {
return tag == null ? (Named<Block>) TagFactory.BLOCK.create(id): (Named<Block>) tag;
}
/**
* Get or create Minecraft {@link Item} {@link Tag.Named}.
*
* @param name - {@link String} tag name.
* @return {@link Item} {@link Tag.Named}.
*/
public static Tag.Named<Item> getMCItemTag(String name) {
ResourceLocation id = new ResourceLocation(name);
Tag<Item> tag = ItemTags.getAllTags().getTag(id);
return tag == null ? (Named<Item>) TagRegistry.item(id) : (Named<Item>) tag;
}
/**
* Adds {@link Block} to NETHER_GROUND and GEN_TERRAIN tags to process it properly in terrain generators and block logic.
*

View file

@ -135,7 +135,7 @@ public class AutoSync {
}
private static boolean didRegisterAdditionalMods = false;
//we call this from HelloClient on the Srver to prepare transfer
//we call this from HelloClient on the Server to prepare transfer
protected static void loadSyncFolder() {
if (Configs.SERVER_CONFIG.isOfferingFiles()) {
syncFolderDescriptions.forEach(desc -> desc.loadCache());

View file

@ -4,10 +4,10 @@ import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.minecraft.client.Minecraft;
import net.minecraft.util.ProgressListener;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.ProgressListener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import ru.bclib.BCLib;

View file

@ -16,6 +16,7 @@ import ru.bclib.api.dataexchange.DataHandlerDescriptor;
import ru.bclib.api.dataexchange.handler.autosync.AutoSyncID.WithContentOverride;
import ru.bclib.api.dataexchange.handler.autosync.SyncFolderDescriptor.SubFile;
import ru.bclib.config.Configs;
import ru.bclib.config.ServerConfig;
import ru.bclib.gui.screens.ModListScreen;
import ru.bclib.gui.screens.ProgressScreen;
import ru.bclib.gui.screens.SyncFilesScreen;
@ -91,6 +92,11 @@ public class HelloClient extends DataHandler.FromServer {
.collect(Collectors.toList())
);
}
mods = mods
.stream()
.filter(entry -> !Configs.SERVER_CONFIG.get(ServerConfig.EXCLUDED_MODS).contains(entry))
.collect(Collectors.toList());
//write Plugin Versions
buf.writeInt(mods.size());
@ -394,6 +400,9 @@ public class HelloClient extends DataHandler.FromServer {
requestBCLibDownload();
this.onCloseSyncFilesScreen();
} else {
Minecraft.getInstance()
.setScreen(null);
}
}));
}

View file

@ -25,6 +25,8 @@ import ru.bclib.api.WorldDataAPI;
import ru.bclib.config.Configs;
import ru.bclib.gui.screens.AtomicProgressListener;
import ru.bclib.gui.screens.ConfirmFixScreen;
import ru.bclib.gui.screens.LevelFixErrorScreen;
import ru.bclib.gui.screens.LevelFixErrorScreen.Listener;
import ru.bclib.gui.screens.ProgressScreen;
import ru.bclib.util.Logger;
@ -39,6 +41,7 @@ import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.zip.ZipException;
/**
* API to manage Patches that need to get applied to a world
@ -47,6 +50,24 @@ public class DataFixerAPI {
static final Logger LOGGER = new Logger("DataFixerAPI");
static class State {
public boolean didFail = false;
protected ArrayList<String> errors = new ArrayList<>();
public void addError(String s){
errors.add(s);
}
public boolean hasError(){
return errors.size()>0;
}
public String getErrorMessage(){
return errors.stream().reduce("", (a, b) -> a + " - " + b + "\n");
}
public String[] getErrorMessages(){
String[] res = new String[errors.size()];
return errors.toArray(res);
}
}
@FunctionalInterface
@ -200,30 +221,48 @@ public class DataFixerAPI {
progress = null;
}
Runnable runner = () -> {
Supplier<State> runner = () -> {
if (createBackup) {
progress.progressStage(new TranslatableComponent("message.bclib.datafixer.progress.waitbackup"));
EditWorldScreen.makeBackupAndShowToast(Minecraft.getInstance().getLevelSource(), levelID);
}
if (applyFixes) {
runDataFixes(dir, profile, progress);
return runDataFixes(dir, profile, progress);
}
return new State();
};
if (showUI) {
Thread fixerThread = new Thread(() -> {
runner.run();
Minecraft.getInstance().execute(() -> {
if (profile != null && showUI) {
onResume.accept(applyFixes);
}
});
final State state = runner.get();
Minecraft.getInstance()
.execute(() -> {
if (profile != null && showUI) {
//something went wrong, show the user our error
if (state.didFail || state.hasError()){
showLevelFixErrorScreen(state, (markFixed)->{
if (markFixed) {
profile.markApplied();
}
onResume.accept(applyFixes);
});
} else {
onResume.accept(applyFixes);
}
}
});
});
fixerThread.start();
} else {
runner.run();
State state = runner.get();
if (state.hasError()){
LOGGER.error("There were Errors while fixing the Level:");
LOGGER.error(state.getErrorMessage());
}
}
};
@ -240,6 +279,11 @@ public class DataFixerAPI {
}
return false;
}
@Environment(EnvType.CLIENT)
private static void showLevelFixErrorScreen(State state, Listener onContinue){
Minecraft.getInstance()
.setScreen(new LevelFixErrorScreen(Minecraft.getInstance().screen, state.getErrorMessages(), onContinue));
}
private static MigrationProfile loadProfileIfNeeded(File levelBaseDir){
if (!Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_PATCH_CATEGORY, "applyPatches", true)) {
@ -270,7 +314,7 @@ public class DataFixerAPI {
Minecraft.getInstance().setScreen(new ConfirmFixScreen((Screen) null, whenFinished::accept));
}
private static void runDataFixes(File dir, MigrationProfile profile, AtomicProgressListener progress) {
private static State runDataFixes(File dir, MigrationProfile profile, AtomicProgressListener progress) {
State state = new State();
progress.resetAtomic();
@ -295,6 +339,7 @@ public class DataFixerAPI {
profile.patchWorldData();
} catch (PatchDidiFailException e){
state.didFail = true;
state.addError("Failed fixing worldconfig (" + e.getMessage() + ")");
BCLib.LOGGER.error(e.getMessage());
}
progress.incAtomic(maxProgress);
@ -313,6 +358,8 @@ public class DataFixerAPI {
progress.incAtomic(maxProgress);
progress.stop();
return state;
}
private static void fixLevel(MigrationProfile profile, State state, File levelBaseDir) {
@ -342,15 +389,17 @@ public class DataFixerAPI {
}
catch (Exception e) {
BCLib.LOGGER.error("Failed fixing Level-Data.");
state.addError("Failed fixing Level-Data in level.dat (" + e.getMessage() + ")");
state.didFail = true;
e.printStackTrace();
}
}
private static void fixPlayer(MigrationProfile data, State state, File file) {
try {
LOGGER.info("Inspecting " + file);
CompoundTag player = NbtIo.readCompressed(file);
CompoundTag player = readNbt(file);
boolean[] changed = { false };
fixPlayerNbt(player, changed, data);
@ -361,11 +410,12 @@ public class DataFixerAPI {
}
catch (Exception e) {
BCLib.LOGGER.error("Failed fixing Player-Data.");
state.addError("Failed fixing Player-Data in " + file.getName() + " (" + e.getMessage() + ")");
state.didFail = true;
e.printStackTrace();
}
}
private static void fixPlayerNbt(CompoundTag player, boolean[] changed, MigrationProfile data) {
//Checking Inventory
ListTag inventory = player.getList("Inventory", Tag.TAG_COMPOUND);
@ -416,7 +466,7 @@ public class DataFixerAPI {
for (int z = 0; z < 32; z++) {
ChunkPos pos = new ChunkPos(x, z);
changed[0] = false;
if (region.hasChunk(pos)) {
if (region.hasChunk(pos) && !state.didFail) {
DataInputStream input = region.getChunkDataInputStream(pos);
CompoundTag root = NbtIo.read(input);
// if ((root.toString().contains("betternether:chest") || root.toString().contains("bclib:chest"))) {
@ -442,6 +492,17 @@ public class DataFixerAPI {
CompoundTag blockTagCompound = ((CompoundTag) blockTag);
changed[0] |= data.replaceStringFromIDs(blockTagCompound, "Name");
});
try {
changed[0] |= data.patchBlockState(palette, ((CompoundTag) tag).getList("BlockStates", Tag.TAG_LONG));
}
catch (PatchDidiFailException e) {
BCLib.LOGGER.error("Failed fixing BlockState in " + pos);
state.addError("Failed fixing BlockState in " + pos + " (" + e.getMessage() + ")");
state.didFail = true;
changed[0] = false;
e.printStackTrace();
}
});
if (changed[0]) {
@ -450,7 +511,6 @@ public class DataFixerAPI {
DataOutputStream output = region.getChunkDataOutputStream(pos);
NbtIo.write(root, output);
output.close();
}
}
}
@ -458,12 +518,13 @@ public class DataFixerAPI {
region.close();
}
catch (Exception e) {
BCLib.LOGGER.error("Failed fixing Player Data.");
BCLib.LOGGER.error("Failed fixing Region.");
state.addError("Failed fixing Region in " + file.getName() + " (" + e.getMessage() + ")");
state.didFail = true;
e.printStackTrace();
}
}
static CompoundTag patchConfTag = null;
static CompoundTag getPatchData(){
if (patchConfTag==null) {
@ -540,4 +601,12 @@ public class DataFixerAPI {
Patch.getALL().add(patch.get());
}
private static CompoundTag readNbt(File file) throws IOException {
try {
return NbtIo.readCompressed(file);
} catch (ZipException e){
return NbtIo.read(file);
}
}
}

View file

@ -0,0 +1,47 @@
package ru.bclib.api.datafixer;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A Patch for level.dat that is always executed no matter what Patchlevel is set in a world.
*/
public abstract class ForcedLevelPatch extends Patch {
protected ForcedLevelPatch(@NotNull String modID, String version) {
super(modID, version, true);
}
@Override
public final Map<String, String> getIDReplacements() {
return new HashMap<String, String>();
}
@Override
public final PatchFunction<CompoundTag, Boolean> getWorldDataPatcher() { return null; }
@Override
public final PatchBiFunction<ListTag, ListTag, Boolean> getBlockStatePatcher() { return null; }
@Override
public final List<String> getWorldDataIDPaths() {
return null;
}
@Override
public PatchFunction<CompoundTag, Boolean> getLevelDatPatcher() { return this::runLevelDatPatch; }
/**
* Called with the contents of level.dat in {@code root}
* @param root The contents of level.dat
* @param profile The active migration profile
* @return true, if the run did change the contents of root
*/
abstract protected Boolean runLevelDatPatch(CompoundTag root, MigrationProfile profile);
}

View file

@ -11,6 +11,7 @@ import ru.bclib.util.ModUtil;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
@ -23,6 +24,7 @@ public class MigrationProfile {
final Set<String> mods;
final Map<String, String> idReplacements;
final List<PatchFunction<CompoundTag, Boolean>> levelPatchers;
final List<PatchBiFunction<ListTag, ListTag, Boolean>> statePatchers;
final List<Patch> worldDataPatchers;
final Map<String, List<String>> worldDataIDPaths;
@ -33,7 +35,7 @@ public class MigrationProfile {
private boolean didRunPrePatch;
private Exception prePatchException;
MigrationProfile(CompoundTag config) {
MigrationProfile(CompoundTag config, boolean applyAll) {
this.config = config;
this.mods = Collections.unmodifiableSet(Patch.getALL()
@ -44,6 +46,7 @@ public class MigrationProfile {
HashMap<String, String> replacements = new HashMap<String, String>();
List<PatchFunction<CompoundTag, Boolean>> levelPatches = new LinkedList<>();
List<Patch> worldDataPatches = new LinkedList<>();
List<PatchBiFunction<ListTag, ListTag, Boolean>> statePatches = new LinkedList<>();
HashMap<String, List<String>> worldDataIDPaths = new HashMap<>();
for (String modID : mods) {
@ -54,12 +57,14 @@ public class MigrationProfile {
List<String> paths = patch.getWorldDataIDPaths();
if (paths!=null) worldDataIDPaths.put(modID, paths);
if (currentPatchLevel(modID) < patch.level) {
if (applyAll || currentPatchLevel(modID) < patch.level || patch.alwaysApply) {
replacements.putAll(patch.getIDReplacements());
if (patch.getLevelDatPatcher()!=null)
levelPatches.add(patch.getLevelDatPatcher());
if (patch.getWorldDataPatcher()!=null)
worldDataPatches.add(patch);
if (patch.getBlockStatePatcher()!=null)
statePatches.add(patch.getBlockStatePatcher());
DataFixerAPI.LOGGER.info("Applying " + patch);
}
else {
@ -72,6 +77,54 @@ public class MigrationProfile {
this.idReplacements = Collections.unmodifiableMap(replacements);
this.levelPatchers = Collections.unmodifiableList(levelPatches);
this.worldDataPatchers = Collections.unmodifiableList(worldDataPatches);
this.statePatchers = Collections.unmodifiableList(statePatches);
}
/**
* This method is supposed to be used by developers to apply id-patches to custom nbt structures. It is only
* available in Developer-Mode
*
*/
public static void fixCustomFolder(File dir){
if (!BCLib.isDevEnvironment()) return;
MigrationProfile profile = Patch.createMigrationData();
List<File> nbts = getAllNbts(dir, null);
nbts.parallelStream().forEach((file) -> {
DataFixerAPI.LOGGER.info("Loading NBT " + file);
try {
CompoundTag root = NbtIo.readCompressed(file);
boolean[] changed = {false};
if (root.contains("palette")){
ListTag items = root.getList("palette", Tag.TAG_COMPOUND);
items.forEach(inTag -> {
CompoundTag tag = (CompoundTag)inTag;
changed[0] |= profile.replaceStringFromIDs(tag, "Name");
});
}
if (changed[0]){
DataFixerAPI.LOGGER.info("Writing NBT " + file);
NbtIo.writeCompressed(root, file);
}
}
catch (IOException e) {
e.printStackTrace();
}
});
}
private static List<File> getAllNbts(File dir, List<File> list) {
if (list == null) {
list = new ArrayList<>();
}
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
getAllNbts(file, list);
} else if (file.isFile() && file.getName().endsWith(".nbt")) {
list.add(file);
}
}
return list;
}
final public CompoundTag getLevelDat(File levelBaseDir){
@ -124,12 +177,13 @@ public class MigrationProfile {
final public void markApplied() {
for (String modID : mods) {
DataFixerAPI.LOGGER.info("Updating Patch-Level for '{}' from {} to {}", modID, ModUtil.convertModVersion(currentPatchLevel(modID)), ModUtil.convertModVersion(Patch.maxPatchLevel(modID)));
config.putString(modID, Patch.maxPatchVersion(modID));
if (config!=null)
config.putString(modID, Patch.maxPatchVersion(modID));
}
}
public String currentPatchVersion(@NotNull String modID) {
if (!config.contains(modID)) return "0.0.0";
if (config==null || !config.contains(modID)) return "0.0.0";
return config.getString(modID);
}
@ -255,4 +309,12 @@ public class MigrationProfile {
}
}
}
public boolean patchBlockState(ListTag palette, ListTag states) throws PatchDidiFailException{
boolean changed = false;
for (PatchBiFunction<ListTag, ListTag, Boolean> f : statePatchers) {
changed |= f.apply(palette, states, this);
}
return changed;
}
}

View file

@ -1,6 +1,7 @@
package ru.bclib.api.datafixer;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import org.jetbrains.annotations.NotNull;
import ru.bclib.util.ModUtil;
@ -30,6 +31,11 @@ public abstract class Patch {
@NotNull
public final String modID;
/**
* This Mod is tested for each level start
*/
public final boolean alwaysApply;
static List<Patch> getALL() {
return ALL;
}
@ -81,6 +87,19 @@ public abstract class Patch {
* {@link Patch#maxPatchVersion(String)}
*/
protected Patch(@NotNull String modID, String version) {
this(modID, version, false);
}
/**
* Internal Constructor used to create patches that can allways run (no matter what patchlevel a level has)
* @param modID The ID of the Mod
* @param version The mod-version that introduces the patch. When {@Code runAllways} is set, this version will
* determine the patchlevel that is written to the level
* @param alwaysApply When true, this patch is always active, no matter the patchlevel of the world.
* This should be used sparingly and just for patches that apply to level.dat (as they only take
* effect when changes are detected). Use {@link ForcedLevelPatch} to instatiate.
*/
Patch(@NotNull String modID, String version, boolean alwaysApply) {
//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!");
@ -91,6 +110,7 @@ public abstract class Patch {
}
this.version = version;
this.alwaysApply = alwaysApply;
this.level = ModUtil.convertModVersion(version);
if (!ALL.stream()
.filter(p -> p.modID
@ -145,22 +165,46 @@ public abstract class Patch {
*/
public PatchFunction<CompoundTag, Boolean> getWorldDataPatcher() { return null; }
/**
* Return a {@link PatchBiFunction} that is called with pallette and blockstate of
* each chunk in every region. This method is called AFTER all ID replacements
* from {@link #getIDReplacements()} were applied to the pallete.
*
* The first parameter is the palette and the second is the blockstate.
*
* 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 PatchBiFunction<ListTag, ListTag, Boolean> getBlockStatePatcher() { 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.
* <p>
* 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
* @param levelBaseDir The location of the level
* @param config The current patch-level configuration*
* @return a new {@link MigrationProfile} Object.
*/
static MigrationProfile createMigrationData(CompoundTag config) {
return new MigrationProfile(config);
return new MigrationProfile(config, false);
}
/**
* This method is supposed to be used by developers to apply id-patches to custom nbt structures. It is only
* available in Developer-Mode
*
*/
static MigrationProfile createMigrationData() {
return new MigrationProfile(null, true);
}
/**
* Returns a list of paths,where your mod may IDs in your {@link ru.bclib.api.WorldDataAPI}-File.
* Returns a list of paths where your mod stores IDs in your {@link ru.bclib.api.WorldDataAPI}-File.
* <p>
* {@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()}

View file

@ -0,0 +1,6 @@
package ru.bclib.api.datafixer;
@FunctionalInterface
public interface PatchBiFunction<U, V, R> {
R apply(U t, V v, MigrationProfile profile) throws PatchDidiFailException;
}