package io.gitlab.jfronny.respackopts; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import io.gitlab.jfronny.respackopts.data.*; import io.gitlab.jfronny.respackopts.data.condition.Condition; import io.gitlab.jfronny.respackopts.data.enums.ConfigSyncMode; import io.gitlab.jfronny.respackopts.data.enums.PackCapability; import io.gitlab.jfronny.respackopts.filters.DirFilterEventImpl; import io.gitlab.jfronny.respackopts.gson.*; import io.gitlab.jfronny.respackopts.gson.entry.BooleanEntrySerializer; import io.gitlab.jfronny.respackopts.gson.entry.ConfigBranchSerializer; import io.gitlab.jfronny.respackopts.gson.entry.EnumEntrySerializer; import io.gitlab.jfronny.respackopts.gson.entry.NumericEntrySerializer; import io.gitlab.jfronny.respackopts.util.RpoCommand; import io.gitlab.jfronny.respackopts.data.entry.*; import io.gitlab.jfronny.respackopts.filters.FileFilterEventImpl; import io.gitlab.jfronny.respackopts.util.GuiFactory; import meteordevelopment.starscript.Script; import meteordevelopment.starscript.StandardLib; import meteordevelopment.starscript.Starscript; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.impl.gui.FabricGuiEntry; import net.minecraft.client.MinecraftClient; import net.minecraft.resource.ResourcePack; import net.minecraft.server.integrated.IntegratedServer; import net.minecraft.util.Identifier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; 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.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; @Environment(EnvType.CLIENT) public class Respackopts implements ClientModInitializer { public static final Map CONFIG_BRANCH = new HashMap<>(); public static final Map PACK_METAS = new HashMap<>(); public static final Map DISPLAY_NAME_LOOKUP = new HashMap<>(); public static final Map PACK_NAME_LOOKUP = new HashMap<>(); public static final Integer META_VERSION = 6; public static final String FILE_EXTENSION = ".rpo"; public static final Gson GSON; public static Starscript STAR_SCRIPT; public static final String ID = "respackopts"; public static final Logger LOGGER = LogManager.getFormatterLogger(ID); public static final Identifier CONF_ID = new Identifier(ID, "conf.json"); public static final Set SAVE_ACTIONS = new HashSet<>(); public static final GuiFactory GUI_FACTORY = new GuiFactory(); public static boolean forcePackReload = false; public static Path CONF_DIR; public static ConfigFile CONFIG; public static final Identifier RPO_SHADER_ID = new Identifier(Respackopts.ID, "config_supplier"); private static String shaderImportSource; static { GSON = new GsonBuilder() .registerTypeAdapter(ConfigEnumEntry.class, new EnumEntrySerializer()) .registerTypeAdapter(ConfigNumericEntry.class, new NumericEntrySerializer()) .registerTypeAdapter(ConfigBooleanEntry.class, new BooleanEntrySerializer()) .registerTypeAdapter(ConfigBranch.class, new ConfigBranchSerializer()) .registerTypeAdapter(Script.class, new ScriptDeserializer()) .registerTypeAdapter(FileRpo.class, new FileRpoDeserializer()) .registerTypeAdapter(DirRpo.class, new DirRpoDeserializer()) .registerTypeAdapter(Condition.class, new ConditionDeserializer()) .registerTypeAdapterFactory(new SingleElementSetTypeAdapterFactory()) .setPrettyPrinting() .create(); try { CONF_DIR = FabricLoader.getInstance().getConfigDir().resolve(ID); CONFIG = ConfigFile.load(); } catch (Throwable e) { LOGGER.error("Could not resolve config directory", e); } try { STAR_SCRIPT = new Starscript(); } catch (Exception e) { FabricGuiEntry.displayCriticalError(new Exception("Respackopts could not initialize Starscript. Expect issues with packs that use it", e), false); } StandardLib.init(STAR_SCRIPT); } public static String getId(ResourcePack pack) { return PACK_NAME_LOOKUP.get(pack.getName()); } public static boolean hasCapability(ResourcePack pack, PackCapability capability) { String id = getId(pack); if (id == null) return false; return PACK_METAS.get(id).capabilities.contains(capability); } @Override public void onInitializeClient() { try { Files.createDirectories(CONF_DIR); } catch (IOException e) { LOGGER.error("Could not initialize config directory", e); } if (CONFIG.debugLogs) SAVE_ACTIONS.add(() -> LOGGER.info("Save")); SAVE_ACTIONS.add(() -> { for (Map.Entry e : CONFIG_BRANCH.entrySet()) { STAR_SCRIPT.set(sanitizeString(e.getKey()), () -> e.getValue().buildStarscript()); } }); SAVE_ACTIONS.add(() -> { if (CONFIG.debugLogs) LOGGER.info("Generating FREX shader code"); StringBuilder sb = new StringBuilder(); sb.append("#ifndef respackopts_loaded"); sb.append("\n#define respackopts_loaded"); for (Map.Entry e : CONFIG_BRANCH.entrySet()) { e.getValue().buildShader(sb, sanitizeString(e.getKey())); } sb.append("\n#endif"); shaderImportSource = sb.toString(); }); DirFilterEventImpl.init(); FileFilterEventImpl.init(); if (CONFIG.debugCommands) RpoCommand.register(); } public static void save() { if (CONFIG.debugLogs) LOGGER.info("Saving configs"); for (Map.Entry e : CONFIG_BRANCH.entrySet()) { try (Writer writer = Files.newBufferedWriter(CONF_DIR.resolve(e.getKey() + ".json"))) { GSON.toJson(e.getValue(), writer); writer.flush(); } catch (IOException ex) { LOGGER.error("Could not save config", ex); } } for (Runnable action : SAVE_ACTIONS) { action.run(); } } public static void load(String id) { Path q = CONF_DIR.resolve(id + ".json"); if (Files.exists(q)) { if (CONFIG.debugLogs) LOGGER.info("Loading configs for: " + id); try (Reader reader = Files.newBufferedReader(q)) { ConfigBranch b = GSON.fromJson(reader, ConfigBranch.class); if (CONFIG_BRANCH.containsKey(id)) CONFIG_BRANCH.get(id).sync(b, ConfigSyncMode.CONF_LOAD); STAR_SCRIPT.set(sanitizeString(id), b::buildStarscript); } catch (IOException e) { LOGGER.error("Failed to load " + id, e); } } } public static String sanitizeString(String s) { // This trims whitespace/underscores and removes non-alphabetical or underscore characters // ^ = start of string // $ = end of string // * = zero or more times // [\\s_] = whitespace or underscores // | = or // [^a-zA-Z_] = not character or underscore return s.replaceAll("[^a-zA-Z_]|^[\\s_]*|[\\s_]*$", ""); } public static CompletableFuture forceReloadResources() { if (CONFIG.debugLogs) LOGGER.info("Forcing resource reload"); return CompletableFuture.allOf(MinecraftClient.getInstance().reloadResources(), reloadData()); } public static CompletableFuture reloadData() { IntegratedServer is = MinecraftClient.getInstance().getServer(); if (is != null) { is.getDataPackManager().scanPacks(); return is.reloadResources(is.getDataPackManager().getEnabledNames()); } return CompletableFuture.completedFuture(null); } public static String getShaderImportSource() { if (shaderImportSource == null) { Respackopts.LOGGER.error("Shader import source is null"); return ""; } return shaderImportSource; } }