diff --git a/src/main/java/youyihj/zenutils/api/config/ConfigUtils.java b/src/main/java/youyihj/zenutils/api/config/ConfigUtils.java index a93ab97..fded94c 100644 --- a/src/main/java/youyihj/zenutils/api/config/ConfigUtils.java +++ b/src/main/java/youyihj/zenutils/api/config/ConfigUtils.java @@ -2,9 +2,9 @@ import crafttweaker.annotations.ZenRegister; import crafttweaker.api.data.*; -import youyihj.zenutils.api.config.elements.ConfigGroup; import stanhebben.zenscript.annotations.ZenClass; import stanhebben.zenscript.annotations.ZenMethod; +import youyihj.zenutils.api.config.elements.ConfigGroup; import java.util.HashMap; import java.util.LinkedList; @@ -19,6 +19,11 @@ public static ConfigGroup named(String name) { return new ConfigGroup(null, name); } + @ZenMethod + public static ModMeta createMeta(String name) { + return new ModMeta(name); + } + @ZenMethod public static IData dataMap(Map dataMap) { HashMap actualMap = new HashMap<>(); diff --git a/src/main/java/youyihj/zenutils/api/config/ModMeta.java b/src/main/java/youyihj/zenutils/api/config/ModMeta.java new file mode 100644 index 0000000..fc9e78a --- /dev/null +++ b/src/main/java/youyihj/zenutils/api/config/ModMeta.java @@ -0,0 +1,48 @@ +package youyihj.zenutils.api.config; + +import crafttweaker.annotations.ZenRegister; +import net.minecraftforge.fml.common.ModMetadata; +import stanhebben.zenscript.annotations.ZenClass; +import stanhebben.zenscript.annotations.ZenMethod; + +@ZenRegister +@ZenClass("mods.zenutils.config.ModMetaData") +public class ModMeta { + public final ModMetadata metadata = new ModMetadata(); + + public ModMeta(String name) { + metadata.name = name; + metadata.description = null; //to distinguish between deliberately empty and not provided (=null, will be filled with default description) + metadata.credits = "ยง3Created via CraftTweaker script"; + } + + @ZenMethod + public ModMeta setDescription(String string) { + this.metadata.description = string; + return this; + } + + @ZenMethod + public ModMeta setVersion(String string) { + this.metadata.version = string; + return this; + } + + @ZenMethod + public ModMeta addAuthor(String string) { + this.metadata.authorList.add(string); + return this; + } + + @ZenMethod + public ModMeta setCredits(String string) { + this.metadata.credits = string; + return this; + } + + @ZenMethod + public ModMeta setLogoLocation(String string) { + this.metadata.logoFile = string; + return this; + } +} diff --git a/src/main/java/youyihj/zenutils/api/config/elements/ConfigGroup.java b/src/main/java/youyihj/zenutils/api/config/elements/ConfigGroup.java index 8caa4d0..c34257e 100644 --- a/src/main/java/youyihj/zenutils/api/config/elements/ConfigGroup.java +++ b/src/main/java/youyihj/zenutils/api/config/elements/ConfigGroup.java @@ -6,9 +6,12 @@ import stanhebben.zenscript.annotations.Optional; import stanhebben.zenscript.annotations.ZenClass; import stanhebben.zenscript.annotations.ZenMethod; -import youyihj.zenutils.impl.config.ConfigAnytimeAnytime; +import youyihj.zenutils.ZenUtils; +import youyihj.zenutils.api.config.ModMeta; import youyihj.zenutils.impl.config.ClassProvider; +import youyihj.zenutils.impl.config.ConfigAnytimeAnytime; +import javax.annotation.Nullable; import java.util.*; @ZenRegister @@ -16,6 +19,8 @@ public class ConfigGroup extends ConfigElement { protected List children; + protected ModMeta modMeta = null; + public ConfigGroup(ConfigGroup parentIn, String nameIn) { super(parentIn, nameIn); this.children = new ArrayList<>(); @@ -171,6 +176,19 @@ public Set getClasses() { return hashSet; } + @ZenMethod + public ConfigGroup withGui(@Optional @Nullable ModMeta modMeta) { + if(this.parent == null) { + if(modMeta == null) + this.modMeta = new ModMeta(this.name); + else + this.modMeta = modMeta; + } else { + ZenUtils.crafttweakerLogger.logError("Cannot register subcategories for mod config GUI"); + } + return this; + } + @ZenMethod public void register() { register0(); @@ -179,7 +197,7 @@ public void register() { for (String clsN : this.getClasses()) { CraftTweakerAPI.registerClass(Class.forName(clsN)); } - ConfigAnytimeAnytime.register(Class.forName(getClassName())); + ConfigAnytimeAnytime.register(Class.forName(getClassName()), modMeta == null ? null : modMeta.metadata); } catch (Throwable t) { throw new RuntimeException(t); } @@ -318,5 +336,4 @@ protected void register0() { ClassProvider.classes.put(className.replace('/', '.'), classWriter.toByteArray()); } - } diff --git a/src/main/java/youyihj/zenutils/impl/config/ConfigAnytimeAnytime.java b/src/main/java/youyihj/zenutils/impl/config/ConfigAnytimeAnytime.java index a88edda..8dac4cb 100644 --- a/src/main/java/youyihj/zenutils/impl/config/ConfigAnytimeAnytime.java +++ b/src/main/java/youyihj/zenutils/impl/config/ConfigAnytimeAnytime.java @@ -29,18 +29,27 @@ import net.minecraftforge.common.config.Config; import net.minecraftforge.common.config.ConfigManager; import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.fml.common.DummyModContainer; +import net.minecraftforge.fml.common.ModContainer; +import net.minecraftforge.fml.common.ModMetadata; +import youyihj.zenutils.ZenUtils; import java.io.File; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Field; +import java.util.HashMap; import java.util.Map; import java.util.Set; public class ConfigAnytimeAnytime { private static final MethodHandle CONFIGMANAGER$SYNC; + public static final Map registeredGuiContainers = new HashMap<>(); + public static final Map registeredGuiSaveMethods = new HashMap<>(); + public static final String DESCRIPTOR = "Gui Container automatically generated by ZenUtils"; + static { try { Class.forName("net.minecraftforge.common.config.ConfigManager", true, Launch.classLoader); // Init first @@ -60,19 +69,19 @@ public class ConfigAnytimeAnytime { * * @param configClass configuration class that is annotated with {@link Config} */ - public static void register(Class configClass) { + public static void register(Class configClass, ModMetadata info) { if (!configClass.isAnnotationPresent(Config.class)) { return; } try { - $register(configClass); + $register(configClass, info); } catch (Throwable e) { throw new RuntimeException(e); } } @SuppressWarnings("unchecked") - private static void $register(Class configClass) throws Throwable { + private static void $register(Class configClass, ModMetadata modMeta) throws Throwable { Field configManager$mod_config_classes = ConfigManager.class.getDeclaredField("MOD_CONFIG_CLASSES"); Field configManager$configs = ConfigManager.class.getDeclaredField("CONFIGS"); configManager$mod_config_classes.setAccessible(true); @@ -101,7 +110,25 @@ public static void register(Class configClass) { } CONFIGMANAGER$SYNC.invokeExact(cfg, configClass, modId, config.category(), true, (Object) null); - cfg.save(); + + if(modMeta != null) { + modMeta.modId = modId; + modMeta.updateUrl = DESCRIPTOR; //needed to filter later in MixinGuiModList + modMeta.autogenerated = false; //otherwise won't be shown + if(modMeta.description == null) + modMeta.description = "Gui Container for " + modId + ".cfg automatically generated by ZenUtils"; + registeredGuiContainers.put(modId, new DummyModContainer(modMeta)); + + Configuration finalCfg = cfg; + registeredGuiSaveMethods.put(modId, () -> { + try { + CONFIGMANAGER$SYNC.invokeExact(finalCfg, configClass, modId, config.category(), true, (Object) null); //this saves to ingame available class + finalCfg.save(); //this saves to file + } catch (Throwable e){ + ZenUtils.crafttweakerLogger.logError("Failed to save CT backed config to file!"); + } + }); + } } } diff --git a/src/main/java/youyihj/zenutils/impl/config/ConfigChangedHandler.java b/src/main/java/youyihj/zenutils/impl/config/ConfigChangedHandler.java new file mode 100644 index 0000000..d6c1c1c --- /dev/null +++ b/src/main/java/youyihj/zenutils/impl/config/ConfigChangedHandler.java @@ -0,0 +1,15 @@ +package youyihj.zenutils.impl.config; + +import net.minecraftforge.fml.client.event.ConfigChangedEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +@Mod.EventBusSubscriber +public class ConfigChangedHandler { + @SubscribeEvent + public static void onConfigChanged(ConfigChangedEvent.OnConfigChangedEvent event) { + if(ConfigAnytimeAnytime.registeredGuiContainers.containsKey(event.getModID())) { + ConfigAnytimeAnytime.registeredGuiSaveMethods.get(event.getModID()).run(); + } + } +} diff --git a/src/main/java/youyihj/zenutils/impl/mixin/vanilla/MixinFMLClientHandler.java b/src/main/java/youyihj/zenutils/impl/mixin/vanilla/MixinFMLClientHandler.java new file mode 100644 index 0000000..f71059e --- /dev/null +++ b/src/main/java/youyihj/zenutils/impl/mixin/vanilla/MixinFMLClientHandler.java @@ -0,0 +1,39 @@ +package youyihj.zenutils.impl.mixin.vanilla; + +import com.google.common.collect.BiMap; +import net.minecraftforge.fml.client.DefaultGuiFactory; +import net.minecraftforge.fml.client.FMLClientHandler; +import net.minecraftforge.fml.client.IModGuiFactory; +import net.minecraftforge.fml.common.ModContainer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import youyihj.zenutils.impl.config.ConfigAnytimeAnytime; + +import java.util.ArrayList; + +@Mixin(FMLClientHandler.class) +public abstract class MixinFMLClientHandler { + @Shadow + private BiMap guiFactories; + + @Inject( + method = "addSpecialModEntries", + at = @At("TAIL"), + remap = false + ) + private void addDummyMods(ArrayList mods, CallbackInfo ci) { + mods.addAll(ConfigAnytimeAnytime.registeredGuiContainers.values()); + } + + @Inject( + method = "finishMinecraftLoading", + at = @At("TAIL"), + remap = false + ) + private void addDefaultGuiFactory(CallbackInfo ci){ + ConfigAnytimeAnytime.registeredGuiContainers.values().forEach(container -> this.guiFactories.put(container, DefaultGuiFactory.forMod(container))); + } +} diff --git a/src/main/java/youyihj/zenutils/impl/mixin/vanilla/MixinGuiConfig.java b/src/main/java/youyihj/zenutils/impl/mixin/vanilla/MixinGuiConfig.java new file mode 100644 index 0000000..0f8aafc --- /dev/null +++ b/src/main/java/youyihj/zenutils/impl/mixin/vanilla/MixinGuiConfig.java @@ -0,0 +1,20 @@ +package youyihj.zenutils.impl.mixin.vanilla; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraftforge.fml.client.config.GuiConfig; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import youyihj.zenutils.impl.config.ConfigAnytimeAnytime; + +@Mixin(GuiConfig.class) +public abstract class MixinGuiConfig { + @WrapOperation( + method = "actionPerformed", + at = @At(value = "INVOKE", target = "Lnet/minecraftforge/fml/common/Loader;isModLoaded(Ljava/lang/String;)Z", remap = false) + ) + private boolean keepAutogeneratedMods(String modId, Operation original) { + boolean isLoaded = original.call(modId); + return isLoaded || ConfigAnytimeAnytime.registeredGuiContainers.containsKey(modId); + } +} diff --git a/src/main/java/youyihj/zenutils/impl/mixin/vanilla/MixinGuiModList.java b/src/main/java/youyihj/zenutils/impl/mixin/vanilla/MixinGuiModList.java new file mode 100644 index 0000000..3148267 --- /dev/null +++ b/src/main/java/youyihj/zenutils/impl/mixin/vanilla/MixinGuiModList.java @@ -0,0 +1,47 @@ +package youyihj.zenutils.impl.mixin.vanilla; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; +import net.minecraftforge.fml.client.GuiModList; +import net.minecraftforge.fml.common.ModContainer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import youyihj.zenutils.impl.config.ConfigAnytimeAnytime; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Mixin(GuiModList.class) +public abstract class MixinGuiModList { + @Shadow(remap = false) private ArrayList mods; + + @WrapOperation( + method = "reloadMods", + at = @At(value = "INVOKE", target = "Ljava/util/ArrayList;clear()V"), + remap = false + ) + private void keepAutogeneratedMods(ArrayList mods, Operation original, @Share("keptMods")LocalRef> keptMods) { + keptMods.set( + mods.stream() + .filter(mod -> mod.getMetadata().updateUrl.equals(ConfigAnytimeAnytime.DESCRIPTOR)) + .collect(Collectors.toList()) + ); + original.call(mods); //clear + } + + @Inject( + method = "reloadMods", + at = @At("TAIL"), + remap = false + ) + private void keepAutogeneratedMods(CallbackInfo ci, @Share("keptMods")LocalRef> keptMods){ + if(keptMods.get() != null) + this.mods.addAll(keptMods.get()); //add at end of list + } +} diff --git a/src/main/resources/mixins.zenutils.vanilla.json b/src/main/resources/mixins.zenutils.vanilla.json index 5e9dd8c..4fefe1b 100644 --- a/src/main/resources/mixins.zenutils.vanilla.json +++ b/src/main/resources/mixins.zenutils.vanilla.json @@ -17,5 +17,8 @@ "MixinForgeHooks" ], "client": [ + "MixinFMLClientHandler", + "MixinGuiModList", + "MixinGuiConfig" ] } \ No newline at end of file