Merge pull request #21 from quiqueck/main

Initialize new worlds as fully patched
This commit is contained in:
paulevsGitch 2021-07-27 08:51:55 +03:00 committed by GitHub
commit b10e505ed3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 102 additions and 27 deletions

View file

@ -6,6 +6,7 @@ import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer; import net.fabricmc.loader.api.ModContainer;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo; import net.minecraft.nbt.NbtIo;
import net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess;
import ru.bclib.BCLib; import ru.bclib.BCLib;
import ru.bclib.api.datafixer.DataFixerAPI; import ru.bclib.api.datafixer.DataFixerAPI;
@ -14,7 +15,15 @@ import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer;
/**
* Mod-specifix data-storage for a world.
*
* This class provides the ability for mod to store persistent data inside a world. The Storage for the world is
* currently initialized as part of the {@link DataFixerAPI} in {@link DataFixerAPI#fixData(LevelStorageAccess, boolean, Consumer)}
* or {@link DataFixerAPI#initializeWorldData(File, boolean)}
*/
public class WorldDataAPI { public class WorldDataAPI {
private static final Map<String, CompoundTag> TAGS = Maps.newHashMap(); private static final Map<String, CompoundTag> TAGS = Maps.newHashMap();
private static final List<String> MODS = Lists.newArrayList(); private static final List<String> MODS = Lists.newArrayList();
@ -104,6 +113,9 @@ public class WorldDataAPI {
*/ */
public static void saveFile(String modID) { public static void saveFile(String modID) {
try { try {
if (!dataDir.exists()){
dataDir.mkdirs();
}
NbtIo.writeCompressed(getRootTag(modID), new File(dataDir, modID + ".nbt")); NbtIo.writeCompressed(getRootTag(modID), new File(dataDir, modID + ".nbt"));
} }
catch (IOException e) { catch (IOException e) {

View file

@ -17,6 +17,8 @@ import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFile;
import net.minecraft.world.level.storage.LevelResource; import net.minecraft.world.level.storage.LevelResource;
import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess;
import org.jetbrains.annotations.NotNull;
import ru.bclib.BCLib; import ru.bclib.BCLib;
import ru.bclib.api.WorldDataAPI; import ru.bclib.api.WorldDataAPI;
import ru.bclib.config.Configs; import ru.bclib.config.Configs;
@ -31,6 +33,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -49,6 +52,29 @@ public class DataFixerAPI {
public void call(); public void call();
} }
private static boolean wrapCall(LevelStorageSource levelSource, String levelID, Function<LevelStorageAccess, Boolean> runWithLevel) {
LevelStorageSource.LevelStorageAccess levelStorageAccess;
try {
levelStorageAccess = levelSource.createAccess(levelID);
} catch (IOException e) {
BCLib.LOGGER.warning((String)"Failed to read level {} data", levelID, e);
SystemToast.onWorldAccessFailure(Minecraft.getInstance(), levelID);
Minecraft.getInstance().setScreen((Screen)null);
return true;
}
boolean returnValue = runWithLevel.apply(levelStorageAccess);
try {
levelStorageAccess.close();
} catch (IOException e) {
BCLib.LOGGER.warning((String)"Failed to unlock access to level {}", levelID, e);
}
return returnValue;
}
/** /**
* Will apply necessary Patches to the world. * Will apply necessary Patches to the world.
* *
@ -63,26 +89,7 @@ public class DataFixerAPI {
* *
*/ */
public static boolean fixData(LevelStorageSource levelSource, String levelID, boolean showUI, Consumer<Boolean> onResume) { public static boolean fixData(LevelStorageSource levelSource, String levelID, boolean showUI, Consumer<Boolean> onResume) {
return wrapCall(levelSource, levelID, (levelStorageAccess) -> fixData(levelStorageAccess, showUI, onResume));
LevelStorageSource.LevelStorageAccess levelStorageAccess;
try {
levelStorageAccess = levelSource.createAccess(levelID);
} catch (IOException e) {
BCLib.LOGGER.warning((String)"Failed to read level {} data", levelID, e);
SystemToast.onWorldAccessFailure(Minecraft.getInstance(), levelID);
Minecraft.getInstance().setScreen((Screen)null);
return true;
}
boolean cancelRun = fixData(levelStorageAccess, showUI, onResume);
try {
levelStorageAccess.close();
} catch (IOException e) {
BCLib.LOGGER.warning((String)"Failed to unlock access to level {}", levelID, e);
}
return cancelRun;
} }
/** /**
@ -98,12 +105,54 @@ public class DataFixerAPI {
*/ */
public static boolean fixData(LevelStorageSource.LevelStorageAccess levelStorageAccess, boolean showUI, Consumer<Boolean> onResume){ public static boolean fixData(LevelStorageSource.LevelStorageAccess levelStorageAccess, boolean showUI, Consumer<Boolean> onResume){
File levelPath = levelStorageAccess.getLevelPath(LevelResource.ROOT).toFile(); File levelPath = levelStorageAccess.getLevelPath(LevelResource.ROOT).toFile();
WorldDataAPI.load(new File(levelPath, "data"));
boolean newWorld = false;
if (!levelPath.exists()) {
BCLib.LOGGER.info("Creating a new World, no fixes needed");
newWorld = true;
}
initializeWorldData(levelPath, newWorld);
if (newWorld) return false;
return fixData(levelPath, levelStorageAccess.getLevelId(), showUI, onResume); return fixData(levelPath, levelStorageAccess.getLevelId(), showUI, onResume);
} }
/**
* Initializes the DataStorage for this world. If the world is new, the patch registry is initialized to the
* current versions of the plugins.
* <p>
* This implementation will create a new {@link LevelStorageAccess} and call {@link #initializeWorldData(File, boolean)}
* using the provided root path.
* @param levelSource The SourceStorage for this Minecraft instance, You can get this using
* {@code Minecraft.getInstance().getLevelSource()}
* @param levelID The ID of the Level you want to patch
* @param newWorld {@code true} if this is a fresh world
*/
public static void initializeWorldData(LevelStorageSource levelSource, String levelID, boolean newWorld) {
wrapCall(levelSource, levelID, (levelStorageAccess) -> {
initializeWorldData(levelStorageAccess.getLevelPath(LevelResource.ROOT).toFile(), newWorld);
return true;
});
}
/**
* Initializes the DataStorage for this world. If the world is new, the patch registry is initialized to the
* current versions of the plugins.
* @param levelPath Folder of the world
* @param newWorld {@code true} if this is a fresh world
*
*/
public static void initializeWorldData(File levelPath, boolean newWorld){
WorldDataAPI.load(new File(levelPath, "data"));
if (newWorld){
getMigrationProfile().markApplied();
WorldDataAPI.saveFile(BCLib.MOD_ID);
}
}
private static boolean fixData(File dir, String levelID, boolean showUI, Consumer<Boolean> onResume) { private static boolean fixData(File dir, String levelID, boolean showUI, Consumer<Boolean> onResume) {
MigrationProfile profile = loadProfile(dir, levelID); MigrationProfile profile = loadProfileIfNeeded();
Consumer<Boolean> runFixes = (applyFixes) -> { Consumer<Boolean> runFixes = (applyFixes) -> {
if (applyFixes) { if (applyFixes) {
@ -153,16 +202,15 @@ public class DataFixerAPI {
return false; return false;
} }
static MigrationProfile loadProfile(File dir, String levelID){ private static MigrationProfile loadProfileIfNeeded(){
if (!Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_PATCH_CATEGORY, "applyPatches", true)) { if (!Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_PATCH_CATEGORY, "applyPatches", true)) {
LOGGER.info("World Patches are disabled"); LOGGER.info("World Patches are disabled");
return null; return null;
} }
final CompoundTag patchConfig = WorldDataAPI.getCompoundTag(BCLib.MOD_ID, Configs.MAIN_PATCH_CATEGORY); MigrationProfile profile = getMigrationProfile();
MigrationProfile profile = Patch.createMigrationData(patchConfig);
if (!profile.hasAnyFixes()) { if (!profile.hasAnyFixes()) {
LOGGER.info("Everything up to date"); LOGGER.info("Everything up to date");
return null; return null;
} }
@ -170,6 +218,13 @@ public class DataFixerAPI {
return profile; return profile;
} }
@NotNull
private static MigrationProfile getMigrationProfile() {
final CompoundTag patchConfig = WorldDataAPI.getCompoundTag(BCLib.MOD_ID, Configs.MAIN_PATCH_CATEGORY);
MigrationProfile profile = Patch.createMigrationData(patchConfig);
return profile;
}
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
static void showBackupWarning(String levelID, Consumer<Boolean> whenFinished){ static void showBackupWarning(String levelID, Consumer<Boolean> whenFinished){
TranslatableComponent promptText = new TranslatableComponent("bclib.datafixer.backupWarning.prompt"); TranslatableComponent promptText = new TranslatableComponent("bclib.datafixer.backupWarning.prompt");

View file

@ -5,6 +5,9 @@ import net.minecraft.client.color.block.BlockColors;
import net.minecraft.client.color.item.ItemColors; import net.minecraft.client.color.item.ItemColors;
import net.minecraft.client.main.GameConfig; import net.minecraft.client.main.GameConfig;
import net.minecraft.core.Registry; import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess.RegistryHolder;
import net.minecraft.world.level.LevelSettings;
import net.minecraft.world.level.levelgen.WorldGenSettings;
import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.LevelStorageSource;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
@ -67,6 +70,11 @@ public abstract class MinecraftMixin {
return levelID; return levelID;
} }
@Inject(method="createLevel", at=@At("HEAD"))
private void bclib_initPatchData(String levelID, LevelSettings levelSettings, RegistryHolder registryHolder, WorldGenSettings worldGenSettings, CallbackInfo ci) {
DataFixerAPI.initializeWorldData(this.levelSource, levelID, true);
}
// @Inject(method="doLoadLevel", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at=@At(value="INVOKE", target="Lnet/minecraft/client/Minecraft;makeServerStem(Lnet/minecraft/core/RegistryAccess$RegistryHolder;Ljava/util/function/Function;Lcom/mojang/datafixers/util/Function4;ZLnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;)Lnet/minecraft/client/Minecraft$ServerStem;")) // @Inject(method="doLoadLevel", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at=@At(value="INVOKE", target="Lnet/minecraft/client/Minecraft;makeServerStem(Lnet/minecraft/core/RegistryAccess$RegistryHolder;Ljava/util/function/Function;Lcom/mojang/datafixers/util/Function4;ZLnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;)Lnet/minecraft/client/Minecraft$ServerStem;"))
// private void bclib_onCallFixer( // private void bclib_onCallFixer(

View file

@ -11,7 +11,7 @@ import ru.bclib.api.datafixer.DataFixerAPI;
abstract public class MainMixin { abstract public class MainMixin {
@ModifyArg(method="main", at=@At(value="INVOKE", target="Lnet/minecraft/server/MinecraftServer;convertFromRegionFormatIfNeeded(Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;)V")) @ModifyArg(method="main", at=@At(value="INVOKE", target="Lnet/minecraft/server/MinecraftServer;convertFromRegionFormatIfNeeded(Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;)V"))
private static LevelStorageSource.LevelStorageAccess bclib_callServerFix(LevelStorageSource.LevelStorageAccess session){ private static LevelStorageSource.LevelStorageAccess bclib_callServerFix(LevelStorageSource.LevelStorageAccess session){
DataFixerAPI.fixData(session, false, (didFix)->{}); DataFixerAPI.fixData(session, false, (didFix)->{/* not called when showUI==false */});
return session; return session;
} }
} }