diff --git a/src/main/java/gregtech/api/util/Mods.java b/src/main/java/gregtech/api/util/Mods.java index b62a262856d..9b56b74688d 100644 --- a/src/main/java/gregtech/api/util/Mods.java +++ b/src/main/java/gregtech/api/util/Mods.java @@ -25,6 +25,10 @@ public enum Mods { AdvancedRocketry(Names.ADVANCED_ROCKETRY), AppliedEnergistics2(Names.APPLIED_ENERGISTICS2), Baubles(Names.BAUBLES), + BetterQuestingUnofficial(Names.BETTER_QUESTING, mod -> { + var container = Loader.instance().getIndexedModList().get(Names.BETTER_QUESTING); + return container.getVersion().startsWith("4."); + }), BinnieCore(Names.BINNIE_CORE), BiomesOPlenty(Names.BIOMES_O_PLENTY), BuildCraftCore(Names.BUILD_CRAFT_CORE), @@ -96,6 +100,7 @@ public static class Names { public static final String ADVANCED_ROCKETRY = "advancedrocketry"; public static final String APPLIED_ENERGISTICS2 = "appliedenergistics2"; public static final String BAUBLES = "baubles"; + public static final String BETTER_QUESTING = "betterquesting"; public static final String BINNIE_CORE = "binniecore"; public static final String BIOMES_O_PLENTY = "biomesoplenty"; public static final String BUILD_CRAFT_CORE = "buildcraftcore"; diff --git a/src/main/java/gregtech/core/CoreModule.java b/src/main/java/gregtech/core/CoreModule.java index 6648efa62bf..5e80c4b3b31 100644 --- a/src/main/java/gregtech/core/CoreModule.java +++ b/src/main/java/gregtech/core/CoreModule.java @@ -28,6 +28,7 @@ import gregtech.api.unification.material.event.PostMaterialEvent; import gregtech.api.unification.material.registry.MarkerMaterialRegistry; import gregtech.api.util.CapesRegistry; +import gregtech.api.util.Mods; import gregtech.api.util.VirtualTankRegistry; import gregtech.api.util.input.KeyBind; import gregtech.api.util.oreglob.OreGlob; @@ -71,6 +72,8 @@ import gregtech.core.sound.GTSoundEvents; import gregtech.core.sound.internal.SoundManager; import gregtech.core.unification.material.internal.MaterialRegistryManager; +import gregtech.datafix.command.CommandDataFix; +import gregtech.integration.bq.BQuDataFixer; import gregtech.loaders.dungeon.DungeonLootLoader; import gregtech.modules.GregTechModules; @@ -316,7 +319,12 @@ public void serverStarting(FMLServerStartingEvent event) { GregTechAPI.commandManager.addCommand(new CommandHand()); GregTechAPI.commandManager.addCommand(new CommandRecipeCheck()); GregTechAPI.commandManager.addCommand(new CommandShaders()); + GregTechAPI.commandManager.addCommand(new CommandDataFix()); CapesRegistry.load(); + + if (Mods.BetterQuestingUnofficial.isModLoaded()) { + BQuDataFixer.onServerStarting(event.getServer()); + } } @Override diff --git a/src/main/java/gregtech/datafix/command/CommandDataFix.java b/src/main/java/gregtech/datafix/command/CommandDataFix.java new file mode 100644 index 00000000000..6a7f0cbad40 --- /dev/null +++ b/src/main/java/gregtech/datafix/command/CommandDataFix.java @@ -0,0 +1,33 @@ +package gregtech.datafix.command; + +import gregtech.api.util.Mods; +import gregtech.integration.bq.CommandBQuDataFix; + +import net.minecraft.command.ICommandSender; +import net.minecraftforge.server.command.CommandTreeBase; + +import org.jetbrains.annotations.NotNull; + +public final class CommandDataFix extends CommandTreeBase { + + public CommandDataFix() { + if (Mods.BetterQuestingUnofficial.isModLoaded()) { + addSubcommand(new CommandBQuDataFix()); + } + } + + @Override + public @NotNull String getName() { + return "datafix"; + } + + @Override + public int getRequiredPermissionLevel() { + return 3; + } + + @Override + public @NotNull String getUsage(@NotNull ICommandSender sender) { + return "gregtech.command.datafix.usage"; + } +} diff --git a/src/main/java/gregtech/integration/bq/BQuDataFixer.java b/src/main/java/gregtech/integration/bq/BQuDataFixer.java new file mode 100644 index 00000000000..f0d5bdb48b8 --- /dev/null +++ b/src/main/java/gregtech/integration/bq/BQuDataFixer.java @@ -0,0 +1,245 @@ +package gregtech.integration.bq; + +import gregtech.api.GregTechAPI; +import gregtech.api.util.Mods; +import gregtech.datafix.migration.lib.MTERegistriesMigrator; + +import net.minecraft.command.ICommandSender; +import net.minecraft.util.ResourceLocation; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public final class BQuDataFixer { + + private static final Logger LOG = LogManager.getLogger(BQuDataFixer.class); + + /** + * {@code saves//betterquesting/QuestDatabase.json} + */ + private static final String QUEST_DB_JSON_FILE = "QuestDatabase.json"; + /** + * {@code config/betterquesting/resources/} + */ + private static final String BQ_RESOURCES_DIR = "resources"; + private static final String MC_CONFIG_DIR = "config"; + + private static final String ID_8 = "id:8"; + private static final String DAMAGE_2 = "Damage:2"; + private static final String TAG_10 = "tag:10"; + private static final String ORIG_ID_8 = "orig_id:8"; + private static final String ORIG_META_3 = "orig_meta:3"; + + private static final String PLACEHOLDER = "betterquesting:placeholder"; + + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + private BQuDataFixer() {} + + public static void onServerStarting(@NotNull ICommandSender server) { + LOG.info("Attempting to apply BQu DataFixers"); + Path worldDir = server.getEntityWorld().getSaveHandler().getWorldDirectory().toPath(); + if (processWorldDir(worldDir)) { + LOG.info("Successfully applied BQu data fixers"); + } else { + LOG.error("Failed to apply BQu data fixers"); + } + } + + /** + * Processes the current world directory and applies data fixers + * + * @param worldDir the current world's directory + * @return if processing was successful + */ + public static boolean processWorldDir(@NotNull Path worldDir) { + LOG.info("Processing world directory"); + Path bqDir = worldDir.resolve(Mods.Names.BETTER_QUESTING); + Path questDbPath = bqDir.resolve(QUEST_DB_JSON_FILE); + if (!Files.isRegularFile(questDbPath)) { + LOG.info("Unable to find BQ Quest Database, assuming this is a new world"); + return true; + } + + JsonObject json; + try { + json = readJson(questDbPath); + } catch (FileNotFoundException e) { + LOG.info("Unable to find BQ Quest Database, assuming this is a new world"); + return true; + } catch (IOException e) { + LOG.error("Failed to read BQ Quest Database in World Folder", e); + return false; + } + + for (var entry : json.entrySet()) { + recurseJsonApplyFix(entry.getValue()); + } + + try { + writeJson(questDbPath, json); + } catch (IOException e) { + LOG.error("Failed to write BQ Quest DataBase in World Folder", e); + } + + return true; + } + + /** + * Processes the BQu config directory and applies data fixers + * + * @param worldDir the current world's directory + * @return if processing was successful + */ + public static boolean processConfigDir(@NotNull Path worldDir) { + LOG.info("Processing config directory"); + Path mcDir = worldDir.getParent().getParent(); + Path configDir = mcDir.resolve(MC_CONFIG_DIR); + Path bqConfigDir = configDir.resolve(Mods.Names.BETTER_QUESTING); + + List paths; + try (var stream = Files.walk(bqConfigDir, 4)) { + paths = stream.filter(p -> !p.endsWith(BQ_RESOURCES_DIR)) // do not walk down the resources dir + .filter(Files::isRegularFile) + .filter(path -> path.toFile().getName().endsWith(".json")) + .collect(Collectors.toList()); + } catch (IOException e) { + LOG.error("Failed to read BQ Quest Database in Config Folder", e); + return false; + } + + Map map = new Object2ObjectOpenHashMap<>(paths.size()); + for (Path path : paths) { + try { + map.put(path, readJson(path)); + } catch (IOException e) { + LOG.error("Failed to read BQ Quest File from Config Folder", e); + } + } + + map.values().stream() + .flatMap(jsonObject -> jsonObject.entrySet().stream()) + .map(Map.Entry::getValue) + .forEach(BQuDataFixer::recurseJsonApplyFix); + + for (var entry : map.entrySet()) { + try { + writeJson(entry.getKey(), entry.getValue()); + } catch (IOException e) { + LOG.error("Failed to write BQ Quest File in Config Folder", e); + } + } + + return true; + } + + /** + * @param path the path to read + * @return the json object stored at the path + * @throws IOException if failure + */ + private static @NotNull JsonObject readJson(@NotNull Path path) throws IOException { + LOG.info("Reading file at path {}", path); + try (Reader reader = Files.newBufferedReader(path)) { + return GSON.fromJson(reader, JsonObject.class); + } + } + + /** + * Recursively walks a JSON file and applies datafixers to matching sub-objects + * + * @param element the json element to recurse + */ + private static void recurseJsonApplyFix(@NotNull JsonElement element) { + if (element.isJsonObject()) { + JsonObject object = element.getAsJsonObject(); + if (object.has(ID_8)) { + applyDataFix(object); + } else { + for (var entry : object.entrySet()) { + recurseJsonApplyFix(entry.getValue()); + } + } + } else if (element.isJsonArray()) { + for (JsonElement value : element.getAsJsonArray()) { + recurseJsonApplyFix(value); + } + } + } + + /** + * @param path the path to write the file + * @param jsonObject the object to write + * @throws IOException if failure + */ + private static void writeJson(@NotNull Path path, @NotNull JsonObject jsonObject) throws IOException { + LOG.info("Writing file to path {}", path); + try (Writer writer = Files.newBufferedWriter(path)) { + GSON.toJson(jsonObject, writer); + } + } + + /** + * Applies {@link MTERegistriesMigrator} fixes to a BQu json file + * + * @param jsonObject the json to fix + */ + private static void applyDataFix(@NotNull JsonObject jsonObject) { + MTERegistriesMigrator migrator = GregTechAPI.MIGRATIONS.registriesMigrator(); + + ResourceLocation itemBlockId; + short meta; + String id = jsonObject.get(ID_8).getAsString(); + + boolean isPlaceHolder = PLACEHOLDER.equals(id); + + if (isPlaceHolder) { + // fix cases where BQu marks items as missing with placeholders + JsonObject orig = jsonObject.getAsJsonObject(TAG_10); + if (orig == null) { + return; + } + + if (!orig.has(ORIG_ID_8) || !orig.has(ORIG_META_3)) { + return; + } + + itemBlockId = new ResourceLocation(orig.get(ORIG_ID_8).getAsString()); + meta = orig.get(ORIG_META_3).getAsShort(); + } else { + itemBlockId = new ResourceLocation(id); + meta = jsonObject.get(DAMAGE_2).getAsShort(); + } + + ResourceLocation fixedName = migrator.fixItemName(itemBlockId, meta); + if (fixedName != null) { + jsonObject.add(ID_8, new JsonPrimitive(fixedName.toString())); + } + + short fixedMeta = migrator.fixItemMeta(itemBlockId, meta); + if (fixedMeta != meta) { + jsonObject.add(DAMAGE_2, new JsonPrimitive(fixedMeta)); + } + + if (isPlaceHolder) { + jsonObject.remove(TAG_10); + } + } +} diff --git a/src/main/java/gregtech/integration/bq/CommandBQuDataFix.java b/src/main/java/gregtech/integration/bq/CommandBQuDataFix.java new file mode 100644 index 00000000000..85a296091a9 --- /dev/null +++ b/src/main/java/gregtech/integration/bq/CommandBQuDataFix.java @@ -0,0 +1,54 @@ +package gregtech.integration.bq; + +import net.minecraft.command.CommandBase; +import net.minecraft.command.ICommandSender; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.util.text.TextFormatting; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.nio.file.Path; + +@ApiStatus.Internal +public final class CommandBQuDataFix extends CommandBase { + + @Override + public @NotNull String getName() { + return "bqu"; + } + + @Override + public @NotNull String getUsage(@NotNull ICommandSender sender) { + return "gregtech.command.datafix.bqu.usage"; + } + + @Override + public int getRequiredPermissionLevel() { + return 3; + } + + @Override + public void execute(@NotNull MinecraftServer server, @NotNull ICommandSender sender, String @NotNull [] args) { + if (args.length < 1 || !"confirm".equalsIgnoreCase(args[0])) { + sender.sendMessage(new TextComponentTranslation("gregtech.command.datafix.bqu.backup") + .setStyle(new Style().setColor(TextFormatting.YELLOW))); + return; + } + + sender.sendMessage(new TextComponentTranslation("gregtech.command.datafix.bqu.start") + .setStyle(new Style().setColor(TextFormatting.YELLOW))); + + Path worldDir = server.getEntityWorld().getSaveHandler().getWorldDirectory().toPath(); + + if (BQuDataFixer.processConfigDir(worldDir) && BQuDataFixer.processWorldDir(worldDir)) { + sender.sendMessage(new TextComponentTranslation("gregtech.command.datafix.bqu.complete") + .setStyle(new Style().setColor(TextFormatting.GREEN))); + } else { + sender.sendMessage(new TextComponentTranslation("gregtech.command.datafix.bqu.failed") + .setStyle(new Style().setColor(TextFormatting.RED))); + } + } +} diff --git a/src/main/resources/assets/gregtech/lang/en_us.lang b/src/main/resources/assets/gregtech/lang/en_us.lang index 8e9b4091db2..9aff6ad1cb8 100644 --- a/src/main/resources/assets/gregtech/lang/en_us.lang +++ b/src/main/resources/assets/gregtech/lang/en_us.lang @@ -5753,7 +5753,7 @@ gregtech.multiblock.hpca.info_coolant_name=PCB Coolant gregtech.multiblock.hpca.info_bridging_enabled=Bridging Enabled gregtech.multiblock.hpca.info_bridging_disabled=Bridging Disabled -gregtech.command.usage=Usage: /gregtech +gregtech.command.usage=Usage: /gregtech gregtech.command.worldgen.usage=Usage: /gregtech worldgen gregtech.command.worldgen.reload.usage=Usage: /gregtech worldgen reload gregtech.command.worldgen.reload.success=Worldgen successfully reloaded from config. @@ -5780,6 +5780,12 @@ gregtech.command.copy.copied_and_click=copied to clipboard. Click to copy again gregtech.command.copy.click_to_copy=Click to copy gregtech.command.copy.copied_start=Copied [ gregtech.command.copy.copied_end=] to the clipboard +gregtech.command.datafix.usage=Usage: /gregtech datafix +gregtech.command.datafix.bqu.usage=Usage: /gregtech datafix bqu [confirm] +gregtech.command.datafix.bqu.backup=Backup your save and BQu config dir, then rerun with the 'confirm' arg +gregtech.command.datafix.bqu.start=Started Migrating BQu Quest Database... +gregtech.command.datafix.bqu.complete=Finished Migrating BQu Quest Database +gregtech.command.datafix.bqu.failed=Failed Migrating BQu Quest Database. Restore your backups! gregtech.chat.cape=§5Congrats: you just unlocked a new cape! See the Cape Selector terminal app to use it.§r