From aa963c5df679cf3b314e7a5f13a71abb26c3feb3 Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 2 Jan 2014 11:51:16 -0500 Subject: [PATCH] Config GUIs in the modlist now work. --- .../cpw/mods/fml/client/FMLClientHandler.java | 30 +++++ .../mods/fml/client/FMLConfigGuiFactory.java | 70 ++++++++++ .../java/cpw/mods/fml/client/GuiModList.java | 29 +++- .../cpw/mods/fml/client/IModGuiFactory.java | 127 ++++++++++++++++++ .../mods/fml/common/DummyModContainer.java | 6 + .../cpw/mods/fml/common/FMLContainer.java | 6 + .../cpw/mods/fml/common/FMLModContainer.java | 6 + .../mods/fml/common/InjectedModContainer.java | 6 + .../main/java/cpw/mods/fml/common/Mod.java | 13 +- .../cpw/mods/fml/common/ModContainer.java | 2 + 10 files changed, 292 insertions(+), 3 deletions(-) create mode 100644 fml/src/main/java/cpw/mods/fml/client/FMLConfigGuiFactory.java create mode 100644 fml/src/main/java/cpw/mods/fml/client/IModGuiFactory.java diff --git a/fml/src/main/java/cpw/mods/fml/client/FMLClientHandler.java b/fml/src/main/java/cpw/mods/fml/client/FMLClientHandler.java index 92b5838df4..40366bcf52 100644 --- a/fml/src/main/java/cpw/mods/fml/client/FMLClientHandler.java +++ b/fml/src/main/java/cpw/mods/fml/client/FMLClientHandler.java @@ -36,7 +36,10 @@ import net.minecraft.network.INetHandler; import net.minecraft.network.NetworkManager; import net.minecraft.server.MinecraftServer; import org.apache.logging.log4j.Level; +import com.google.common.base.Strings; import com.google.common.base.Throwables; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; @@ -119,6 +122,8 @@ public class FMLClientHandler implements IFMLSidedHandler private Map resourcePackMap; + private BiMap guiFactories; + /** * Called to start the whole game off * @@ -239,6 +244,26 @@ public class FMLClientHandler implements IFMLSidedHandler // Reload resources // client.func_110436_a(); RenderingRegistry.instance().loadEntityRenderers((Map, Render>)RenderManager.field_78727_a.field_78729_o); + guiFactories = HashBiMap.create(); + for (ModContainer mc : Loader.instance().getActiveModList()) + { + String className = mc.getGuiClassName(); + if (Strings.isNullOrEmpty(className)) + { + continue; + } + try + { + Class clazz = Class.forName(className, true, Loader.instance().getModClassLoader()); + Class guiClassFactory = clazz.asSubclass(IModGuiFactory.class); + IModGuiFactory guiFactory = guiClassFactory.newInstance(); + guiFactory.initialize(client); + guiFactories.put(mc, guiFactory); + } catch (Exception e) + { + FMLLog.log(Level.ERROR, e, "A critical error occurred instantiating the gui factory for mod %s", mc.getModId()); + } + } loading = false; } @@ -514,4 +539,9 @@ public class FMLClientHandler implements IFMLSidedHandler { showGuiScreen(new GuiIngameModOptions(guiIngameMenu)); } + + public IModGuiFactory getGuiFactoryFor(ModContainer selectedMod) + { + return guiFactories.get(selectedMod); + } } diff --git a/fml/src/main/java/cpw/mods/fml/client/FMLConfigGuiFactory.java b/fml/src/main/java/cpw/mods/fml/client/FMLConfigGuiFactory.java new file mode 100644 index 0000000000..aff7969edd --- /dev/null +++ b/fml/src/main/java/cpw/mods/fml/client/FMLConfigGuiFactory.java @@ -0,0 +1,70 @@ +package cpw.mods.fml.client; + +import java.util.Set; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; + +public class FMLConfigGuiFactory implements IModGuiFactory { + public static class FMLConfigGuiScreen extends GuiScreen { + private GuiScreen parent; + + public FMLConfigGuiScreen(GuiScreen parent) + { + this.parent = parent; + } + + @SuppressWarnings("unchecked") + @Override + public void func_73866_w_() + { + this.field_146292_n.add(new GuiButton(1, this.field_146294_l / 2 - 75, this.field_146295_m - 38, I18n.func_135052_a("gui.done"))); + } + + @Override + protected void func_146284_a(GuiButton p_73875_1_) + { + if (p_73875_1_.field_146124_l && p_73875_1_.field_146127_k == 1) + { + FMLClientHandler.instance().showGuiScreen(parent); + } + } + + @Override + public void func_73863_a(int p_73863_1_, int p_73863_2_, float p_73863_3_) + { + this.func_146276_q_(); + this.func_73732_a(this.field_146289_q, "Forge Mod Loader test config screen", this.field_146294_l / 2, 40, 0xFFFFFF); + super.func_73863_a(p_73863_1_, p_73863_2_, p_73863_3_); + } + + } + private Minecraft minecraft; + @Override + public void initialize(Minecraft minecraftInstance) + { + this.minecraft = minecraftInstance; + } + + @Override + public Class mainConfigGuiClass() + { + return FMLConfigGuiScreen.class; + } + + @Override + public Set runtimeGuiCategories() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public RuntimeOptionGuiHandler getHandlerFor(RuntimeOptionCategoryElement element) + { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/fml/src/main/java/cpw/mods/fml/client/GuiModList.java b/fml/src/main/java/cpw/mods/fml/client/GuiModList.java index 9a7958f386..b83ca7188d 100644 --- a/fml/src/main/java/cpw/mods/fml/client/GuiModList.java +++ b/fml/src/main/java/cpw/mods/fml/client/GuiModList.java @@ -33,10 +33,12 @@ import net.minecraft.client.resources.I18n; import net.minecraft.client.resources.IResourcePack; import net.minecraft.util.ResourceLocation; +import org.apache.logging.log4j.Level; import org.lwjgl.opengl.GL11; import com.google.common.base.Strings; +import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.Loader; import cpw.mods.fml.common.ModContainer; import cpw.mods.fml.common.ModContainer.Disableable; @@ -93,8 +95,8 @@ public class GuiModList extends GuiScreen } listWidth=Math.min(listWidth, 150); this.field_146292_n.add(new GuiButton(6, this.field_146294_l / 2 - 75, this.field_146295_m - 38, I18n.func_135052_a("gui.done"))); - configModButton = new GuiButton(7, 10, this.field_146295_m - 60, this.listWidth, 20, "Config"); - disableModButton = new GuiButton(8, 10, this.field_146295_m - 38, this.listWidth, 20, "Disable"); + configModButton = new GuiButton(20, 10, this.field_146295_m - 60, this.listWidth, 20, "Config"); + disableModButton = new GuiButton(21, 10, this.field_146295_m - 38, this.listWidth, 20, "Disable"); this.field_146292_n.add(configModButton); this.field_146292_n.add(disableModButton); this.modList=new GuiSlotModList(this, mods, listWidth); @@ -110,6 +112,18 @@ public class GuiModList extends GuiScreen case 6: this.field_146297_k.func_147108_a(this.mainMenu); return; + case 20: + try + { + IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedMod); + GuiScreen newScreen = guiFactory.mainConfigGuiClass().getConstructor(GuiScreen.class).newInstance(this); + this.field_146297_k.func_147108_a(newScreen); + } + catch (Exception e) + { + FMLLog.log(Level.ERROR, e, "There was a critical issue trying to build the config GUI for %s", selectedMod.getModId()); + } + return; } } super.func_146284_a(button); @@ -222,6 +236,17 @@ public class GuiModList extends GuiScreen disableModButton.field_146125_m = true; disableModButton.field_146124_l = false; } + IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedMod); + if (guiFactory == null || guiFactory.mainConfigGuiClass() == null) + { + configModButton.field_146125_m = true; + configModButton.field_146124_l = false; + } + else + { + configModButton.field_146125_m = true; + configModButton.field_146124_l = true; + } } else { offset = ( this.listWidth + this.field_146294_l ) / 2; this.func_73732_a(this.field_146289_q, selectedMod.getName(), offset, 35, 0xFFFFFF); diff --git a/fml/src/main/java/cpw/mods/fml/client/IModGuiFactory.java b/fml/src/main/java/cpw/mods/fml/client/IModGuiFactory.java new file mode 100644 index 0000000000..0e7fa1889b --- /dev/null +++ b/fml/src/main/java/cpw/mods/fml/client/IModGuiFactory.java @@ -0,0 +1,127 @@ +package cpw.mods.fml.client; + +import java.util.Set; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; + +public interface IModGuiFactory { + /** + * Called when instantiated to initialize with the active minecraft instance. + * + * @param minecraftInstance the instance + */ + public void initialize(Minecraft minecraftInstance); + /** + * Return the name of a class extending {@link GuiScreen}. This class will + * be instantiated when the "config" button is pressed in the mod list. It will + * have a single argument constructor - the "parent" screen, the same as all + * Minecraft GUIs. The expected behaviour is that this screen will replace the + * "mod list" screen completely, and will return to the mod list screen through + * the parent link, once the appropriate action is taken from the config screen. + * + * A null from this method indicates that the mod does not provide a "config" + * button GUI screen, and the config button will be hidden/disabled. + * + * This config GUI is anticipated to provide configuration to the mod in a friendly + * visual way. It should not be abused to set internals such as IDs (they're gonna + * keep disappearing anyway), but rather, interesting behaviours. This config GUI + * is never run when a server game is running, and should be used to configure + * desired behaviours that affect server state. Costs, mod game modes, stuff like that + * can be changed here. + * + * @return A class that will be instantiated on clicks on the config button + * or null if no GUI is desired. + */ + public Class mainConfigGuiClass(); + + + /** + * Return a list of the "runtime" categories this mod wishes to populate with + * GUI elements. + * + * Runtime categories are created on demand and organized in a 'lite' tree format. + * The parent represents the parent node in the tree. There is one special parent + * 'Help' that will always list first, and is generally meant to provide Help type + * content for mods. The remaining parents will sort alphabetically, though + * this may change if there is a lot of alphabetic abuse. "AAA" is probably never a valid + * category parent. + * + * Runtime configuration itself falls into two flavours: in-game help, which is + * generally non interactive except for the text it wishes to show, and client-only + * affecting behaviours. This would include things like toggling minimaps, or cheat modes + * or anything NOT affecting the behaviour of the server. Please don't abuse this to + * change the state of the server in any way, this is intended to behave identically + * when the server is local or remote. + * + * @return the set of options this mod wishes to have available, or empty if none + */ + public Set runtimeGuiCategories(); + + /** + * Return an instance of a {@link RuntimeOptionGuiHandler} that handles painting the + * right hand side option screen for the specified {@link RuntimeOptionCategoryElement}. + * + * @param element The element we wish to paint for + * @return The Handler for painting it + */ + public RuntimeOptionGuiHandler getHandlerFor(RuntimeOptionCategoryElement element); + + /** + * Represents an option category and entry in the runtime gui options list. + * + * @author cpw + * + */ + public static class RuntimeOptionCategoryElement { + public final String parent; + public final String child; + + public RuntimeOptionCategoryElement(String parent, String child) + { + this.parent = parent; + this.child = child; + } + } + + /** + * Responsible for painting the mod specific section of runtime options GUI for a particular category + * + * @author cpw + * + */ + public interface RuntimeOptionGuiHandler { + /** + * Called to add widgets to the screen, such as buttons. + * GUI identifier numbers should start at 100 and increase. + * The callback will be through {@link #actionCallback(int)} + * + * @param x X + * @param y Y + * @param w width + * @param h height + */ + public void addWidgets(int x, int y, int w, int h); + + /** + * Called to paint the rectangle specified. + * @param x X + * @param y Y + * @param w width + * @param h height + */ + public void paint(int x, int y, int w, int h); + + /** + * Called if a widget with id >= 100 is fired. + * + * @param actionId the actionId of the firing widget + */ + public void actionCallback(int actionId); + + /** + * Called when this handler is about to go away (probably replaced by another one, or closing the + * option screen) + */ + public void close(); + } +} diff --git a/fml/src/main/java/cpw/mods/fml/common/DummyModContainer.java b/fml/src/main/java/cpw/mods/fml/common/DummyModContainer.java index f31a7a1e11..e603187248 100644 --- a/fml/src/main/java/cpw/mods/fml/common/DummyModContainer.java +++ b/fml/src/main/java/cpw/mods/fml/common/DummyModContainer.java @@ -186,4 +186,10 @@ public class DummyModContainer implements ModContainer { return Disableable.NEVER; } + + @Override + public String getGuiClassName() + { + return null; + } } diff --git a/fml/src/main/java/cpw/mods/fml/common/FMLContainer.java b/fml/src/main/java/cpw/mods/fml/common/FMLContainer.java index a55cbe82db..f4064423de 100644 --- a/fml/src/main/java/cpw/mods/fml/common/FMLContainer.java +++ b/fml/src/main/java/cpw/mods/fml/common/FMLContainer.java @@ -215,4 +215,10 @@ public class FMLContainer extends DummyModContainer implements WorldAccessContai { return getSource().isDirectory() ? FMLFolderResourcePack.class : FMLFileResourcePack.class; } + + @Override + public String getGuiClassName() + { + return "cpw.mods.fml.client.FMLConfigGuiFactory"; + } } diff --git a/fml/src/main/java/cpw/mods/fml/common/FMLModContainer.java b/fml/src/main/java/cpw/mods/fml/common/FMLModContainer.java index 3729f43ba0..50b24aef4b 100644 --- a/fml/src/main/java/cpw/mods/fml/common/FMLModContainer.java +++ b/fml/src/main/java/cpw/mods/fml/common/FMLModContainer.java @@ -616,4 +616,10 @@ public class FMLModContainer implements ModContainer { return disableability; } + + @Override + public String getGuiClassName() + { + return (String) descriptor.get("guiFactory"); + } } diff --git a/fml/src/main/java/cpw/mods/fml/common/InjectedModContainer.java b/fml/src/main/java/cpw/mods/fml/common/InjectedModContainer.java index b02e2545b0..f4acf416f8 100644 --- a/fml/src/main/java/cpw/mods/fml/common/InjectedModContainer.java +++ b/fml/src/main/java/cpw/mods/fml/common/InjectedModContainer.java @@ -174,4 +174,10 @@ public class InjectedModContainer implements ModContainer { return wrappedContainer.canBeDisabled(); } + + @Override + public String getGuiClassName() + { + return wrappedContainer.getGuiClassName(); + } } diff --git a/fml/src/main/java/cpw/mods/fml/common/Mod.java b/fml/src/main/java/cpw/mods/fml/common/Mod.java index 9d9ebf18ba..cab2511ade 100644 --- a/fml/src/main/java/cpw/mods/fml/common/Mod.java +++ b/fml/src/main/java/cpw/mods/fml/common/Mod.java @@ -17,6 +17,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import cpw.mods.fml.client.IModGuiFactory; import cpw.mods.fml.common.event.FMLEvent; import cpw.mods.fml.common.event.FMLFingerprintViolationEvent; import cpw.mods.fml.common.event.FMLInitializationEvent; @@ -149,10 +150,20 @@ public @interface Mod * (minimap mods, graphical tweak mods) then you can set true here and receive the FMLDeactivationEvent to perform deactivation * tasks. * This does not affect administrative disabling through the system property fml.modStates or the config file fmlModState.properties. + * The mod will only be deactivated outside of a running game world - FML will never allow mod deactivation whilst a game server + * is running. * - * @return if I can be deactivated whilst the game is running. + * @return if this mod can be deactivated whilst the game is open. */ boolean canBeDeactivated() default false; + + /** + * An optional GUI factory for this mod. This is the name of a class implementing {@link IModGuiFactory} that will be instantiated + * on the client side, and will have certain configuration/options guis requested from it. + * + * @return The name of a class implementing {@link IModGuiFactory} + */ + String guiFactory() default ""; /** * A list of custom properties for this mod. Completely up to the mod author if/when they * want to put anything in here. diff --git a/fml/src/main/java/cpw/mods/fml/common/ModContainer.java b/fml/src/main/java/cpw/mods/fml/common/ModContainer.java index 47cc223d11..164178880e 100644 --- a/fml/src/main/java/cpw/mods/fml/common/ModContainer.java +++ b/fml/src/main/java/cpw/mods/fml/common/ModContainer.java @@ -143,4 +143,6 @@ public interface ModContainer Map getSharedModDescriptor(); Disableable canBeDisabled(); + + String getGuiClassName(); }