package io.gitlab.jfronny.respackopts; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonSyntaxException; import io.gitlab.jfronny.respackopts.filters.DirFilterEventImpl; import io.gitlab.jfronny.respackopts.filters.FileFilterEventImpl; 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.integration.FrexCompat; import io.gitlab.jfronny.respackopts.model.ConfigFile; import io.gitlab.jfronny.respackopts.model.condition.Condition; import io.gitlab.jfronny.respackopts.model.tree.ConfigBooleanEntry; import io.gitlab.jfronny.respackopts.model.tree.ConfigBranch; import io.gitlab.jfronny.respackopts.model.tree.ConfigEnumEntry; import io.gitlab.jfronny.respackopts.model.tree.ConfigNumericEntry; import io.gitlab.jfronny.respackopts.util.GuiFactory; import io.gitlab.jfronny.respackopts.util.MetaCache; import io.gitlab.jfronny.respackopts.util.RpoCommand; import io.gitlab.jfronny.respackopts.util.RpoFormatException; 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.fabric.api.resource.conditions.v1.ResourceConditions; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.impl.gui.FabricGuiEntry; import net.minecraft.client.MinecraftClient; import net.minecraft.server.integrated.IntegratedServer; import net.minecraft.util.Identifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashSet; import java.util.Set; import java.util.concurrent.CompletableFuture; @Environment(EnvType.CLIENT) public class Respackopts implements ClientModInitializer { public static final Integer META_VERSION = 7; 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 = LoggerFactory.getLogger(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 final Identifier FAPI_CONDITION = new Identifier(ID, "config"); public static boolean forcePackReload = false; public static Path FALLBACK_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(Condition.class, new ConditionDeserializer()) .registerTypeAdapterFactory(new SingleEntrySetTypeAdapterFactory()) .registerTypeAdapterFactory(new SingleEntryListTypeAdapterFactory()) .setPrettyPrinting() .create(); try { FALLBACK_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); } @Override public void onInitializeClient() { try { Files.createDirectories(FALLBACK_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(() -> MetaCache.forEach((key, state) -> STAR_SCRIPT.set(sanitizeString(state.packId()), state.configBranch()::buildStarScript))); SAVE_ACTIONS.add(() -> { if (CONFIG.debugLogs) LOGGER.info("Generating shader code"); StringBuilder sb = new StringBuilder(); sb.append("#ifndef respackopts_loaded"); sb.append("\n#define respackopts_loaded"); MetaCache.forEach((key, state) -> state.configBranch().buildShader(sb, sanitizeString(state.packId()))); sb.append("\n#endif"); shaderImportSource = sb.toString(); }); DirFilterEventImpl.init(); FileFilterEventImpl.init(); if (CONFIG.debugCommands) RpoCommand.register(); if (FabricLoader.getInstance().isModLoaded("frex")) { FrexCompat.onInitializeFrex(); } ResourceConditions.register(FAPI_CONDITION, jsonObject -> { try { if (!jsonObject.has("value")) throw new RpoFormatException(FAPI_CONDITION + " does not have a value"); return GSON.fromJson(jsonObject.get("value"), Condition.class).evaluate("fabric"); } catch (RpoFormatException | JsonSyntaxException e) { LOGGER.error("Could not parse condition", e); return false; } }); } 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; } }