diff --git a/conf/decompile/minecraft_srg_forge-1.7.2.jar b/conf/decompile/minecraft_srg_forge-1.7.2.jar new file mode 100644 index 0000000..70dc36a --- /dev/null +++ b/conf/decompile/minecraft_srg_forge-1.7.2.jar Binary files differ diff --git a/src/main/java/cpw/mods/fml/client/FMLConfigGuiFactory.java b/src/main/java/cpw/mods/fml/client/FMLConfigGuiFactory.java index 83697c8..cf220bc 100644 --- a/src/main/java/cpw/mods/fml/client/FMLConfigGuiFactory.java +++ b/src/main/java/cpw/mods/fml/client/FMLConfigGuiFactory.java @@ -1,50 +1,88 @@ package cpw.mods.fml.client; +import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.regex.Pattern; + import com.google.common.collect.ImmutableSet; + +import cpw.mods.fml.client.config.ConfigGuiType; +import cpw.mods.fml.client.config.DummyConfigElement; +import cpw.mods.fml.client.config.GuiConfig; +import cpw.mods.fml.client.config.IConfigElement; +import cpw.mods.fml.client.config.DummyConfigElement.DummyCategoryElement; +import cpw.mods.fml.client.config.DummyConfigElement.DummyListElement; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; 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 class FMLConfigGuiFactory implements IModGuiFactory +{ + public static class FMLConfigGuiScreen extends GuiConfig + { public FMLConfigGuiScreen(GuiScreen parent) { - this.parent = parent; + super(parent, getConfigElements(), "FML", false, false, I18n.format("fml.config.sample.title")); } - - @SuppressWarnings("unchecked") - @Override - public void initGui() + + private static List getConfigElements() { - this.buttonList.add(new GuiButton(1, this.width / 2 - 75, this.height - 38, I18n.format("gui.done"))); - } + List list = new ArrayList(); + List listsList = new ArrayList(); + List stringsList = new ArrayList(); + List numbersList = new ArrayList(); + Pattern commaDelimitedPattern = Pattern.compile("([A-Za-z]+((,){1}( )*|$))+?"); + + // Top Level Settings + list.add(new DummyConfigElement("imABoolean", true, ConfigGuiType.BOOLEAN, "fml.config.sample.imABoolean").setRequiresMcRestart(true)); + list.add(new DummyConfigElement("imAnInteger", 42, ConfigGuiType.INTEGER, "fml.config.sample.imAnInteger", -1, 256).setRequiresMcRestart(true)); + list.add(new DummyConfigElement("imADouble", 42.4242D, ConfigGuiType.DOUBLE, "fml.config.sample.imADouble", -1.0D, 256.256D).setRequiresMcRestart(true)); + list.add(new DummyConfigElement("imAString", "http://www.montypython.net/scripts/string.php", ConfigGuiType.STRING, "fml.config.sample.imAString").setRequiresMcRestart(true)); + + // Lists category + listsList.add(new DummyListElement("booleanList", new Boolean[] {true, false, true, false, true, false, true, false}, ConfigGuiType.BOOLEAN, "fml.config.sample.booleanList")); + listsList.add(new DummyListElement("booleanListFixed", new Boolean[] {true, false, true, false, true, false, true, false}, ConfigGuiType.BOOLEAN, "fml.config.sample.booleanListFixed", true)); + listsList.add(new DummyListElement("booleanListMax", new Boolean[] {true, false, true, false, true, false, true, false}, ConfigGuiType.BOOLEAN, "fml.config.sample.booleanListMax", 10)); + listsList.add(new DummyListElement("doubleList", new Double[] {0.0D, 1.1D, 2.2D, 3.3D, 4.4D, 5.5D, 6.6D, 7.7D, 8.8D, 9.9D}, ConfigGuiType.DOUBLE, "fml.config.sample.doubleList")); + listsList.add(new DummyListElement("doubleListFixed", new Double[] {0.0D, 1.1D, 2.2D, 3.3D, 4.4D, 5.5D, 6.6D, 7.7D, 8.8D, 9.9D}, ConfigGuiType.DOUBLE, "fml.config.sample.doubleListFixed", true)); + listsList.add(new DummyListElement("doubleListMax", new Double[] {0.0D, 1.1D, 2.2D, 3.3D, 4.4D, 5.5D, 6.6D, 7.7D, 8.8D, 9.9D}, ConfigGuiType.DOUBLE, "fml.config.sample.doubleListMax", 15)); + listsList.add(new DummyListElement("doubleListBounded", new Double[] {0.0D, 1.1D, 2.2D, 3.3D, 4.4D, 5.5D, 6.6D, 7.7D, 8.8D, 9.9D}, ConfigGuiType.DOUBLE, "fml.config.sample.doubleListBounded", -1.0D, 10.0D)); + listsList.add(new DummyListElement("integerList", new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, ConfigGuiType.INTEGER, "fml.config.sample.integerList")); + listsList.add(new DummyListElement("integerListFixed", new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, ConfigGuiType.INTEGER, "fml.config.sample.integerListFixed", true)); + listsList.add(new DummyListElement("integerListMax", new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, ConfigGuiType.INTEGER, "fml.config.sample.integerListMax", 15)); + listsList.add(new DummyListElement("integerListBounded", new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, ConfigGuiType.INTEGER, "fml.config.sample.integerListBounded", -1, 10)); + listsList.add(new DummyListElement("stringList", new String[] {"An", "array", "of", "string", "values"}, ConfigGuiType.STRING, "fml.config.sample.stringList")); + listsList.add(new DummyListElement("stringListFixed", new String[] {"A", "fixed", "length", "array", "of", "string", "values"}, ConfigGuiType.STRING, "fml.config.sample.stringListFixed", true)); + listsList.add(new DummyListElement("stringListMax", new String[] {"An", "array", "of", "string", "values", "with", "a", "max", "length", "of", "15"}, ConfigGuiType.STRING, "fml.config.sample.stringListMax", 15)); + listsList.add(new DummyListElement("stringListPattern", new String[] {"Valid", "Not Valid", "Is, Valid", "Comma, Separated, Value"}, ConfigGuiType.STRING, "fml.config.sample.stringListPattern", commaDelimitedPattern)); + + list.add(new DummyCategoryElement("lists", "fml.config.sample.ctgy.lists", listsList)); + + // Strings category + stringsList.add(new DummyConfigElement("basicString", "Just a regular String value, anything goes.", ConfigGuiType.STRING, "fml.config.sample.basicString")); + stringsList.add(new DummyConfigElement("cycleString", "this", ConfigGuiType.STRING, "fml.config.sample.cycleString", new String[] {"this", "property", "cycles", "through", "a", "list", "of", "valid", "choices"})); + stringsList.add(new DummyConfigElement("patternString", "only, comma, separated, words, can, be, entered, in, this, box", ConfigGuiType.STRING, "fml.config.sample.patternString", commaDelimitedPattern)); + stringsList.add(new DummyConfigElement("chatColorPicker", "c", ConfigGuiType.COLOR, "fml.config.sample.chatColorPicker", new String[] {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"})); + stringsList.add(new DummyConfigElement("modIDSelector", "FML", ConfigGuiType.MOD_ID, "fml.config.sample.modIDSelector")); + + list.add(new DummyCategoryElement("strings", "fml.config.sample.ctgy.strings", stringsList)); + + // Numbers category + numbersList.add((new DummyConfigElement("basicInteger", 42, ConfigGuiType.INTEGER, "fml.config.sample.basicInteger"))); + numbersList.add((new DummyConfigElement("boundedInteger", 42, ConfigGuiType.INTEGER, "fml.config.sample.boundedInteger", -1, 256))); + numbersList.add(new DummyConfigElement("basicDouble", 42.4242D, ConfigGuiType.DOUBLE, "fml.config.sample.basicDouble")); + numbersList.add(new DummyConfigElement("boundedDouble", 42.4242D, ConfigGuiType.DOUBLE, "fml.config.sample.boundedDouble", -1.0D, 256.256D)); - @Override - protected void actionPerformed(GuiButton par1GuiButton) - { - if (par1GuiButton.enabled && par1GuiButton.id == 1) - { - FMLClientHandler.instance().showGuiScreen(parent); - } + list.add(new DummyCategoryElement("numbers", "fml.config.sample.ctgy.numbers", numbersList)); + + return list; } - - @Override - public void drawScreen(int par1, int par2, float par3) - { - this.drawDefaultBackground(); - this.drawCenteredString(this.fontRendererObj, "Forge Mod Loader test config screen", this.width / 2, 40, 0xFFFFFF); - super.drawScreen(par1, par2, par3); - } - } - @SuppressWarnings("unused") private Minecraft minecraft; @Override public void initialize(Minecraft minecraftInstance) diff --git a/src/main/java/cpw/mods/fml/client/config/ConfigGuiType.java b/src/main/java/cpw/mods/fml/client/config/ConfigGuiType.java new file mode 100644 index 0000000..a788c54 --- /dev/null +++ b/src/main/java/cpw/mods/fml/client/config/ConfigGuiType.java @@ -0,0 +1,24 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2014 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors (this class): + * bspkrs - implementation + */ + +package cpw.mods.fml.client.config; + +public enum ConfigGuiType +{ + STRING, + INTEGER, + BOOLEAN, + DOUBLE, + COLOR, + MOD_ID, + CONFIG_CATEGORY; +} \ No newline at end of file diff --git a/src/main/java/cpw/mods/fml/client/config/DummyConfigElement.java b/src/main/java/cpw/mods/fml/client/config/DummyConfigElement.java new file mode 100644 index 0000000..aaf1798 --- /dev/null +++ b/src/main/java/cpw/mods/fml/client/config/DummyConfigElement.java @@ -0,0 +1,418 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2014 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors (this class): + * bspkrs - implementation + */ + +package cpw.mods.fml.client.config; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import cpw.mods.fml.client.config.GuiConfigEntries.IConfigEntry; +import cpw.mods.fml.client.config.GuiEditArrayEntries.IArrayEntry; + +import net.minecraft.client.resources.I18n; + + +/** + * This class's main purpose is to provide the necessary objects for a sample Config GUI for FML, although + * there may be practical uses for the objects defined here such as using the DummyCategoryElement object as a + * wrapper for a custom IGuiConfigListEntry object that opens a special screen. + * + * @author bspkrs + */ +public class DummyConfigElement implements IConfigElement +{ + protected boolean isProperty = true; + protected boolean isList = false; + protected ConfigGuiType type; + protected String name; + protected String langKey; + protected Object value; + protected Object defaultValue; + protected T[] values; + protected T[] defaultValues; + protected String[] validValues; + protected Pattern validStringPattern; + protected T minValue; + protected T maxValue; + protected boolean requiresWorldRestart = false; + protected boolean requiresMcRestart = false; + protected boolean isListFixedLength = false; + protected int maxListLength = -1; + protected List childElements; + protected Class configEntryClass; + protected Class arrayEntryClass; + + /** + * This class provides a Dummy Category IConfigElement. It can be used to define a custom list of GUI entries that will + * appear on the child screen or to specify a custom IGuiConfigListEntryfor a special category. + */ + public static class DummyCategoryElement extends DummyConfigElement + { + public DummyCategoryElement(String name, String langKey, List childElements) + { + this(name, langKey, childElements, (Class) null); + } + + public DummyCategoryElement(String name, String langKey, Class customListEntryClass) + { + this(name, langKey, new ArrayList(), customListEntryClass); + } + + public DummyCategoryElement(String name, String langKey, List childElements, Class customListEntryClass) + { + super(name, (T) null, ConfigGuiType.CONFIG_CATEGORY, langKey); + this.childElements = childElements; + this.configEntryClass = customListEntryClass; + isProperty = false; + } + } + + /** + * This class provides a dummy array-type IConfigElement. + */ + public static class DummyListElement extends DummyConfigElement + { + public DummyListElement(String name, T[] defaultValues, ConfigGuiType type, String langKey, boolean isListFixedLength, int maxListLength, Pattern validStringPattern, T minValue, T maxValue) + { + super(name, (T) null, type, langKey, minValue, maxValue); + this.defaultValues = defaultValues; + this.values = defaultValues; + this.isListFixedLength = isListFixedLength; + this.maxListLength = maxListLength; + this.validStringPattern = validStringPattern; + isList = true; + } + + public DummyListElement(String name, T[] defaultValues, ConfigGuiType type, String langKey) + { + this(name, defaultValues, type, langKey, false, -1, (Pattern) null, (T) null, (T) null); + } + + public DummyListElement(String name, T[] defaultValues, ConfigGuiType type, String langKey, boolean isListFixedLength) + { + this(name, defaultValues, type, langKey, isListFixedLength, -1, (Pattern) null, (T) null, (T) null); + } + + public DummyListElement(String name, T[] defaultValues, ConfigGuiType type, String langKey, int maxListLength) + { + this(name, defaultValues, type, langKey, false, maxListLength, (Pattern) null, (T) null, (T) null); + } + + public DummyListElement(String name, T[] defaultValues, ConfigGuiType type, String langKey, T minValue, T maxValue) + { + this(name, defaultValues, type, langKey, false, -1, (Pattern) null, minValue, maxValue); + } + + public DummyListElement(String name, T[] defaultValues, ConfigGuiType type, String langKey, boolean isListFixedLength, T minValue, T maxValue) + { + this(name, defaultValues, type, langKey, isListFixedLength, -1, (Pattern) null, minValue, maxValue); + } + + public DummyListElement(String name, T[] defaultValues, ConfigGuiType type, String langKey, int maxListLength, T minValue, T maxValue) + { + this(name, defaultValues, type, langKey, false, maxListLength, (Pattern) null, minValue, maxValue); + } + + public DummyListElement(String name, T[] defaultValues, ConfigGuiType type, String langKey, boolean isListFixedLength, int maxListLength, T minValue, T maxValue) + { + this(name, defaultValues, type, langKey, isListFixedLength, maxListLength, (Pattern) null, minValue, maxValue); + } + + public DummyListElement(String name, T[] defaultValues, ConfigGuiType type, String langKey, Pattern validStringPattern) + { + this(name, defaultValues, type, langKey, false, -1, validStringPattern, (T) null, (T) null); + } + + public DummyListElement(String name, T[] defaultValues, ConfigGuiType type, String langKey, boolean isListFixedLength, Pattern validStringPattern) + { + this(name, defaultValues, type, langKey, isListFixedLength, -1, validStringPattern, (T) null, (T) null); + } + + public DummyListElement(String name, T[] defaultValues, ConfigGuiType type, String langKey, int maxListLength, Pattern validStringPattern) + { + this(name, defaultValues, type, langKey, false, maxListLength, validStringPattern, (T) null, (T) null); + } + + public DummyListElement setCustomEditListEntryClass(Class clazz) + { + this.arrayEntryClass = clazz; + return this; + } + + @Override + public Object getDefault() + { + return Arrays.toString(this.defaultValues); + } + } + + public DummyConfigElement(String name, T defaultValue, ConfigGuiType type, String langKey, String[] validValues, Pattern validStringPattern, T minValue, T maxValue) + { + this.name = name; + this.defaultValue = defaultValue; + this.value = defaultValue; + this.type = type; + this.langKey = langKey; + this.validValues = validValues; + this.validStringPattern = validStringPattern; + if (minValue == null) + { + if (type == ConfigGuiType.INTEGER) + this.minValue = (T) (Integer) Integer.MIN_VALUE; + else if (type == ConfigGuiType.DOUBLE) + this.minValue = (T) (Double) (-Double.MAX_VALUE); + } + else + this.minValue = minValue; + if (maxValue == null) + { + if (type == ConfigGuiType.INTEGER) + this.maxValue = (T) (Integer) Integer.MAX_VALUE; + else if (type == ConfigGuiType.DOUBLE) + this.maxValue = (T) (Double) Double.MAX_VALUE; + } + else + this.maxValue = maxValue; + } + + public DummyConfigElement(String name, T defaultValue, ConfigGuiType type, String langKey, Pattern validStringPattern) + { + this(name, defaultValue, type, langKey, (String[]) null, validStringPattern, (T) null, (T) null); + } + + public DummyConfigElement(String name, T defaultValue, ConfigGuiType type, String langKey, String[] validValues) + { + this(name, defaultValue, type, langKey, validValues, (Pattern) null, (T) null, (T) null); + } + + public DummyConfigElement(String name, T defaultValue, ConfigGuiType type, String langKey) + { + this(name, defaultValue, type, langKey, (String[]) null, (Pattern) null, (T) null, (T) null); + } + + public DummyConfigElement(String name, T defaultValue, ConfigGuiType type, String langKey, T minValue, T maxValue) + { + this(name, defaultValue, type, langKey, (String[]) null, (Pattern) null, minValue, maxValue); + } + + public DummyConfigElement setCustomListEntryClass(Class clazz) + { + this.configEntryClass = clazz; + return this; + } + + @Override + public boolean isProperty() + { + return isProperty; + } + + public IConfigElement setConfigEntryClass(Class clazz) + { + this.configEntryClass = clazz; + return this; + } + + @Override + public Class getConfigEntryClass() + { + return configEntryClass; + } + + public IConfigElement setArrayEntryClass(Class clazz) + { + this.arrayEntryClass = clazz; + return this; + } + + @Override + public Class getArrayEntryClass() + { + return arrayEntryClass; + } + + @Override + public String getName() + { + return name; + } + + @Override + public String getQualifiedName() + { + return name; + } + + @Override + public String getLanguageKey() + { + return langKey; + } + + @Override + public String getComment() + { + return I18n.format(langKey + ".tooltip"); + } + + @Override + public List getChildElements() + { + return childElements; + } + + @Override + public ConfigGuiType getType() + { + return type; + } + + @Override + public boolean isList() + { + return isList; + } + + @Override + public boolean isListLengthFixed() + { + return this.isListFixedLength; + } + + @Override + public int getMaxListLength() + { + return this.maxListLength; + } + + @Override + public boolean isDefault() + { + if (isProperty) + { + if (!isList) + { + if (value != null) + return value.equals(defaultValue); + else + return defaultValue == null; + } + else + { + return Arrays.deepEquals(values, defaultValues); + } + } + + return true; + } + + @Override + public Object getDefault() + { + return defaultValue; + } + + @Override + public T[] getDefaults() + { + return defaultValues; + } + + @Override + public void setToDefault() + { + if (isList) + this.values = Arrays.copyOf(this.defaultValues, this.defaultValues.length); + else + this.value = defaultValue; + } + + public IConfigElement setRequiresWorldRestart(boolean requiresWorldRestart) + { + this.requiresWorldRestart = requiresWorldRestart; + return this; + } + + @Override + public boolean requiresWorldRestart() + { + return requiresWorldRestart; + } + + @Override + public boolean showInGui() + { + return true; + } + + public IConfigElement setRequiresMcRestart(boolean requiresMcRestart) + { + this.requiresMcRestart = this.requiresWorldRestart = requiresMcRestart; + return this; + } + + @Override + public boolean requiresMcRestart() + { + return requiresMcRestart; + } + + @Override + public String[] getValidValues() + { + return validValues; + } + + @Override + public Pattern getValidationPattern() + { + return validStringPattern; + } + + @Override + public Object get() + { + return value; + } + + @Override + public T[] getList() + { + return values; + } + + @Override + public void set(T value) + { + defaultValue = value; + } + + @Override + public void set(T[] aVal) + { + defaultValues = aVal; + } + + @Override + public T getMinValue() + { + return minValue; + } + + @Override + public T getMaxValue() + { + return maxValue; + } +} \ No newline at end of file diff --git a/src/main/java/cpw/mods/fml/client/config/GuiButtonExt.java b/src/main/java/cpw/mods/fml/client/config/GuiButtonExt.java new file mode 100644 index 0000000..9a73980 --- /dev/null +++ b/src/main/java/cpw/mods/fml/client/config/GuiButtonExt.java @@ -0,0 +1,79 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2014 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors (this class): + * bspkrs - implementation + */ + +package cpw.mods.fml.client.config; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; + +import org.lwjgl.opengl.GL11; + +/** + * This class provides a button that fixes several bugs present in the vanilla GuiButton drawing code. + * The gist of it is that it allows buttons of any size without gaps in the graphics and with the + * borders drawn properly. It also prevents button text from extending out of the sides of the button by + * trimming the end of the string and adding an ellipsis.

+ * + * The code that handles drawing the button is in GuiUtils. + * + * @author bspkrs + */ +public class GuiButtonExt extends GuiButton +{ + public GuiButtonExt(int id, int xPos, int yPos, String displayString) + { + super(id, xPos, yPos, displayString); + } + + public GuiButtonExt(int id, int xPos, int yPos, int width, int height, String displayString) + { + super(id, xPos, yPos, width, height, displayString); + } + + /** + * Draws this button to the screen. + */ + @Override + public void drawButton(Minecraft mc, int mouseX, int mouseY) + { + if (this.visible) + { + this.field_146123_n = mouseX >= this.xPosition && mouseY >= this.yPosition && mouseX < this.xPosition + this.width && mouseY < this.yPosition + this.height; + int k = this.getHoverState(this.field_146123_n); + GuiUtils.drawContinuousTexturedBox(buttonTextures, this.xPosition, this.yPosition, 0, 46 + k * 20, this.width, this.height, 200, 20, 2, 3, 2, 2, this.zLevel); + this.mouseDragged(mc, mouseX, mouseY); + int color = 14737632; + + if (packedFGColour != 0) + { + color = packedFGColour; + } + else if (!this.enabled) + { + color = 10526880; + } + else if (this.field_146123_n) + { + color = 16777120; + } + + String buttonText = this.displayString; + int strWidth = mc.fontRenderer.getStringWidth(buttonText); + int ellipsisWidth = mc.fontRenderer.getStringWidth("..."); + + if (strWidth > width - 6 && strWidth > ellipsisWidth) + buttonText = mc.fontRenderer.trimStringToWidth(buttonText, width - 6 - ellipsisWidth).trim() + "..."; + + this.drawCenteredString(mc.fontRenderer, buttonText, this.xPosition + this.width / 2, this.yPosition + (this.height - 8) / 2, color); + } + } +} \ No newline at end of file diff --git a/src/main/java/cpw/mods/fml/client/config/GuiCheckBox.java b/src/main/java/cpw/mods/fml/client/config/GuiCheckBox.java new file mode 100644 index 0000000..a0ead1a --- /dev/null +++ b/src/main/java/cpw/mods/fml/client/config/GuiCheckBox.java @@ -0,0 +1,84 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2014 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors (this class): + * bspkrs - implementation + */ + +package cpw.mods.fml.client.config; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; + +/** + * This class provides a checkbox style control. + * + * @author bspkrs + */ +public class GuiCheckBox extends GuiButton +{ + private boolean isChecked; + private int boxWidth; + + public GuiCheckBox(int id, int xPos, int yPos, String displayString, boolean isChecked) + { + super(id, xPos, yPos, displayString); + this.isChecked = isChecked; + this.boxWidth = 11; + this.height = 11; + this.width = this.boxWidth + 2 + Minecraft.getMinecraft().fontRenderer.getStringWidth(displayString); + } + + @Override + public void drawButton(Minecraft mc, int mouseX, int mouseY) + { + if (this.visible) + { + this.field_146123_n = mouseX >= this.xPosition && mouseY >= this.yPosition && mouseX < this.xPosition + this.boxWidth && mouseY < this.yPosition + this.height; + GuiUtils.drawContinuousTexturedBox(buttonTextures, this.xPosition, this.yPosition, 0, 46, this.boxWidth, this.height, 200, 20, 2, 3, 2, 2, this.zLevel); + this.mouseDragged(mc, mouseX, mouseY); + int color = 14737632; + + if (packedFGColour != 0) + { + color = packedFGColour; + } + else if (!this.enabled) + { + color = 10526880; + } + + if (this.isChecked) + this.drawCenteredString(mc.fontRenderer, "x", this.xPosition + this.boxWidth / 2 + 1, this.yPosition + 1, 14737632); + + this.drawString(mc.fontRenderer, displayString, xPosition + this.boxWidth + 2, yPosition + 2, color); + } + } + + @Override + public boolean mousePressed(Minecraft p_146116_1_, int p_146116_2_, int p_146116_3_) + { + if (this.enabled && this.visible && p_146116_2_ >= this.xPosition && p_146116_3_ >= this.yPosition && p_146116_2_ < this.xPosition + this.width && p_146116_3_ < this.yPosition + this.height) + { + this.isChecked = !this.isChecked; + return true; + } + + return false; + } + + public boolean isChecked() + { + return this.isChecked; + } + + public void setIsChecked(boolean isChecked) + { + this.isChecked = isChecked; + } +} \ No newline at end of file diff --git a/src/main/java/cpw/mods/fml/client/config/GuiConfig.java b/src/main/java/cpw/mods/fml/client/config/GuiConfig.java new file mode 100644 index 0000000..69f2864 --- /dev/null +++ b/src/main/java/cpw/mods/fml/client/config/GuiConfig.java @@ -0,0 +1,347 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2014 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors (this class): + * bspkrs - implementation + */ + +package cpw.mods.fml.client.config; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiMainMenu; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.GuiYesNo; +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.client.resources.I18n; +import net.minecraft.util.ChatComponentText; + +import org.lwjgl.input.Keyboard; + +import static cpw.mods.fml.client.config.GuiUtils.RESET_CHAR; +import static cpw.mods.fml.client.config.GuiUtils.UNDO_CHAR; + +import cpw.mods.fml.client.config.GuiConfigEntries.IConfigEntry; +import cpw.mods.fml.client.event.ConfigChangedEvent; +import cpw.mods.fml.client.event.ConfigChangedEvent.OnConfigChangedEvent; +import cpw.mods.fml.client.event.ConfigChangedEvent.PostConfigChangedEvent; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.eventhandler.Event.Result; + +/** + * This class is the base GuiScreen for all config GUI screens. It can be extended by mods to provide the top-level config screen + * that will be called when the Config button is clicked from the Main Menu Mods list. + * + * @author bspkrs + */ +public class GuiConfig extends GuiScreen +{ + /** + * A reference to the screen object that created this. Used for navigating between screens. + */ + public final GuiScreen parentScreen; + public String title = "Config GUI"; + public String titleLine2; + public final List configElements; + public final List initEntries; + public GuiConfigEntries entryList; + private GuiButtonExt btnDefaultAll; + private GuiButtonExt btnUndoAll; + private GuiCheckBox chkApplyGlobally; + public final String modID; + /** + * When set to a non-null value the OnConfigChanged and PostConfigChanged events will be posted when the Done button is pressed + * if any configElements were changed (includes child screens). If not defined, the events will be posted if the parent gui is null + * or if the parent gui is not an instance of GuiConfig. + */ + public final String configID; + public final boolean isWorldRunning; + public final boolean allRequireWorldRestart; + public final boolean allRequireMcRestart; + public boolean needsRefresh = true; + private HoverChecker undoHoverChecker; + private HoverChecker resetHoverChecker; + private HoverChecker checkBoxHoverChecker; + + /** + * GuiConfig constructor that will use ConfigChangedEvent when editing is concluded. If a non-null value is passed for configID, + * the OnConfigChanged and PostConfigChanged events will be posted when the Done button is pressed if any configElements were changed + * (includes child screens). If configID is not defined, the events will be posted if the parent gui is null or if the parent gui + * is not an instance of GuiConfig. + * + * @param parentScreen the parent GuiScreen object + * @param configElements a List of IConfigProperty objects + * @param areAllPropsHotLoadable send true if every property on this screen is able to be modified on the fly while a world is running + * @param modID the mod ID for the mod whose config settings will be edited + * @param configID an identifier that will be passed to the OnConfigChanged and PostConfigChanged events. Setting this value will force + * the save action to be called when the Done button is pressed on this screen if any configElements were changed. + * @param allRequireWorldRestart send true if all configElements on this screen require a world restart + * @param title the desired title for this screen. For consistency it is recommended that you pass the path of the config file being + * edited. + */ + public GuiConfig(GuiScreen parentScreen, List configElements, String modID, String configID, + boolean allRequireWorldRestart, boolean allRequireMcRestart, String title) + { + this(parentScreen, configElements, modID, configID, allRequireWorldRestart, allRequireMcRestart, title, null); + } + + /** + * GuiConfig constructor that will use ConfigChangedEvent when editing is concluded. This constructor passes null for configID. + * If configID is not defined, the events will be posted if the parent gui is null or if the parent gui is not an instance of GuiConfig. + * + * @param parentScreen the parent GuiScreen object + * @param configElements a List of IConfigProperty objects + * @param areAllPropsHotLoadable send true if every property on this screen is able to be modified on the fly while a world is running + * @param modID the mod ID for the mod whose config settings will be edited + * @param allRequireWorldRestart send true if all configElements on this screen require a world restart + * @param title the desired title for this screen. For consistency it is recommended that you pass the path of the config file being + * edited. + */ + public GuiConfig(GuiScreen parentScreen, List configElements, String modID, + boolean allRequireWorldRestart, boolean allRequireMcRestart, String title) + { + this(parentScreen, configElements, modID, null, allRequireWorldRestart, allRequireMcRestart, title, null); + } + + /** + * GuiConfig constructor that will use ConfigChangedEvent when editing is concluded. This constructor passes null for configID. + * If configID is not defined, the events will be posted if the parent gui is null or if the parent gui is not an instance of GuiConfig. + * + * @param parentScreen the parent GuiScreen object + * @param configElements a List of IConfigProperty objects + * @param modID the mod ID for the mod whose config settings will be edited + * @param allRequireWorldRestart send true if all configElements on this screen require a world restart + * @param title the desired title for this screen. For consistency it is recommended that you pass the path of the config file being + * edited. + * @param titleLine2 the desired title second line for this screen. Typically this is used to send the category name of the category + * currently being edited. + */ + public GuiConfig(GuiScreen parentScreen, List configElements, String modID, + boolean allRequireWorldRestart, boolean allRequireMcRestart, String title, String titleLine2) + { + this(parentScreen, configElements, modID, null, allRequireWorldRestart, allRequireMcRestart, title, titleLine2); + } + + /** + * GuiConfig constructor that will use ConfigChangedEvent when editing is concluded. titleLine2 is specified in this constructor. + * If a non-null value is passed for configID, the OnConfigChanged and PostConfigChanged events will be posted when the Done button is + * pressed if any configElements were changed (includes child screens). If configID is not defined, the events will be posted if the parent + * gui is null or if the parent gui is not an instance of GuiConfig. + * + * @param parentScreen the parent GuiScreen object + * @param configElements a List of IConfigProperty objects + * @param modID the mod ID for the mod whose config settings will be edited + * @param configID an identifier that will be passed to the OnConfigChanged and PostConfigChanged events + * @param allRequireWorldRestart send true if all configElements on this screen require a world restart + * @param title the desired title for this screen. For consistency it is recommended that you pass the path of the config file being + * edited. + * @param titleLine2 the desired title second line for this screen. Typically this is used to send the category name of the category + * currently being edited. + */ + public GuiConfig(GuiScreen parentScreen, List configElements, String modID, String configID, + boolean allRequireWorldRestart, boolean allRequireMcRestart, String title, String titleLine2) + { + this.mc = Minecraft.getMinecraft(); + this.parentScreen = parentScreen; + this.configElements = configElements; + this.entryList = new GuiConfigEntries(this, mc); + this.initEntries = new ArrayList(entryList.listEntries); + this.allRequireWorldRestart = allRequireWorldRestart; + this.allRequireMcRestart = allRequireMcRestart; + this.modID = modID; + this.configID = configID; + this.isWorldRunning = mc.theWorld != null; + if (title != null) + this.title = title; + this.titleLine2 = titleLine2; + if (this.titleLine2 != null && this.titleLine2.startsWith(" > ")) + this.titleLine2 = this.titleLine2.replaceFirst(" > ", ""); + } + + public static String getAbridgedConfigPath(String path) + { + Minecraft mc = Minecraft.getMinecraft(); + if (mc.mcDataDir.getAbsolutePath().endsWith(".")) + return path.replace("\\", "/").replace(mc.mcDataDir.getAbsolutePath().replace("\\", "/").substring(0, mc.mcDataDir.getAbsolutePath().length() - 1), "/.minecraft/"); + else + return path.replace("\\", "/").replace(mc.mcDataDir.getAbsolutePath().replace("\\", "/"), "/.minecraft"); + } + + @Override + public void initGui() + { + Keyboard.enableRepeatEvents(true); + + if (this.entryList == null || this.needsRefresh) + { + this.entryList = new GuiConfigEntries(this, mc); + this.needsRefresh = false; + } + + int undoGlyphWidth = mc.fontRenderer.getStringWidth(UNDO_CHAR) * 2; + int resetGlyphWidth = mc.fontRenderer.getStringWidth(RESET_CHAR) * 2; + int doneWidth = Math.max(mc.fontRenderer.getStringWidth(I18n.format("gui.done")) + 20, 100); + int undoWidth = mc.fontRenderer.getStringWidth(" " + I18n.format("fml.configgui.tooltip.undoChanges")) + undoGlyphWidth + 20; + int resetWidth = mc.fontRenderer.getStringWidth(" " + I18n.format("fml.configgui.tooltip.resetToDefault")) + resetGlyphWidth + 20; + int checkWidth = mc.fontRenderer.getStringWidth(I18n.format("fml.configgui.applyGlobally")) + 13; + int buttonWidthHalf = (doneWidth + 5 + undoWidth + 5 + resetWidth + 5 + checkWidth) / 2; + this.buttonList.add(new GuiButtonExt(2000, this.width / 2 - buttonWidthHalf, this.height - 29, doneWidth, 20, I18n.format("gui.done"))); + this.buttonList.add(this.btnDefaultAll = new GuiUnicodeGlyphButton(2001, this.width / 2 - buttonWidthHalf + doneWidth + 5 + undoWidth + 5, + this.height - 29, resetWidth, 20, " " + I18n.format("fml.configgui.tooltip.resetToDefault"), RESET_CHAR, 2.0F)); + this.buttonList.add(btnUndoAll = new GuiUnicodeGlyphButton(2002, this.width / 2 - buttonWidthHalf + doneWidth + 5, + this.height - 29, undoWidth, 20, " " + I18n.format("fml.configgui.tooltip.undoChanges"), UNDO_CHAR, 2.0F)); + this.buttonList.add(chkApplyGlobally = new GuiCheckBox(2003, this.width / 2 - buttonWidthHalf + doneWidth + 5 + undoWidth + 5 + resetWidth + 5, + this.height - 24, I18n.format("fml.configgui.applyGlobally"), false)); + + this.undoHoverChecker = new HoverChecker(this.btnUndoAll, 800); + this.resetHoverChecker = new HoverChecker(this.btnDefaultAll, 800); + this.checkBoxHoverChecker = new HoverChecker(chkApplyGlobally, 800); + this.entryList.initGui(); + } + + @Override + public void onGuiClosed() + { + this.entryList.onGuiClosed(); + + if (this.configID != null && this.parentScreen instanceof GuiConfig) + { + GuiConfig parentGuiConfig = (GuiConfig) this.parentScreen; + parentGuiConfig.needsRefresh = true; + parentGuiConfig.initGui(); + } + + if (!(this.parentScreen instanceof GuiConfig)) + Keyboard.enableRepeatEvents(false); + } + + @Override + protected void actionPerformed(GuiButton button) + { + if (button.id == 2000) + { + boolean flag = true; + try + { + if ((configID != null || this.parentScreen == null || !(this.parentScreen instanceof GuiConfig)) + && (this.entryList.hasChangedEntry(true))) + { + boolean requiresMcRestart = this.entryList.saveConfigElements(); + + if (Loader.isModLoaded(modID)) + { + ConfigChangedEvent event = new OnConfigChangedEvent(modID, configID, isWorldRunning, requiresMcRestart); + FMLCommonHandler.instance().bus().post(event); + if (!event.getResult().equals(Result.DENY)) + FMLCommonHandler.instance().bus().post(new PostConfigChangedEvent(modID, configID, isWorldRunning, requiresMcRestart)); + + if (requiresMcRestart) + { + flag = false; + mc.displayGuiScreen(new GuiMessageDialog(parentScreen, "fml.configgui.gameRestartTitle", + new ChatComponentText(I18n.format("fml.configgui.gameRestartRequired")), "fml.configgui.confirmRestartMessage")); + } + + if (this.parentScreen instanceof GuiConfig) + ((GuiConfig) this.parentScreen).needsRefresh = true; + } + } + } + catch (Throwable e) + { + e.printStackTrace(); + } + + if (flag) + this.mc.displayGuiScreen(this.parentScreen); + } + else if (button.id == 2001) + { + this.entryList.setAllToDefault(this.chkApplyGlobally.isChecked()); + } + else if (button.id == 2002) + { + this.entryList.undoAllChanges(this.chkApplyGlobally.isChecked()); + } + } + + @Override + protected void mouseClicked(int x, int y, int mouseEvent) + { + if (mouseEvent != 0 || !this.entryList.func_148179_a(x, y, mouseEvent)) + { + this.entryList.mouseClicked(x, y, mouseEvent); + super.mouseClicked(x, y, mouseEvent); + } + } + + @Override + protected void mouseMovedOrUp(int x, int y, int mouseEvent) + { + if (mouseEvent != 0 || !this.entryList.func_148181_b(x, y, mouseEvent)) + { + super.mouseMovedOrUp(x, y, mouseEvent); + } + } + + @Override + protected void keyTyped(char eventChar, int eventKey) + { + if (eventKey == Keyboard.KEY_ESCAPE) + this.mc.displayGuiScreen(parentScreen); + else + this.entryList.keyTyped(eventChar, eventKey); + } + + @Override + public void updateScreen() + { + super.updateScreen(); + this.entryList.updateScreen(); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) + { + this.drawDefaultBackground(); + this.entryList.drawScreen(mouseX, mouseY, partialTicks); + this.drawCenteredString(this.fontRendererObj, this.title, this.width / 2, 8, 16777215); + String title2 = this.titleLine2; + + if (title2 != null) + { + int strWidth = mc.fontRenderer.getStringWidth(title2); + int elipsisWidth = mc.fontRenderer.getStringWidth("..."); + if (strWidth > width - 6 && strWidth > elipsisWidth) + title2 = mc.fontRenderer.trimStringToWidth(title2, width - 6 - elipsisWidth).trim() + "..."; + this.drawCenteredString(this.fontRendererObj, title2, this.width / 2, 18, 16777215); + } + + this.btnUndoAll.enabled = this.entryList.areAnyEntriesEnabled(this.chkApplyGlobally.isChecked()) && this.entryList.hasChangedEntry(this.chkApplyGlobally.isChecked()); + this.btnDefaultAll.enabled = this.entryList.areAnyEntriesEnabled(this.chkApplyGlobally.isChecked()) && !this.entryList.areAllEntriesDefault(this.chkApplyGlobally.isChecked()); + super.drawScreen(mouseX, mouseY, partialTicks); + this.entryList.drawScreenPost(mouseX, mouseY, partialTicks); + if (this.undoHoverChecker.checkHover(mouseX, mouseY)) + this.drawToolTip(this.mc.fontRenderer.listFormattedStringToWidth(I18n.format("fml.configgui.tooltip.undoAll"), 300), mouseX, mouseY); + if (this.resetHoverChecker.checkHover(mouseX, mouseY)) + this.drawToolTip(this.mc.fontRenderer.listFormattedStringToWidth(I18n.format("fml.configgui.tooltip.resetAll"), 300), mouseX, mouseY); + if (this.checkBoxHoverChecker.checkHover(mouseX, mouseY)) + this.drawToolTip(this.mc.fontRenderer.listFormattedStringToWidth(I18n.format("fml.configgui.tooltip.applyGlobally"), 300), mouseX, mouseY); + } + + public void drawToolTip(List stringList, int x, int y) + { + this.func_146283_a(stringList, x, y); + } +} \ No newline at end of file diff --git a/src/main/java/cpw/mods/fml/client/config/GuiConfigEntries.java b/src/main/java/cpw/mods/fml/client/config/GuiConfigEntries.java new file mode 100644 index 0000000..9377507 --- /dev/null +++ b/src/main/java/cpw/mods/fml/client/config/GuiConfigEntries.java @@ -0,0 +1,1665 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2014 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors (this class): + * bspkrs - implementation + */ +package cpw.mods.fml.client.config; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiListExtended; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.GuiSlot; +import net.minecraft.client.gui.GuiTextField; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.resources.I18n; +import net.minecraft.util.EnumChatFormatting; + +import org.lwjgl.input.Keyboard; + +import static cpw.mods.fml.client.config.GuiUtils.RESET_CHAR; +import static cpw.mods.fml.client.config.GuiUtils.UNDO_CHAR; + +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.ModContainer; + +/** + * This class implements the scrolling list functionality of the config GUI screens. It also provides all the default control handlers + * for the various property types. + * + * @author bspkrs + */ +public class GuiConfigEntries extends GuiListExtended +{ + public final GuiConfig owningScreen; + public final Minecraft mc; + public List listEntries; + /** + * The max width of the label of all IConfigEntry objects. + */ + public int maxLabelTextWidth = 0; + /** + * The max x boundary of all IConfigEntry objects. + */ + public int maxEntryRightBound = 0; + /** + * The x position where the label should be drawn. + */ + public int labelX; + /** + * The x position where the control should be drawn. + */ + public int controlX; + /** + * The width of the control. + */ + public int controlWidth; + /** + * The minimum x position where the Undo/Default buttons will start + */ + public int resetX; + /** + * The x position of the scroll bar. + */ + public int scrollBarX; + + public GuiConfigEntries(GuiConfig parent, Minecraft mc) + { + super(mc, parent.width, parent.height, parent.titleLine2 != null ? 33 : 23, parent.height - 32, 20); + this.owningScreen = parent; + this.setShowSelectionBox(false); + this.mc = mc; + this.listEntries = new ArrayList(); + int i = 0; + String s = null; + + for (IConfigElement configElement : parent.configElements) + { + if (configElement != null) + { + if (configElement.isProperty() && configElement.showInGui()) // as opposed to being a child category entry + { + int length; + + // protects against language keys that are not defined in the .lang file + if (!I18n.format(configElement.getLanguageKey()).equals(configElement.getLanguageKey())) + length = mc.fontRenderer.getStringWidth(I18n.format(configElement.getLanguageKey())); + else + length = mc.fontRenderer.getStringWidth(configElement.getName()); + + if (length > this.maxLabelTextWidth) + this.maxLabelTextWidth = length; + } + } + } + + int viewWidth = this.maxLabelTextWidth + 8 + (width / 2); + labelX = (this.width / 2) - (viewWidth / 2); + controlX = labelX + maxLabelTextWidth + 8; + resetX = (this.width / 2) + (viewWidth / 2) - 45; + controlWidth = resetX - controlX - 5; + scrollBarX = this.width; + + for (IConfigElement configElement : parent.configElements) + { + if (configElement != null && configElement.showInGui()) + { + if (configElement.getConfigEntryClass() != null) + try + { + this.listEntries.add((IConfigEntry) configElement.getConfigEntryClass() + .getConstructor(GuiConfig.class, GuiConfigEntries.class, IConfigElement.class) + .newInstance(this.owningScreen, this, configElement)); + } + catch (Throwable e) + { + FMLLog.severe("There was a critical error instantiating the custom IConfigEntry for config element %s.", configElement.getName()); + e.printStackTrace(); + } + else if (configElement.isProperty()) + { + if (configElement.isList()) + this.listEntries.add(new GuiConfigEntries.ArrayEntry(this.owningScreen, this, configElement)); + else if (configElement.getType() == ConfigGuiType.BOOLEAN) + this.listEntries.add(new GuiConfigEntries.BooleanEntry(this.owningScreen, this, (IConfigElement) configElement)); + else if (configElement.getType() == ConfigGuiType.INTEGER) + this.listEntries.add(new GuiConfigEntries.IntegerEntry(this.owningScreen, this, (IConfigElement) configElement)); + else if (configElement.getType() == ConfigGuiType.DOUBLE) + this.listEntries.add(new GuiConfigEntries.DoubleEntry(this.owningScreen, this, (IConfigElement) configElement)); + else if (configElement.getType() == ConfigGuiType.COLOR) + { + if (configElement.getValidValues() != null && configElement.getValidValues().length > 0) + this.listEntries.add(new GuiConfigEntries.ChatColorEntry(this.owningScreen, this, (IConfigElement) configElement)); + else + this.listEntries.add(new GuiConfigEntries.StringEntry(this.owningScreen, this, (IConfigElement) configElement)); + } + else if (configElement.getType() == ConfigGuiType.MOD_ID) + { + Map values = new TreeMap(); + for (ModContainer mod : Loader.instance().getActiveModList()) + values.put(mod.getModId(), mod.getName()); + values.put("minecraft", "Minecraft"); + this.listEntries.add(new SelectValueEntry(this.owningScreen, this, (IConfigElement) configElement, values)); + } + else if (configElement.getType() == ConfigGuiType.STRING) + { + if (configElement.getValidValues() != null && configElement.getValidValues().length > 0) + this.listEntries.add(new GuiConfigEntries.CycleValueEntry(this.owningScreen, this, (IConfigElement) configElement)); + else + this.listEntries.add(new GuiConfigEntries.StringEntry(this.owningScreen, this, (IConfigElement) configElement)); + } + } + else if (configElement.getType() == ConfigGuiType.CONFIG_CATEGORY) + this.listEntries.add(new CategoryEntry(this.owningScreen, this, configElement)); + } + } + } + + protected void initGui() + { + this.width = owningScreen.width; + this.height = owningScreen.height; + + this.maxLabelTextWidth = 0; + for (IConfigEntry entry : this.listEntries) + if (entry.getLabelWidth() > this.maxLabelTextWidth) + this.maxLabelTextWidth = entry.getLabelWidth(); + + this.top = owningScreen.titleLine2 != null ? 33 : 23; + this.bottom = owningScreen.height - 32; + this.left = 0; + this.right = width; + int viewWidth = this.maxLabelTextWidth + 8 + (width / 2); + labelX = (this.width / 2) - (viewWidth / 2); + controlX = labelX + maxLabelTextWidth + 8; + resetX = (this.width / 2) + (viewWidth / 2) - 45; + + this.maxEntryRightBound = 0; + for (IConfigEntry entry : this.listEntries) + if (entry.getEntryRightBound() > this.maxEntryRightBound) + this.maxEntryRightBound = entry.getEntryRightBound(); + + scrollBarX = this.maxEntryRightBound + 5; + controlWidth = maxEntryRightBound - controlX - 45; + } + + @Override + public int getSize() + { + return this.listEntries.size(); + } + + /** + * Gets the IGuiListEntry object for the given index + */ + @Override + public IConfigEntry getListEntry(int index) + { + return this.listEntries.get(index); + } + + @Override + public int getScrollBarX() + { + return scrollBarX; + } + + /** + * Gets the width of the list + */ + @Override + public int getListWidth() + { + return owningScreen.width; + } + + /** + * This method is a pass-through for IConfigEntry objects that require keystrokes. Called from the parent GuiConfig screen. + */ + public void keyTyped(char eventChar, int eventKey) + { + for (IConfigEntry entry : this.listEntries) + entry.keyTyped(eventChar, eventKey); + } + + /** + * This method is a pass-through for IConfigEntry objects that contain GuiTextField elements. Called from the parent GuiConfig + * screen. + */ + public void updateScreen() + { + for (IConfigEntry entry : this.listEntries) + entry.updateCursorCounter(); + } + + /** + * This method is a pass-through for IConfigEntry objects that contain GuiTextField elements. Called from the parent GuiConfig + * screen. + */ + public void mouseClicked(int mouseX, int mouseY, int mouseEvent) + { + for (IConfigEntry entry : this.listEntries) + entry.mouseClicked(mouseX, mouseY, mouseEvent); + } + + /** + * This method is a pass-through for IConfigListEntry objects that need to perform actions when the containing GUI is closed. + */ + public void onGuiClosed() + { + for (IConfigEntry entry : this.listEntries) + entry.onGuiClosed(); + } + + /** + * Saves all properties on this screen / child screens. This method returns true if any elements were changed that require + * a restart for proper handling. + */ + public boolean saveConfigElements() + { + boolean requiresRestart = false; + for (IConfigEntry entry : this.listEntries) + if (entry.saveConfigElement()) + requiresRestart = true; + + return requiresRestart; + } + + /** + * Returns true if all IConfigEntry objects on this screen are set to default. If includeChildren is true sub-category + * objects are checked as well. + */ + public boolean areAllEntriesDefault(boolean includeChildren) + { + for (IConfigEntry entry : this.listEntries) + if ((includeChildren || !(entry instanceof CategoryEntry)) && !entry.isDefault()) + return false; + + return true; + } + + /** + * Sets all IConfigEntry objects on this screen to default. If includeChildren is true sub-category objects are set as + * well. + */ + public void setAllToDefault(boolean includeChildren) + { + for (IConfigEntry entry : this.listEntries) + if ((includeChildren || !(entry instanceof CategoryEntry))) + entry.setToDefault(); + } + + /** + * Returns true if any IConfigEntry objects on this screen are changed. If includeChildren is true sub-category objects + * are checked as well. + */ + public boolean hasChangedEntry(boolean includeChildren) + { + for (IConfigEntry entry : this.listEntries) + if ((includeChildren || !(entry instanceof CategoryEntry)) && entry.isChanged()) + return true; + + return false; + } + + /** + * Returns true if any IConfigEntry objects on this screen are enabled. If includeChildren is true sub-category objects + * are checked as well. + */ + public boolean areAnyEntriesEnabled(boolean includeChildren) + { + for (IConfigEntry entry : this.listEntries) + if ((includeChildren || !(entry instanceof CategoryEntry)) && entry.enabled()) + return true; + + return false; + } + + /** + * Reverts changes to all IConfigEntry objects on this screen. If includeChildren is true sub-category objects are + * reverted as well. + */ + public void undoAllChanges(boolean includeChildren) + { + for (IConfigEntry entry : this.listEntries) + if ((includeChildren || !(entry instanceof CategoryEntry))) + entry.undoChanges(); + } + + /** + * Calls the drawToolTip() method for all IConfigEntry objects on this screen. This is called from the parent GuiConfig screen + * after drawing all other elements. + */ + public void drawScreenPost(int mouseX, int mouseY, float partialTicks) + { + for (IConfigEntry entry : this.listEntries) + entry.drawToolTip(mouseX, mouseY); + } + + /** + * BooleanPropEntry + * + * Provides a GuiButton that toggles between true and false. + */ + public static class BooleanEntry extends ButtonEntry + { + protected final boolean beforeValue; + protected boolean currentValue; + + private BooleanEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement) + { + super(owningScreen, owningEntryList, configElement); + this.beforeValue = Boolean.valueOf(configElement.get().toString()); + this.currentValue = beforeValue; + this.btnValue.enabled = enabled(); + updateValueButtonText(); + } + + @Override + public void updateValueButtonText() + { + this.btnValue.displayString = I18n.format(String.valueOf(currentValue)); + btnValue.packedFGColour = currentValue ? GuiUtils.getColorCode('2', true) : GuiUtils.getColorCode('4', true); + } + + @Override + public void valueButtonPressed(int slotIndex) + { + if (enabled()) + currentValue = !currentValue; + } + + @Override + public boolean isDefault() + { + return currentValue == Boolean.valueOf(configElement.getDefault().toString()); + } + + @Override + public void setToDefault() + { + if (enabled()) + { + currentValue = Boolean.valueOf(configElement.getDefault().toString()); + updateValueButtonText(); + } + } + + @Override + public boolean isChanged() + { + return currentValue != beforeValue; + } + + @Override + public void undoChanges() + { + if (enabled()) + { + currentValue = beforeValue; + updateValueButtonText(); + } + } + + @Override + public boolean saveConfigElement() + { + if (enabled() && isChanged()) + { + configElement.set(currentValue); + return configElement.requiresMcRestart(); + } + return false; + } + + @Override + public Boolean getCurrentValue() + { + return currentValue; + } + + @Override + public Boolean[] getCurrentValues() + { + return new Boolean[] { getCurrentValue() }; + } + } + + /** + * CycleValueEntry + * + * Provides a GuiButton that cycles through the prop's validValues array. If the current prop value is not a valid value, the first + * entry replaces the current value. + */ + public static class CycleValueEntry extends ButtonEntry + { + protected final int beforeIndex; + protected final int defaultIndex; + protected int currentIndex; + + private CycleValueEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement) + { + super(owningScreen, owningEntryList, configElement); + beforeIndex = getIndex(configElement.get().toString()); + defaultIndex = getIndex(configElement.getDefault().toString()); + currentIndex = beforeIndex; + this.btnValue.enabled = enabled(); + updateValueButtonText(); + } + + private int getIndex(String s) + { + for (int i = 0; i < configElement.getValidValues().length; i++) + if (configElement.getValidValues()[i].equalsIgnoreCase(s)) + { + return i; + } + + return 0; + } + + @Override + public void updateValueButtonText() + { + this.btnValue.displayString = I18n.format(configElement.getValidValues()[currentIndex]); + } + + @Override + public void valueButtonPressed(int slotIndex) + { + if (enabled()) + { + if (++this.currentIndex >= configElement.getValidValues().length) + this.currentIndex = 0; + + updateValueButtonText(); + } + } + + @Override + public boolean isDefault() + { + return currentIndex == defaultIndex; + } + + @Override + public void setToDefault() + { + if (enabled()) + { + currentIndex = defaultIndex; + updateValueButtonText(); + } + } + + @Override + public boolean isChanged() + { + return currentIndex != beforeIndex; + } + + @Override + public void undoChanges() + { + if (enabled()) + { + currentIndex = beforeIndex; + updateValueButtonText(); + } + } + + @Override + public boolean saveConfigElement() + { + if (enabled() && isChanged()) + { + configElement.set(configElement.getValidValues()[currentIndex]); + return configElement.requiresMcRestart(); + } + return false; + } + + @Override + public String getCurrentValue() + { + return configElement.getValidValues()[currentIndex]; + } + + @Override + public String[] getCurrentValues() + { + return new String[] { getCurrentValue() }; + } + } + + /** + * ChatColorEntry + * + * Provides a GuiButton that cycles through the list of chat color codes. + */ + public static class ChatColorEntry extends CycleValueEntry + { + ChatColorEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement) + { + super(owningScreen, owningEntryList, configElement); + this.btnValue.enabled = enabled(); + updateValueButtonText(); + } + + @Override + public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, Tessellator tessellator, int mouseX, int mouseY, boolean isSelected) + { + this.btnValue.packedFGColour = GuiUtils.getColorCode(this.configElement.getValidValues()[currentIndex].charAt(0), true); + super.drawEntry(slotIndex, x, y, listWidth, slotHeight, tessellator, mouseX, mouseY, isSelected); + } + + @Override + public void updateValueButtonText() + { + this.btnValue.displayString = I18n.format(configElement.getValidValues()[currentIndex]) + " - " + I18n.format("fml.configgui.sampletext"); + } + } + + /** + * SelectValueEntry + * + * Provides a GuiButton with the current value as the displayString. Accepts a Map of selectable values with the signature where the key is the Object to be selected and the value is the String that will show on the selection list. EG: a map of Mod + * ID values where the key is the Mod ID and the value is the Mod Name. + */ + public static class SelectValueEntry extends ButtonEntry + { + protected final String beforeValue; + protected Object currentValue; + protected Map selectableValues; + + public SelectValueEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement, Map selectableValues) + { + super(owningScreen, owningEntryList, configElement); + beforeValue = configElement.get().toString(); + currentValue = configElement.get().toString(); + this.selectableValues = selectableValues; + updateValueButtonText(); + } + + @Override + public void updateValueButtonText() + { + this.btnValue.displayString = currentValue.toString(); + } + + @Override + public void valueButtonPressed(int slotIndex) + { + mc.displayGuiScreen(new GuiSelectString(this.owningScreen, configElement, slotIndex, selectableValues, currentValue, enabled())); + } + + public void setValueFromChildScreen(Object newValue) + { + if (enabled() && currentValue != null ? !currentValue.equals(newValue) : newValue != null) + { + currentValue = newValue; + updateValueButtonText(); + } + } + + @Override + public boolean isDefault() + { + if (configElement.getDefault() != null) + return configElement.getDefault().equals(currentValue); + else + return currentValue == null; + } + + @Override + public void setToDefault() + { + if (enabled()) + { + this.currentValue = configElement.getDefault().toString(); + updateValueButtonText(); + } + } + + @Override + public boolean isChanged() + { + if (beforeValue != null) + return !beforeValue.equals(currentValue); + else + return currentValue == null; + } + + @Override + public void undoChanges() + { + if (enabled()) + { + currentValue = beforeValue; + updateValueButtonText(); + } + } + + @Override + public boolean saveConfigElement() + { + if (enabled() && isChanged()) + { + this.configElement.set(currentValue); + return configElement.requiresMcRestart(); + } + return false; + } + + @Override + public String getCurrentValue() + { + return this.currentValue.toString(); + } + + @Override + public String[] getCurrentValues() + { + return new String[] { getCurrentValue() }; + } + } + + /** + * ArrayEntry + * + * Provides a GuiButton with the list contents as the displayString. Clicking the button navigates to a screen where the list can be + * edited. + */ + public static class ArrayEntry extends ButtonEntry + { + protected final Object[] beforeValues; + protected Object[] currentValues; + + public ArrayEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement) + { + super(owningScreen, owningEntryList, configElement); + beforeValues = configElement.getList(); + currentValues = configElement.getList(); + updateValueButtonText(); + } + + @Override + public void updateValueButtonText() + { + this.btnValue.displayString = ""; + for (Object o : currentValues) + this.btnValue.displayString += ", [" + o + "]"; + + this.btnValue.displayString = this.btnValue.displayString.replaceFirst(", ", ""); + } + + @Override + public void valueButtonPressed(int slotIndex) + { + mc.displayGuiScreen(new GuiEditArray(this.owningScreen, configElement, slotIndex, currentValues, enabled())); + } + + public void setListFromChildScreen(Object[] newList) + { + if (enabled() && !Arrays.deepEquals(currentValues, newList)) + { + currentValues = newList; + updateValueButtonText(); + } + } + + @Override + public boolean isDefault() + { + return Arrays.deepEquals(configElement.getDefaults(), currentValues); + } + + @Override + public void setToDefault() + { + if (enabled()) + { + this.currentValues = configElement.getDefaults(); + updateValueButtonText(); + } + } + + @Override + public boolean isChanged() + { + return !Arrays.deepEquals(beforeValues, currentValues); + } + + @Override + public void undoChanges() + { + if (enabled()) + { + currentValues = beforeValues; + updateValueButtonText(); + } + } + + @Override + public boolean saveConfigElement() + { + if (enabled() && isChanged()) + { + this.configElement.set(currentValues); + return configElement.requiresMcRestart(); + } + return false; + } + + @Override + public Object getCurrentValue() + { + return this.btnValue.displayString; + } + + @Override + public Object[] getCurrentValues() + { + return this.currentValues; + } + } + + /** + * ButtonEntry + * + * Provides a basic GuiButton entry to be used as a base for other entries that require a button for the value. + */ + public static abstract class ButtonEntry extends ListEntryBase + { + protected final GuiButtonExt btnValue; + + public ButtonEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement) + { + super(owningScreen, owningEntryList, configElement); + this.btnValue = new GuiButtonExt(0, this.owningEntryList.controlX, 0, this.owningEntryList.controlWidth, 18, + configElement.get() != null ? I18n.format(String.valueOf(configElement.get())) : ""); + } + + /** + * Updates the displayString of the value button. + */ + public abstract void updateValueButtonText(); + + /** + * Called when the value button has been clicked. + */ + public abstract void valueButtonPressed(int slotIndex); + + @Override + public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, Tessellator tessellator, int mouseX, int mouseY, boolean isSelected) + { + super.drawEntry(slotIndex, x, y, listWidth, slotHeight, tessellator, mouseX, mouseY, isSelected); + this.btnValue.width = this.owningEntryList.controlWidth; + this.btnValue.xPosition = this.owningScreen.entryList.controlX; + this.btnValue.yPosition = y; + this.btnValue.enabled = enabled(); + this.btnValue.drawButton(this.mc, mouseX, mouseY); + } + + /** + * Returns true if the mouse has been pressed on this control. + */ + @Override + public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) + { + if (this.btnValue.mousePressed(this.mc, x, y)) + { + btnValue.func_146113_a(mc.getSoundHandler()); + valueButtonPressed(index); + updateValueButtonText(); + return true; + } + else + return super.mousePressed(index, x, y, mouseEvent, relativeX, relativeY); + } + + /** + * Fired when the mouse button is released. Arguments: index, x, y, mouseEvent, relativeX, relativeY + */ + @Override + public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) + { + super.mouseReleased(index, x, y, mouseEvent, relativeX, relativeY); + this.btnValue.mouseReleased(x, y); + } + + @Override + public void keyTyped(char eventChar, int eventKey) + {} + + @Override + public void updateCursorCounter() + {} + + @Override + public void mouseClicked(int x, int y, int mouseEvent) + {} + } + + /** + * IntegerEntry + * + * Provides a GuiTextField for user input. Input is restricted to ensure the value can be parsed using Integer.parseInteger(). + */ + public static class IntegerEntry extends StringEntry + { + protected final int beforeValue; + + public IntegerEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement) + { + super(owningScreen, owningEntryList, configElement); + this.beforeValue = Integer.valueOf(configElement.get().toString()); + } + + @Override + public void keyTyped(char eventChar, int eventKey) + { + if (enabled() || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END) + { + String validChars = "0123456789"; + String before = this.textFieldValue.getText(); + if (validChars.contains(String.valueOf(eventChar)) + || (!before.startsWith("-") && this.textFieldValue.getCursorPosition() == 0 && eventChar == '-') + || eventKey == Keyboard.KEY_BACK || eventKey == Keyboard.KEY_DELETE + || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END) + this.textFieldValue.textboxKeyTyped((enabled() ? eventChar : Keyboard.CHAR_NONE), eventKey); + + if (!textFieldValue.getText().trim().isEmpty() && !textFieldValue.getText().trim().equals("-")) + { + try + { + long value = Long.parseLong(textFieldValue.getText().trim()); + if (value < Integer.valueOf(configElement.getMinValue().toString()) || value > Integer.valueOf(configElement.getMaxValue().toString())) + this.isValidValue = false; + else + this.isValidValue = true; + } + catch (Throwable e) + { + this.isValidValue = false; + } + } + else + this.isValidValue = false; + } + } + + @Override + public boolean isChanged() + { + try + { + return this.beforeValue != Integer.parseInt(textFieldValue.getText().trim()); + } + catch (Throwable e) + { + return true; + } + } + + @Override + public void undoChanges() + { + if (enabled()) + this.textFieldValue.setText(String.valueOf(beforeValue)); + } + + @Override + public boolean saveConfigElement() + { + if (enabled()) + { + if (isChanged() && this.isValidValue) + try + { + int value = Integer.parseInt(textFieldValue.getText().trim()); + this.configElement.set(value); + return configElement.requiresMcRestart(); + } + catch (Throwable e) + { + this.configElement.setToDefault(); + } + else if (isChanged() && !this.isValidValue) + try + { + int value = Integer.parseInt(textFieldValue.getText().trim()); + if (value < Integer.valueOf(configElement.getMinValue().toString())) + this.configElement.set(configElement.getMinValue()); + else + this.configElement.set(configElement.getMaxValue()); + + } + catch (Throwable e) + { + this.configElement.setToDefault(); + } + + return configElement.requiresMcRestart() && beforeValue != Integer.parseInt(configElement.get().toString()); + } + return false; + } + } + + /** + * DoubleEntry + * + * Provides a GuiTextField for user input. Input is restricted to ensure the value can be parsed using Double.parseDouble(). + */ + public static class DoubleEntry extends StringEntry + { + protected final double beforeValue; + + public DoubleEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement) + { + super(owningScreen, owningEntryList, configElement); + this.beforeValue = Double.valueOf(configElement.get().toString()); + } + + @Override + public void keyTyped(char eventChar, int eventKey) + { + if (enabled() || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END) + { + String validChars = "0123456789"; + String before = this.textFieldValue.getText(); + if (validChars.contains(String.valueOf(eventChar)) || + (!before.startsWith("-") && this.textFieldValue.getCursorPosition() == 0 && eventChar == '-') + || (!before.contains(".") && eventChar == '.') + || eventKey == Keyboard.KEY_BACK || eventKey == Keyboard.KEY_DELETE || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT + || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END) + this.textFieldValue.textboxKeyTyped((enabled() ? eventChar : Keyboard.CHAR_NONE), eventKey); + + if (!textFieldValue.getText().trim().isEmpty() && !textFieldValue.getText().trim().equals("-")) + { + try + { + double value = Double.parseDouble(textFieldValue.getText().trim()); + if (value < Double.valueOf(configElement.getMinValue().toString()) || value > Double.valueOf(configElement.getMaxValue().toString())) + this.isValidValue = false; + else + this.isValidValue = true; + } + catch (Throwable e) + { + this.isValidValue = false; + } + } + else + this.isValidValue = false; + } + } + + @Override + public boolean isChanged() + { + try + { + return this.beforeValue != Double.parseDouble(textFieldValue.getText().trim()); + } + catch (Throwable e) + { + return true; + } + } + + @Override + public void undoChanges() + { + if (enabled()) + this.textFieldValue.setText(String.valueOf(beforeValue)); + } + + @Override + public boolean saveConfigElement() + { + if (enabled()) + { + if (isChanged() && this.isValidValue) + try + { + double value = Double.parseDouble(textFieldValue.getText().trim()); + this.configElement.set(value); + return configElement.requiresMcRestart(); + } + catch (Throwable e) + { + this.configElement.setToDefault(); + } + else if (isChanged() && !this.isValidValue) + try + { + double value = Double.parseDouble(textFieldValue.getText().trim()); + if (value < Double.valueOf(configElement.getMinValue().toString())) + this.configElement.set(configElement.getMinValue()); + else + this.configElement.set(configElement.getMaxValue()); + } + catch (Throwable e) + { + this.configElement.setToDefault(); + } + + return configElement.requiresMcRestart() && beforeValue != Double.parseDouble(configElement.get().toString()); + } + return false; + } + } + + /** + * StringEntry + * + * Provides a GuiTextField for user input. + */ + public static class StringEntry extends ListEntryBase + { + protected final GuiTextField textFieldValue; + protected final String beforeValue; + + public StringEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement) + { + super(owningScreen, owningEntryList, configElement); + beforeValue = configElement.get().toString(); + this.textFieldValue = new GuiTextField(this.mc.fontRenderer, this.owningEntryList.controlX + 1, 0, this.owningEntryList.controlWidth - 3, 16); + this.textFieldValue.setMaxStringLength(10000); + this.textFieldValue.setText(configElement.get().toString()); + } + + @Override + public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, Tessellator tessellator, int mouseX, int mouseY, boolean isSelected) + { + super.drawEntry(slotIndex, x, y, listWidth, slotHeight, tessellator, mouseX, mouseY, isSelected); + this.textFieldValue.xPosition = this.owningEntryList.controlX + 2; + this.textFieldValue.yPosition = y + 1; + this.textFieldValue.width = this.owningEntryList.controlWidth - 4; + this.textFieldValue.setEnabled(enabled()); + this.textFieldValue.drawTextBox(); + } + + @Override + public void keyTyped(char eventChar, int eventKey) + { + if (enabled() || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END) + { + this.textFieldValue.textboxKeyTyped((enabled() ? eventChar : Keyboard.CHAR_NONE), eventKey); + + if (configElement.getValidationPattern() != null) + { + if (configElement.getValidationPattern().matcher(this.textFieldValue.getText().trim()).matches()) + isValidValue = true; + else + isValidValue = false; + } + } + } + + @Override + public void updateCursorCounter() + { + this.textFieldValue.updateCursorCounter(); + } + + @Override + public void mouseClicked(int x, int y, int mouseEvent) + { + this.textFieldValue.mouseClicked(x, y, mouseEvent); + } + + @Override + public boolean isDefault() + { + return configElement.getDefault() != null ? configElement.getDefault().toString().equals(this.textFieldValue.getText()) : + this.textFieldValue.getText().trim().isEmpty(); + } + + @Override + public void setToDefault() + { + if (enabled()) + { + this.textFieldValue.setText(this.configElement.getDefault().toString()); + keyTyped((char) Keyboard.CHAR_NONE, Keyboard.KEY_HOME); + } + } + + @Override + public boolean isChanged() + { + return beforeValue != null ? !this.beforeValue.equals(textFieldValue.getText()) : this.textFieldValue.getText().trim().isEmpty(); + } + + @Override + public void undoChanges() + { + if (enabled()) + this.textFieldValue.setText(beforeValue); + } + + @Override + public boolean saveConfigElement() + { + if (enabled()) + { + if (isChanged() && this.isValidValue) + { + this.configElement.set(this.textFieldValue.getText()); + return configElement.requiresMcRestart(); + } + else if (isChanged() && !this.isValidValue) + { + this.configElement.setToDefault(); + return configElement.requiresMcRestart() + && beforeValue != null ? beforeValue.equals(configElement.getDefault()) : configElement.getDefault() == null; + } + } + return false; + } + + @Override + public Object getCurrentValue() + { + return this.textFieldValue.getText(); + } + + @Override + public Object[] getCurrentValues() + { + return new Object[] { getCurrentValue() }; + } + } + + /** + * CategoryEntry + * + * Provides an entry that consists of a GuiButton for navigating to the child category GuiConfig screen. + */ + public static class CategoryEntry extends ListEntryBase + { + protected GuiScreen childScreen; + protected final GuiButtonExt btnSelectCategory; + + public CategoryEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement) + { + super(owningScreen, owningEntryList, configElement); + + this.childScreen = this.buildChildScreen(); + + this.btnSelectCategory = new GuiButtonExt(0, 0, 0, 300, 18, I18n.format(name)); + this.tooltipHoverChecker = new HoverChecker(this.btnSelectCategory, 800); + + this.drawLabel = false; + } + + /** + * This method is called in the constructor and is used to set the childScreen field. + */ + protected GuiScreen buildChildScreen() + { + return new GuiConfig(this.owningScreen, this.configElement.getChildElements(), this.owningScreen.modID, + owningScreen.allRequireWorldRestart || this.configElement.requiresWorldRestart(), + owningScreen.allRequireMcRestart || this.configElement.requiresMcRestart(), this.owningScreen.title, + ((this.owningScreen.titleLine2 == null ? "" : this.owningScreen.titleLine2) + " > " + this.name)); + } + + @Override + public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, Tessellator tessellator, int mouseX, int mouseY, boolean isSelected) + { + this.btnSelectCategory.xPosition = listWidth / 2 - 150; + this.btnSelectCategory.yPosition = y; + this.btnSelectCategory.enabled = enabled(); + this.btnSelectCategory.drawButton(this.mc, mouseX, mouseY); + + super.drawEntry(slotIndex, x, y, listWidth, slotHeight, tessellator, mouseX, mouseY, isSelected); + } + + @Override + public void drawToolTip(int mouseX, int mouseY) + { + boolean canHover = mouseY < this.owningScreen.entryList.bottom && mouseY > this.owningScreen.entryList.top; + + if (this.tooltipHoverChecker.checkHover(mouseX, mouseY, canHover)) + this.owningScreen.drawToolTip(toolTip, mouseX, mouseY); + + super.drawToolTip(mouseX, mouseY); + } + + @Override + public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) + { + if (this.btnSelectCategory.mousePressed(this.mc, x, y)) + { + btnSelectCategory.func_146113_a(mc.getSoundHandler()); + Minecraft.getMinecraft().displayGuiScreen(childScreen); + return true; + } + else + return super.mousePressed(index, x, y, mouseEvent, relativeX, relativeY); + } + + @Override + public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) + { + this.btnSelectCategory.mouseReleased(x, y); + } + + @Override + public boolean isDefault() + { + if (childScreen instanceof GuiConfig && ((GuiConfig) childScreen).entryList != null) + return ((GuiConfig) childScreen).entryList.areAllEntriesDefault(true); + + return true; + } + + @Override + public void setToDefault() + { + if (childScreen instanceof GuiConfig && ((GuiConfig) childScreen).entryList != null) + ((GuiConfig) childScreen).entryList.setAllToDefault(true); + } + + @Override + public void keyTyped(char eventChar, int eventKey) + {} + + @Override + public void updateCursorCounter() + {} + + @Override + public void mouseClicked(int x, int y, int mouseEvent) + {} + + @Override + public boolean saveConfigElement() + { + boolean requiresRestart = false; + + if (childScreen instanceof GuiConfig && ((GuiConfig) childScreen).entryList != null) + { + requiresRestart = configElement.requiresMcRestart() && ((GuiConfig) childScreen).entryList.hasChangedEntry(true); + + if (((GuiConfig) childScreen).entryList.saveConfigElements()) + requiresRestart = true; + } + + return requiresRestart; + } + + @Override + public boolean isChanged() + { + if (childScreen instanceof GuiConfig && ((GuiConfig) childScreen).entryList != null) + return ((GuiConfig) childScreen).entryList.hasChangedEntry(true); + else + return false; + } + + @Override + public void undoChanges() + { + if (childScreen instanceof GuiConfig && ((GuiConfig) childScreen).entryList != null) + ((GuiConfig) childScreen).entryList.undoAllChanges(true); + } + + @Override + public boolean enabled() + { + return true; + } + + @Override + public int getLabelWidth() + { + return 0; + } + + @Override + public int getEntryRightBound() + { + return this.owningEntryList.width / 2 + 155 + 22 + 18; + } + + @Override + public String getCurrentValue() + { + return ""; + } + + @Override + public String[] getCurrentValues() + { + return new String[] { getCurrentValue() }; + } + } + + /** + * ListEntryBase + * + * Provides a base entry for others to extend. Handles drawing the prop label (if drawLabel == true) and the Undo/Default buttons. + */ + public static abstract class ListEntryBase implements IConfigEntry + { + protected final GuiConfig owningScreen; + protected final GuiConfigEntries owningEntryList; + protected final IConfigElement configElement; + protected final Minecraft mc; + protected final String name; + protected final GuiButtonExt btnUndoChanges; + protected final GuiButtonExt btnDefault; + protected List toolTip; + protected List undoToolTip; + protected List defaultToolTip; + protected boolean isValidValue = true; + protected HoverChecker tooltipHoverChecker; + protected HoverChecker undoHoverChecker; + protected HoverChecker defaultHoverChecker; + protected boolean drawLabel; + + public ListEntryBase(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement configElement) + { + this.owningScreen = owningScreen; + this.owningEntryList = owningEntryList; + this.configElement = configElement; + this.mc = Minecraft.getMinecraft(); + String trans = I18n.format(configElement.getLanguageKey()); + if (!trans.equals(configElement.getLanguageKey())) + this.name = trans; + else + this.name = configElement.getName(); + this.btnUndoChanges = new GuiButtonExt(0, 0, 0, 18, 18, UNDO_CHAR); + this.btnDefault = new GuiButtonExt(0, 0, 0, 18, 18, RESET_CHAR); + + this.undoHoverChecker = new HoverChecker(this.btnUndoChanges, 800); + this.defaultHoverChecker = new HoverChecker(this.btnDefault, 800); + this.undoToolTip = Arrays.asList(new String[] { I18n.format("fml.configgui.tooltip.undoChanges") }); + this.defaultToolTip = Arrays.asList(new String[] { I18n.format("fml.configgui.tooltip.resetToDefault") }); + + this.drawLabel = true; + + String comment; + + comment = I18n.format(configElement.getLanguageKey() + ".tooltip"); + + if (!comment.equals(configElement.getLanguageKey() + ".tooltip")) + toolTip = new ArrayList(this.mc.fontRenderer.listFormattedStringToWidth( + EnumChatFormatting.GREEN + name + "\n" + EnumChatFormatting.YELLOW + comment, 300)); + else if (configElement.getComment() != null && !configElement.getComment().trim().isEmpty()) + toolTip = new ArrayList(this.mc.fontRenderer.listFormattedStringToWidth( + EnumChatFormatting.GREEN + name + "\n" + EnumChatFormatting.YELLOW + configElement.getComment(), 300)); + else + toolTip = new ArrayList(this.mc.fontRenderer.listFormattedStringToWidth( + EnumChatFormatting.GREEN + name + "\n" + EnumChatFormatting.RED + "No tooltip defined.", 300)); + + if ((configElement.getType() == ConfigGuiType.INTEGER + && (Integer.valueOf(configElement.getMinValue().toString()) != Integer.MIN_VALUE || Integer.valueOf(configElement.getMaxValue().toString()) != Integer.MAX_VALUE)) + || (configElement.getType() == ConfigGuiType.DOUBLE + && (Double.valueOf(configElement.getMinValue().toString()) != -Double.MAX_VALUE || Double.valueOf(configElement.getMaxValue().toString()) != Double.MAX_VALUE))) + toolTip.addAll(this.mc.fontRenderer.listFormattedStringToWidth( + EnumChatFormatting.AQUA + I18n.format("fml.configgui.tooltip.defaultNumeric", configElement.getMinValue(), configElement.getMaxValue(), configElement.getDefault()), 300)); + else if (configElement.getType() != ConfigGuiType.CONFIG_CATEGORY) + toolTip.addAll(this.mc.fontRenderer.listFormattedStringToWidth(EnumChatFormatting.AQUA + I18n.format("fml.configgui.tooltip.default", configElement.getDefault()),300)); + + if (configElement.requiresMcRestart() || owningScreen.allRequireMcRestart) + toolTip.add(EnumChatFormatting.RED + "[" + I18n.format("fml.configgui.gameRestartTitle") + "]"); + } + + @Override + public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, Tessellator tessellator, int mouseX, int mouseY, boolean isSelected) + { + boolean isChanged = isChanged(); + + if (drawLabel) + { + String label = (!isValidValue ? EnumChatFormatting.RED.toString() : + (isChanged ? EnumChatFormatting.WHITE.toString() : EnumChatFormatting.GRAY.toString())) + + (isChanged ? EnumChatFormatting.ITALIC.toString() : "") + this.name; + this.mc.fontRenderer.drawString( + label, + this.owningScreen.entryList.labelX, + y + slotHeight / 2 - this.mc.fontRenderer.FONT_HEIGHT / 2, + 16777215); + } + + this.btnUndoChanges.xPosition = this.owningEntryList.scrollBarX - 44; + this.btnUndoChanges.yPosition = y; + this.btnUndoChanges.enabled = enabled() && isChanged; + this.btnUndoChanges.drawButton(this.mc, mouseX, mouseY); + + this.btnDefault.xPosition = this.owningEntryList.scrollBarX - 22; + this.btnDefault.yPosition = y; + this.btnDefault.enabled = enabled() && !isDefault(); + this.btnDefault.drawButton(this.mc, mouseX, mouseY); + + if (this.tooltipHoverChecker == null) + this.tooltipHoverChecker = new HoverChecker(y, y + slotHeight, x, this.owningScreen.entryList.controlX - 8, 800); + else + this.tooltipHoverChecker.updateBounds(y, y + slotHeight, x, this.owningScreen.entryList.controlX - 8); + } + + @Override + public void drawToolTip(int mouseX, int mouseY) + { + boolean canHover = mouseY < this.owningScreen.entryList.bottom && mouseY > this.owningScreen.entryList.top; + if (toolTip != null && this.tooltipHoverChecker != null) + { + if (this.tooltipHoverChecker.checkHover(mouseX, mouseY, canHover)) + this.owningScreen.drawToolTip(toolTip, mouseX, mouseY); + } + + if (this.undoHoverChecker.checkHover(mouseX, mouseY, canHover)) + this.owningScreen.drawToolTip(undoToolTip, mouseX, mouseY); + + if (this.defaultHoverChecker.checkHover(mouseX, mouseY, canHover)) + this.owningScreen.drawToolTip(defaultToolTip, mouseX, mouseY); + } + + @Override + public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) + { + if (this.btnDefault.mousePressed(this.mc, x, y)) + { + btnDefault.func_146113_a(mc.getSoundHandler()); + setToDefault(); + return true; + } + else if (this.btnUndoChanges.mousePressed(this.mc, x, y)) + { + btnUndoChanges.func_146113_a(mc.getSoundHandler()); + undoChanges(); + return true; + } + return false; + } + + @Override + public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) + { + this.btnDefault.mouseReleased(x, y); + } + + @Override + public abstract boolean isDefault(); + + @Override + public abstract void setToDefault(); + + @Override + public abstract void keyTyped(char eventChar, int eventKey); + + @Override + public abstract void updateCursorCounter(); + + @Override + public abstract void mouseClicked(int x, int y, int mouseEvent); + + @Override + public abstract boolean isChanged(); + + @Override + public abstract void undoChanges(); + + @Override + public abstract boolean saveConfigElement(); + + @Override + public boolean enabled() + { + return owningScreen.isWorldRunning ? !owningScreen.allRequireWorldRestart && !configElement.requiresWorldRestart() : true; + } + + @Override + public int getLabelWidth() + { + return this.mc.fontRenderer.getStringWidth(this.name); + } + + @Override + public int getEntryRightBound() + { + return this.owningEntryList.resetX + 40; + } + + @Override + public IConfigElement getConfigElement() + { + return configElement; + } + + @Override + public String getName() + { + return configElement.getName(); + } + + @Override + public abstract Object getCurrentValue(); + + @Override + public abstract Object[] getCurrentValues(); + + public void onGuiClosed() + {} + } + + /** + * Provides an interface for defining GuiPropertyList.listEntry objects. + */ + public static interface IConfigEntry extends GuiListExtended.IGuiListEntry + { + /** + * Gets the IConfigElement object owned by this entry. + * @return + */ + public IConfigElement getConfigElement(); + + /** + * Gets the name of the ConfigElement owned by this entry. + */ + public String getName(); + + /** + * Gets the current value of this entry as a String. + */ + public T getCurrentValue(); + + /** + * Gets the current values of this list entry as a String[]. + */ + public T[] getCurrentValues(); + + /** + * Is this list entry enabled? + * + * @return true if this entry's controls should be enabled, false otherwise. + */ + public boolean enabled(); + + /** + * Handles user keystrokes for any GuiTextField objects in this entry. Call {@code GuiTextField.keyTyped()} for any GuiTextField + * objects that should receive the input provided. + */ + public void keyTyped(char eventChar, int eventKey); + + /** + * Call {@code GuiTextField.updateCursorCounter()} for any GuiTextField objects in this entry. + */ + public void updateCursorCounter(); + + /** + * Call {@code GuiTextField.mouseClicked()} for and GuiTextField objects in this entry. + */ + public void mouseClicked(int x, int y, int mouseEvent); + + /** + * Is this entry's value equal to the default value? Generally true should be returned if this entry is not a property or category + * entry. + * + * @return true if this entry's value is equal to this entry's default value. + */ + public boolean isDefault(); + + /** + * Sets this entry's value to the default value. + */ + public void setToDefault(); + + /** + * Handles reverting any changes that have occurred to this entry. + */ + public void undoChanges(); + + /** + * Has the value of this entry changed? + * + * @return true if changes have been made to this entry's value, false otherwise. + */ + public boolean isChanged(); + + /** + * Handles saving any changes that have been made to this entry back to the underlying object. It is a good practice to check + * isChanged() before performing the save action. This method should return true if the element has changed AND REQUIRES A RESTART. + */ + public boolean saveConfigElement(); + + /** + * Handles drawing any tooltips that apply to this entry. This method is called after all other GUI elements have been drawn to the + * screen, so it could also be used to draw any GUI element that needs to be drawn after all entries have had drawEntry() called. + */ + public void drawToolTip(int mouseX, int mouseY); + + /** + * Gets this entry's label width. + */ + public int getLabelWidth(); + + /** + * Gets this entry's right-hand x boundary. This value is used to control where the scroll bar is placed. + */ + public int getEntryRightBound(); + + /** + * This method is called when the parent GUI is closed. Most handlers won't need this; it is provided for special cases. + */ + public void onGuiClosed(); + } +} \ No newline at end of file diff --git a/src/main/java/cpw/mods/fml/client/config/GuiEditArray.java b/src/main/java/cpw/mods/fml/client/config/GuiEditArray.java new file mode 100644 index 0000000..f950d38 --- /dev/null +++ b/src/main/java/cpw/mods/fml/client/config/GuiEditArray.java @@ -0,0 +1,204 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2014 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors (this class): + * bspkrs - implementation + */ + +package cpw.mods.fml.client.config; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; +import net.minecraft.util.EnumChatFormatting; + +import static cpw.mods.fml.client.config.GuiUtils.RESET_CHAR; +import static cpw.mods.fml.client.config.GuiUtils.UNDO_CHAR; + +import org.lwjgl.input.Keyboard; + +/** + * This class is the base screen used for editing an array-type property. It provides a list of array entries for the user to edit. + * This screen is invoked from a GuiConfig screen by controls that use the EditListPropEntry IGuiConfigListEntry object. + * + * @author bspkrs + */ +public class GuiEditArray extends GuiScreen +{ + protected GuiScreen parentScreen; + protected IConfigElement configElement; + private GuiEditArrayEntries entryList; + private GuiButtonExt btnUndoChanges, btnDefault, btnDone; + private String title; + protected String titleLine2; + protected String titleLine3; + protected int slotIndex; + private final Object[] beforeValues; + private Object[] currentValues; + private HoverChecker tooltipHoverChecker; + private List toolTip; + protected boolean enabled; + + public GuiEditArray(GuiScreen parentScreen, IConfigElement configElement, int slotIndex, Object[] currentValues, boolean enabled) + { + this.mc = Minecraft.getMinecraft(); + this.parentScreen = parentScreen; + this.configElement = configElement; + this.slotIndex = slotIndex; + this.beforeValues = currentValues; + this.currentValues = currentValues; + this.toolTip = new ArrayList(); + this.enabled = enabled; + String propName = I18n.format(configElement.getLanguageKey()); + String comment; + + comment = I18n.format(configElement.getLanguageKey() + ".tooltip", + "\n" + EnumChatFormatting.AQUA, configElement.getDefault(), configElement.getMinValue(), configElement.getMaxValue()); + + if (!comment.equals(configElement.getLanguageKey() + ".tooltip")) + toolTip = mc.fontRenderer.listFormattedStringToWidth( + EnumChatFormatting.GREEN + propName + "\n" + EnumChatFormatting.YELLOW + comment, 300); + else if (configElement.getComment() != null && !configElement.getComment().trim().isEmpty()) + toolTip = mc.fontRenderer.listFormattedStringToWidth( + EnumChatFormatting.GREEN + propName + "\n" + EnumChatFormatting.YELLOW + configElement.getComment(), 300); + else + toolTip = mc.fontRenderer.listFormattedStringToWidth( + EnumChatFormatting.GREEN + propName + "\n" + EnumChatFormatting.RED + "No tooltip defined.", 300); + + if (parentScreen instanceof GuiConfig) + { + this.title = ((GuiConfig) parentScreen).title; + if (((GuiConfig) parentScreen).titleLine2 != null) + { + this.titleLine2 = ((GuiConfig) parentScreen).titleLine2; + this.titleLine3 = I18n.format(configElement.getLanguageKey()); + } + else + this.titleLine2 = I18n.format(configElement.getLanguageKey()); + this.tooltipHoverChecker = new HoverChecker(28, 37, 0, parentScreen.width, 800); + } + else + { + this.title = I18n.format(configElement.getLanguageKey()); + this.tooltipHoverChecker = new HoverChecker(8, 17, 0, parentScreen.width, 800); + } + } + + @Override + public void initGui() + { + this.entryList = new GuiEditArrayEntries(this, this.mc, this.configElement, this.beforeValues, this.currentValues); + + int undoGlyphWidth = mc.fontRenderer.getStringWidth(UNDO_CHAR) * 2; + int resetGlyphWidth = mc.fontRenderer.getStringWidth(RESET_CHAR) * 2; + int doneWidth = Math.max(mc.fontRenderer.getStringWidth(I18n.format("gui.done")) + 20, 100); + int undoWidth = mc.fontRenderer.getStringWidth(" " + I18n.format("fml.configgui.tooltip.undoChanges")) + undoGlyphWidth + 20; + int resetWidth = mc.fontRenderer.getStringWidth(" " + I18n.format("fml.configgui.tooltip.resetToDefault")) + resetGlyphWidth + 20; + int buttonWidthHalf = (doneWidth + 5 + undoWidth + 5 + resetWidth) / 2; + this.buttonList.add(btnDone = new GuiButtonExt(2000, this.width / 2 - buttonWidthHalf, this.height - 29, doneWidth, 20, I18n.format("gui.done"))); + this.buttonList.add(btnDefault = new GuiUnicodeGlyphButton(2001, this.width / 2 - buttonWidthHalf + doneWidth + 5 + undoWidth + 5, + this.height - 29, resetWidth, 20, " " + I18n.format("fml.configgui.tooltip.resetToDefault"), RESET_CHAR, 2.0F)); + this.buttonList.add(btnUndoChanges = new GuiUnicodeGlyphButton(2002, this.width / 2 - buttonWidthHalf + doneWidth + 5, + this.height - 29, undoWidth, 20, " " + I18n.format("fml.configgui.tooltip.undoChanges"), UNDO_CHAR, 2.0F)); + } + + @Override + protected void actionPerformed(GuiButton button) + { + if (button.id == 2000) + { + try + { + this.entryList.saveListChanges(); + } + catch (Throwable e) + { + e.printStackTrace(); + } + this.mc.displayGuiScreen(this.parentScreen); + } + else if (button.id == 2001) + { + this.currentValues = (String[]) configElement.getDefaults(); + this.entryList = new GuiEditArrayEntries(this, this.mc, this.configElement, this.beforeValues, this.currentValues); + } + else if (button.id == 2002) + { + this.currentValues = Arrays.copyOf(beforeValues, beforeValues.length); + this.entryList = new GuiEditArrayEntries(this, this.mc, this.configElement, this.beforeValues, this.currentValues); + } + } + + @Override + protected void mouseClicked(int x, int y, int mouseEvent) + { + if (mouseEvent != 0 || !this.entryList.func_148179_a(x, y, mouseEvent)) + { + this.entryList.mouseClicked(x, y, mouseEvent); + super.mouseClicked(x, y, mouseEvent); + } + } + + @Override + protected void mouseMovedOrUp(int x, int y, int mouseEvent) + { + if (mouseEvent != 0 || !this.entryList.func_148181_b(x, y, mouseEvent)) + { + super.mouseMovedOrUp(x, y, mouseEvent); + } + } + + @Override + protected void keyTyped(char eventChar, int eventKey) + { + if (eventKey == Keyboard.KEY_ESCAPE) + this.mc.displayGuiScreen(parentScreen); + else + this.entryList.keyTyped(eventChar, eventKey); + } + + @Override + public void updateScreen() + { + super.updateScreen(); + this.entryList.updateScreen(); + } + + @Override + public void drawScreen(int par1, int par2, float par3) + { + this.drawDefaultBackground(); + this.entryList.drawScreen(par1, par2, par3); + this.drawCenteredString(this.fontRendererObj, this.title, this.width / 2, 8, 16777215); + + if (this.titleLine2 != null) + this.drawCenteredString(this.fontRendererObj, this.titleLine2, this.width / 2, 18, 16777215); + + if (this.titleLine3 != null) + this.drawCenteredString(this.fontRendererObj, this.titleLine3, this.width / 2, 28, 16777215); + + this.btnDone.enabled = this.entryList.isListSavable(); + this.btnDefault.enabled = enabled && !this.entryList.isDefault(); + this.btnUndoChanges.enabled = enabled && this.entryList.isChanged(); + super.drawScreen(par1, par2, par3); + this.entryList.drawScreenPost(par1, par2, par3); + + if (this.tooltipHoverChecker != null && this.tooltipHoverChecker.checkHover(par1, par2)) + drawToolTip(this.toolTip, par1, par2); + } + + public void drawToolTip(List stringList, int x, int y) + { + this.func_146283_a(stringList, x, y); + } +} \ No newline at end of file diff --git a/src/main/java/cpw/mods/fml/client/config/GuiEditArrayEntries.java b/src/main/java/cpw/mods/fml/client/config/GuiEditArrayEntries.java new file mode 100644 index 0000000..ad02937 --- /dev/null +++ b/src/main/java/cpw/mods/fml/client/config/GuiEditArrayEntries.java @@ -0,0 +1,671 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2014 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors (this class): + * bspkrs - implementation + */ + +package cpw.mods.fml.client.config; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiListExtended; +import net.minecraft.client.gui.GuiTextField; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.resources.I18n; +import net.minecraft.util.EnumChatFormatting; + +import org.lwjgl.input.Keyboard; + +import static cpw.mods.fml.client.config.GuiUtils.INVALID; +import static cpw.mods.fml.client.config.GuiUtils.VALID; + +import cpw.mods.fml.client.config.GuiConfigEntries.ArrayEntry; +import cpw.mods.fml.common.FMLLog; + +/** + * This class implements the scrolling list functionality of the GuiEditList screen. It also provides all the default controls + * for editing array-type properties. + * + * @author bspkrs + */ +public class GuiEditArrayEntries extends GuiListExtended +{ + private GuiEditArray owningGui; + public Minecraft mc; + public IConfigElement configElement; + public List listEntries; + public boolean isDefault; + public boolean isChanged; + public boolean canAddMoreEntries; + public final int controlWidth; + public final Object[] beforeValues; + public Object[] currentValues; + + public GuiEditArrayEntries(GuiEditArray parent, Minecraft mc, IConfigElement configElement, Object[] beforeValues, Object[] currentValues) + { + super(mc, parent.width, parent.height, parent.titleLine2 != null ? (parent.titleLine3 != null ? 43 : 33) : 23, parent.height - 32, 20); + this.owningGui = parent; + this.mc = mc; + this.configElement = configElement; + this.beforeValues = beforeValues; + this.currentValues = currentValues; + this.setShowSelectionBox(false); + this.isChanged = !Arrays.deepEquals(beforeValues, currentValues); + this.isDefault = Arrays.deepEquals(currentValues, configElement.getDefaults()); + this.canAddMoreEntries = !configElement.isListLengthFixed() && (configElement.getMaxListLength() == -1 || currentValues.length < configElement.getMaxListLength()); + + listEntries = new ArrayList(); + + controlWidth = (parent.width / 2) - (configElement.isListLengthFixed() ? 0 : 48); + + if (configElement.isList() && configElement.getArrayEntryClass() != null) + { + Class clazz = configElement.getArrayEntryClass(); + for (Object value : currentValues) + { + try + { + listEntries.add(clazz.getConstructor(GuiEditArray.class, GuiEditArrayEntries.class, IConfigElement.class, Object.class) + .newInstance(this.owningGui, this, configElement, value.toString())); + } + catch (Throwable e) + { + FMLLog.severe("There was a critical error instantiating the custom IGuiEditListEntry for property %s.", configElement.getName()); + e.printStackTrace(); + } + } + } + else if (configElement.isList() && configElement.getType().equals(ConfigGuiType.BOOLEAN)) + for (Object value : currentValues) + listEntries.add(new BooleanEntry(this.owningGui, this, configElement, Boolean.valueOf(value.toString()))); + else if (configElement.isList() && configElement.getType().equals(ConfigGuiType.INTEGER)) + for (Object value : currentValues) + listEntries.add(new IntegerEntry(this.owningGui, this, configElement, Integer.parseInt(value.toString()))); + else if (configElement.isList() && configElement.getType().equals(ConfigGuiType.DOUBLE)) + for (Object value : currentValues) + listEntries.add(new DoubleEntry(this.owningGui, this, configElement, Double.parseDouble(value.toString()))); + else if (configElement.isList()) + for (Object value : currentValues) + listEntries.add(new StringEntry(this.owningGui, this, configElement, value.toString())); + + if (!configElement.isListLengthFixed()) + listEntries.add(new BaseEntry(this.owningGui, this, configElement)); + + } + + @Override + protected int getScrollBarX() + { + return width - (width / 4); + } + + /** + * Gets the width of the list + */ + @Override + public int getListWidth() + { + return owningGui.width; + } + + @Override + public IArrayEntry getListEntry(int index) + { + return listEntries.get(index); + } + + @Override + protected int getSize() + { + return listEntries.size(); + } + + public void addNewEntry(int index) + { + if (configElement.isList() && configElement.getType() == ConfigGuiType.BOOLEAN) + listEntries.add(index, new BooleanEntry(this.owningGui, this, this.configElement, Boolean.valueOf(true))); + else if (configElement.isList() && configElement.getType() == ConfigGuiType.INTEGER) + listEntries.add(index, new IntegerEntry(this.owningGui, this, this.configElement, 0)); + else if (configElement.isList() && configElement.getType() == ConfigGuiType.DOUBLE) + listEntries.add(index, new DoubleEntry(this.owningGui, this, this.configElement, 0.0D)); + else if (configElement.isList()) + listEntries.add(index, new StringEntry(this.owningGui, this, this.configElement, "")); + this.canAddMoreEntries = !configElement.isListLengthFixed() + && (configElement.getMaxListLength() == -1 || this.listEntries.size() - 1 < configElement.getMaxListLength()); + keyTyped((char) Keyboard.CHAR_NONE, Keyboard.KEY_END); + } + + public void removeEntry(int index) + { + this.listEntries.remove(index); + this.canAddMoreEntries = !configElement.isListLengthFixed() + && (configElement.getMaxListLength() == -1 || this.listEntries.size() - 1 < configElement.getMaxListLength()); + keyTyped((char) Keyboard.CHAR_NONE, Keyboard.KEY_END); + } + + public boolean isChanged() + { + return isChanged; + } + + public boolean isDefault() + { + return isDefault; + } + + public void recalculateState() + { + isDefault = true; + isChanged = false; + + int listLength = configElement.isListLengthFixed() ? listEntries.size() : listEntries.size() - 1; + + if (listLength != configElement.getDefaults().length) + { + isDefault = false; + } + + if (listLength != beforeValues.length) + { + isChanged = true; + } + + if (isDefault) + for (int i = 0; i < listLength; i++) + if (!configElement.getDefaults()[i].equals(listEntries.get(i).getValue())) + isDefault = false; + + if (!isChanged) + for (int i = 0; i < listLength; i++) + if (!beforeValues[i].equals(listEntries.get(i).getValue())) + isChanged = true; + } + + protected void keyTyped(char eventChar, int eventKey) + { + for (IArrayEntry entry : this.listEntries) + entry.keyTyped(eventChar, eventKey); + + recalculateState(); + } + + protected void updateScreen() + { + for (IArrayEntry entry : this.listEntries) + entry.updateCursorCounter(); + } + + protected void mouseClicked(int x, int y, int mouseEvent) + { + for (IArrayEntry entry : this.listEntries) + entry.mouseClicked(x, y, mouseEvent); + } + + protected boolean isListSavable() + { + for (IArrayEntry entry : this.listEntries) + if (!entry.isValueSavable()) + return false; + + return true; + } + + protected void saveListChanges() + { + int listLength = configElement.isListLengthFixed() ? listEntries.size() : listEntries.size() - 1; + + if (owningGui.slotIndex != -1 && owningGui.parentScreen != null + && owningGui.parentScreen instanceof GuiConfig + && ((GuiConfig) owningGui.parentScreen).entryList.getListEntry(owningGui.slotIndex) instanceof ArrayEntry) + { + ArrayEntry entry = (ArrayEntry) ((GuiConfig) owningGui.parentScreen).entryList.getListEntry(owningGui.slotIndex); + + Object[] ao = new Object[listLength]; + for (int i = 0; i < listLength; i++) + ao[i] = listEntries.get(i).getValue(); + + entry.setListFromChildScreen(ao); + } + else + { + if (configElement.isList() && configElement.getType() == ConfigGuiType.BOOLEAN) + { + Boolean[] abol = new Boolean[listLength]; + for (int i = 0; i < listLength; i++) + abol[i] = Boolean.valueOf(listEntries.get(i).getValue().toString()); + + configElement.set(abol); + } + else if (configElement.isList() && configElement.getType() == ConfigGuiType.INTEGER) + { + Integer[] ai = new Integer[listLength]; + for (int i = 0; i < listLength; i++) + ai[i] = Integer.valueOf(listEntries.get(i).getValue().toString()); + + configElement.set(ai); + } + else if (configElement.isList() && configElement.getType() == ConfigGuiType.DOUBLE) + { + Double[] ad = new Double[listLength]; + for (int i = 0; i < listLength; i++) + ad[i] = Double.valueOf(listEntries.get(i).getValue().toString()); + + configElement.set(ad); + } + else if (configElement.isList()) + { + String[] as = new String[listLength]; + for (int i = 0; i < listLength; i++) + as[i] = listEntries.get(i).getValue().toString(); + + configElement.set(as); + } + } + } + + protected void drawScreenPost(int mouseX, int mouseY, float f) + { + for (IArrayEntry entry : this.listEntries) + entry.drawToolTip(mouseX, mouseY); + } + + /** + * IGuiListEntry Inner Classes + */ + + public static class DoubleEntry extends StringEntry + { + public DoubleEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, Double value) + { + super(owningScreen, owningEntryList, configElement, value); + this.isValidated = true; + } + + @Override + public void keyTyped(char eventChar, int eventKey) + { + if (owningScreen.enabled || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT + || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END) + { + String validChars = "0123456789"; + String before = this.textFieldValue.getText(); + if (validChars.contains(String.valueOf(eventChar)) || + (!before.startsWith("-") && this.textFieldValue.getCursorPosition() == 0 && eventChar == '-') + || (!before.contains(".") && eventChar == '.') + || eventKey == Keyboard.KEY_BACK || eventKey == Keyboard.KEY_DELETE || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT + || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END) + this.textFieldValue.textboxKeyTyped((owningScreen.enabled ? eventChar : Keyboard.CHAR_NONE), eventKey); + + if (!textFieldValue.getText().trim().isEmpty() && !textFieldValue.getText().trim().equals("-")) + { + try + { + double value = Double.parseDouble(textFieldValue.getText().trim()); + if (value < Double.valueOf(configElement.getMinValue().toString()) || value > Double.valueOf(configElement.getMaxValue().toString())) + this.isValidValue = false; + else + this.isValidValue = true; + } + catch (Throwable e) + { + this.isValidValue = false; + } + } + else + this.isValidValue = false; + } + } + + @Override + public Double getValue() + { + try + { + return Double.valueOf(this.textFieldValue.getText().trim()); + } + catch (Throwable e) + { + return Double.MAX_VALUE; + } + } + } + + public static class IntegerEntry extends StringEntry + { + public IntegerEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, Integer value) + { + super(owningScreen, owningEntryList, configElement, value); + this.isValidated = true; + } + + @Override + public void keyTyped(char eventChar, int eventKey) + { + if (owningScreen.enabled || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT + || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END) + { + String validChars = "0123456789"; + String before = this.textFieldValue.getText(); + if (validChars.contains(String.valueOf(eventChar)) + || (!before.startsWith("-") && this.textFieldValue.getCursorPosition() == 0 && eventChar == '-') + || eventKey == Keyboard.KEY_BACK || eventKey == Keyboard.KEY_DELETE + || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END) + this.textFieldValue.textboxKeyTyped((owningScreen.enabled ? eventChar : Keyboard.CHAR_NONE), eventKey); + + if (!textFieldValue.getText().trim().isEmpty() && !textFieldValue.getText().trim().equals("-")) + { + try + { + long value = Long.parseLong(textFieldValue.getText().trim()); + if (value < Integer.valueOf(configElement.getMinValue().toString()) || value > Integer.valueOf(configElement.getMaxValue().toString())) + this.isValidValue = false; + else + this.isValidValue = true; + } + catch (Throwable e) + { + this.isValidValue = false; + } + } + else + this.isValidValue = false; + } + } + + @Override + public Integer getValue() + { + try + { + return Integer.valueOf(this.textFieldValue.getText().trim()); + } + catch (Throwable e) + { + return Integer.MAX_VALUE; + } + } + } + + public static class StringEntry extends BaseEntry + { + protected final GuiTextField textFieldValue; + + public StringEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, Object value) + { + super(owningScreen, owningEntryList, configElement); + this.textFieldValue = new GuiTextField(owningEntryList.mc.fontRenderer, owningEntryList.width / 4 + 1, 0, owningEntryList.controlWidth - 3, 16); + this.textFieldValue.setMaxStringLength(10000); + this.textFieldValue.setText(value.toString()); + this.isValidated = configElement.getValidationPattern() != null; + + if (configElement.getValidationPattern() != null) + { + if (configElement.getValidationPattern().matcher(this.textFieldValue.getText().trim()).matches()) + isValidValue = true; + else + isValidValue = false; + } + } + + @Override + public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, Tessellator tessellator, int mouseX, int mouseY, boolean isSelected) + { + super.drawEntry(slotIndex, x, y, listWidth, slotHeight, tessellator, mouseX, mouseY, isSelected); + if (configElement.isListLengthFixed() || slotIndex != owningEntryList.listEntries.size() - 1) + { + this.textFieldValue.setVisible(true); + this.textFieldValue.yPosition = y + 1; + this.textFieldValue.drawTextBox(); + } + else + this.textFieldValue.setVisible(false); + } + + @Override + public void keyTyped(char eventChar, int eventKey) + { + if (owningScreen.enabled || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT + || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END) + { + this.textFieldValue.textboxKeyTyped((owningScreen.enabled ? eventChar : Keyboard.CHAR_NONE), eventKey); + + if (configElement.getValidationPattern() != null) + { + if (configElement.getValidationPattern().matcher(this.textFieldValue.getText().trim()).matches()) + isValidValue = true; + else + isValidValue = false; + } + } + } + + @Override + public void updateCursorCounter() + { + this.textFieldValue.updateCursorCounter(); + } + + @Override + public void mouseClicked(int x, int y, int mouseEvent) + { + this.textFieldValue.mouseClicked(x, y, mouseEvent); + } + + @Override + public Object getValue() + { + return this.textFieldValue.getText().trim(); + } + + } + + public static class BooleanEntry extends BaseEntry + { + protected final GuiButtonExt btnValue; + private boolean value; + + public BooleanEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, boolean value) + { + super(owningScreen, owningEntryList, configElement); + this.value = value; + this.btnValue = new GuiButtonExt(0, 0, 0, owningEntryList.controlWidth, 18, I18n.format(String.valueOf(value))); + this.btnValue.enabled = owningScreen.enabled; + this.isValidated = false; + } + + @Override + public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, Tessellator tessellator, int mouseX, int mouseY, boolean isSelected) + { + super.drawEntry(slotIndex, x, y, listWidth, slotHeight, tessellator, mouseX, mouseY, isSelected); + this.btnValue.xPosition = listWidth / 4; + this.btnValue.yPosition = y; + + String trans = I18n.format(String.valueOf(value)); + if (!trans.equals(String.valueOf(value))) + this.btnValue.displayString = trans; + else + this.btnValue.displayString = String.valueOf(value); + btnValue.packedFGColour = value ? GuiUtils.getColorCode('2', true) : GuiUtils.getColorCode('4', true); + + this.btnValue.drawButton(owningEntryList.mc, mouseX, mouseY); + } + + @Override + public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) + { + if (this.btnValue.mousePressed(owningEntryList.mc, x, y)) + { + btnValue.func_146113_a(owningEntryList.mc.getSoundHandler()); + value = !value; + owningEntryList.recalculateState(); + return true; + } + + return super.mousePressed(index, x, y, mouseEvent, relativeX, relativeY); + } + + @Override + public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) + { + this.btnValue.mouseReleased(x, y); + super.mouseReleased(index, x, y, mouseEvent, relativeX, relativeY); + } + + @Override + public Object getValue() + { + return Boolean.valueOf(value); + } + } + + public static class BaseEntry implements IArrayEntry + { + protected final GuiEditArray owningScreen; + protected final GuiEditArrayEntries owningEntryList; + protected final IConfigElement configElement; + protected final GuiButtonExt btnAddNewEntryAbove; + private final HoverChecker addNewEntryAboveHoverChecker; + protected final GuiButtonExt btnRemoveEntry; + private final HoverChecker removeEntryHoverChecker; + private List addNewToolTip, removeToolTip; + protected boolean isValidValue = true; + protected boolean isValidated = false; + + public BaseEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement) + { + this.owningScreen = owningScreen; + this.owningEntryList = owningEntryList; + this.configElement = configElement; + this.btnAddNewEntryAbove = new GuiButtonExt(0, 0, 0, 18, 18, "+"); + this.btnAddNewEntryAbove.packedFGColour = GuiUtils.getColorCode('2', true); + this.btnAddNewEntryAbove.enabled = owningScreen.enabled; + this.btnRemoveEntry = new GuiButtonExt(0, 0, 0, 18, 18, "x"); + this.btnRemoveEntry.packedFGColour = GuiUtils.getColorCode('c', true); + this.btnRemoveEntry.enabled = owningScreen.enabled; + this.addNewEntryAboveHoverChecker = new HoverChecker(this.btnAddNewEntryAbove, 800); + this.removeEntryHoverChecker = new HoverChecker(this.btnRemoveEntry, 800); + this.addNewToolTip = new ArrayList(); + this.removeToolTip = new ArrayList(); + addNewToolTip.add(I18n.format("fml.configgui.tooltip.addNewEntryAbove")); + removeToolTip.add(I18n.format("fml.configgui.tooltip.removeEntry")); + } + + @Override + public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, Tessellator tessellator, int mouseX, int mouseY, boolean isSelected) + { + if (this.getValue() != null && this.isValidated) + owningEntryList.mc.fontRenderer.drawString( + isValidValue ? EnumChatFormatting.GREEN + VALID : EnumChatFormatting.RED + INVALID, + listWidth / 4 - owningEntryList.mc.fontRenderer.getStringWidth(VALID) - 2, + y + slotHeight / 2 - owningEntryList.mc.fontRenderer.FONT_HEIGHT / 2, + 16777215); + + int half = listWidth / 2; + if (owningEntryList.canAddMoreEntries) + { + this.btnAddNewEntryAbove.visible = true; + this.btnAddNewEntryAbove.xPosition = half + ((half / 2) - 44); + this.btnAddNewEntryAbove.yPosition = y; + this.btnAddNewEntryAbove.drawButton(owningEntryList.mc, mouseX, mouseY); + } + else + this.btnAddNewEntryAbove.visible = false; + + if (!configElement.isListLengthFixed() && slotIndex != owningEntryList.listEntries.size() - 1) + { + this.btnRemoveEntry.visible = true; + this.btnRemoveEntry.xPosition = half + ((half / 2) - 22); + this.btnRemoveEntry.yPosition = y; + this.btnRemoveEntry.drawButton(owningEntryList.mc, mouseX, mouseY); + } + else + this.btnRemoveEntry.visible = false; + } + + @Override + public void drawToolTip(int mouseX, int mouseY) + { + boolean canHover = mouseY < owningEntryList.bottom && mouseY > owningEntryList.top; + if (this.btnAddNewEntryAbove.visible && this.addNewEntryAboveHoverChecker.checkHover(mouseX, mouseY, canHover)) + owningScreen.drawToolTip(this.addNewToolTip, mouseX, mouseY); + if (this.btnRemoveEntry.visible && this.removeEntryHoverChecker.checkHover(mouseX, mouseY, canHover)) + owningScreen.drawToolTip(this.removeToolTip, mouseX, mouseY); + } + + @Override + public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) + { + if (this.btnAddNewEntryAbove.mousePressed(owningEntryList.mc, x, y)) + { + btnAddNewEntryAbove.func_146113_a(owningEntryList.mc.getSoundHandler()); + owningEntryList.addNewEntry(index); + owningEntryList.recalculateState(); + return true; + } + else if (this.btnRemoveEntry.mousePressed(owningEntryList.mc, x, y)) + { + btnRemoveEntry.func_146113_a(owningEntryList.mc.getSoundHandler()); + owningEntryList.removeEntry(index); + owningEntryList.recalculateState(); + return true; + } + + return false; + } + + @Override + public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) + { + this.btnAddNewEntryAbove.mouseReleased(x, y); + this.btnRemoveEntry.mouseReleased(x, y); + } + + @Override + public void keyTyped(char eventChar, int eventKey) + {} + + @Override + public void updateCursorCounter() + {} + + @Override + public void mouseClicked(int x, int y, int mouseEvent) + {} + + @Override + public boolean isValueSavable() + { + return isValidValue; + } + + @Override + public Object getValue() + { + return null; + } + } + + public static interface IArrayEntry extends GuiListExtended.IGuiListEntry + { + public void keyTyped(char eventChar, int eventKey); + + public void updateCursorCounter(); + + public void mouseClicked(int x, int y, int mouseEvent); + + public void drawToolTip(int mouseX, int mouseY); + + public boolean isValueSavable(); + + public Object getValue(); + } +} \ No newline at end of file diff --git a/src/main/java/cpw/mods/fml/client/config/GuiMessageDialog.java b/src/main/java/cpw/mods/fml/client/config/GuiMessageDialog.java new file mode 100644 index 0000000..b432164 --- /dev/null +++ b/src/main/java/cpw/mods/fml/client/config/GuiMessageDialog.java @@ -0,0 +1,24 @@ +package cpw.mods.fml.client.config; + +import net.minecraft.client.gui.GuiDisconnected; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; +import net.minecraft.util.IChatComponent; + +public class GuiMessageDialog extends GuiDisconnected +{ + private String buttonText; + + public GuiMessageDialog(GuiScreen nextScreen, String title, IChatComponent message, String buttonText) + { + super(nextScreen, title, message); + this.buttonText = buttonText; + } + + public void initGui() + { + super.initGui(); + ((GuiButton) buttonList.get(0)).displayString = I18n.format(buttonText); + } +} \ No newline at end of file diff --git a/src/main/java/cpw/mods/fml/client/config/GuiSelectString.java b/src/main/java/cpw/mods/fml/client/config/GuiSelectString.java new file mode 100644 index 0000000..bf6e38e --- /dev/null +++ b/src/main/java/cpw/mods/fml/client/config/GuiSelectString.java @@ -0,0 +1,172 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2014 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors (this class): + * bspkrs - implementation + */ + +package cpw.mods.fml.client.config; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; +import net.minecraft.util.EnumChatFormatting; + +import static cpw.mods.fml.client.config.GuiUtils.RESET_CHAR; +import static cpw.mods.fml.client.config.GuiUtils.UNDO_CHAR; + +/** + * This class provides a screen that allows the user to select a value from a list. + * + * @author bspkrs + */ +public class GuiSelectString extends GuiScreen +{ + protected GuiScreen parentScreen; + protected IConfigElement configElement; + private GuiSelectStringEntries entriesList; + private GuiButtonExt btnUndoChanges, btnDefault, btnDone; + private String title; + protected String titleLine2; + protected String titleLine3; + protected int slotIndex; + private final Map selectableValues; + public final Object beforeValue; + public Object currentValue; + private HoverChecker tooltipHoverChecker; + private List toolTip; + protected boolean enabled; + + public GuiSelectString(GuiScreen parentScreen, IConfigElement configElement, int slotIndex, Map selectableValues, Object currentValue, boolean enabled) + { + this.mc = Minecraft.getMinecraft(); + this.parentScreen = parentScreen; + this.configElement = configElement; + this.slotIndex = slotIndex; + this.selectableValues = selectableValues; + this.beforeValue = currentValue; + this.currentValue = currentValue; + this.toolTip = new ArrayList(); + this.enabled = enabled; + String propName = I18n.format(configElement.getLanguageKey()); + String comment; + + comment = I18n.format(configElement.getLanguageKey() + ".tooltip", + "\n" + EnumChatFormatting.AQUA, configElement.getDefault(), configElement.getMinValue(), configElement.getMaxValue()); + + if (!comment.equals(configElement.getLanguageKey() + ".tooltip")) + toolTip = mc.fontRenderer.listFormattedStringToWidth( + EnumChatFormatting.GREEN + propName + "\n" + EnumChatFormatting.YELLOW + comment, 300); + else if (configElement.getComment() != null && !configElement.getComment().trim().isEmpty()) + toolTip = mc.fontRenderer.listFormattedStringToWidth( + EnumChatFormatting.GREEN + propName + "\n" + EnumChatFormatting.YELLOW + configElement.getComment(), 300); + else + toolTip = mc.fontRenderer.listFormattedStringToWidth( + EnumChatFormatting.GREEN + propName + "\n" + EnumChatFormatting.RED + "No tooltip defined.", 300); + + if (parentScreen instanceof GuiConfig) + { + this.title = ((GuiConfig) parentScreen).title; + this.titleLine2 = ((GuiConfig) parentScreen).titleLine2; + this.titleLine3 = I18n.format(configElement.getLanguageKey()); + this.tooltipHoverChecker = new HoverChecker(28, 37, 0, parentScreen.width, 800); + + } + else + { + this.title = I18n.format(configElement.getLanguageKey()); + this.tooltipHoverChecker = new HoverChecker(8, 17, 0, parentScreen.width, 800); + } + } + + @Override + public void initGui() + { + this.entriesList = new GuiSelectStringEntries(this, this.mc, this.configElement, this.selectableValues); + + int undoGlyphWidth = mc.fontRenderer.getStringWidth(UNDO_CHAR) * 2; + int resetGlyphWidth = mc.fontRenderer.getStringWidth(RESET_CHAR) * 2; + int doneWidth = Math.max(mc.fontRenderer.getStringWidth(I18n.format("gui.done")) + 20, 100); + int undoWidth = mc.fontRenderer.getStringWidth(" " + I18n.format("fml.configgui.tooltip.undoChanges")) + undoGlyphWidth + 20; + int resetWidth = mc.fontRenderer.getStringWidth(" " + I18n.format("fml.configgui.tooltip.resetToDefault")) + resetGlyphWidth + 20; + int buttonWidthHalf = (doneWidth + 5 + undoWidth + 5 + resetWidth) / 2; + this.buttonList.add(btnDone = new GuiButtonExt(2000, this.width / 2 - buttonWidthHalf, this.height - 29, doneWidth, 20, I18n.format("gui.done"))); + this.buttonList.add(btnDefault = new GuiUnicodeGlyphButton(2001, this.width / 2 - buttonWidthHalf + doneWidth + 5 + undoWidth + 5, + this.height - 29, resetWidth, 20, " " + I18n.format("fml.configgui.tooltip.resetToDefault"), RESET_CHAR, 2.0F)); + this.buttonList.add(btnUndoChanges = new GuiUnicodeGlyphButton(2002, this.width / 2 - buttonWidthHalf + doneWidth + 5, + this.height - 29, undoWidth, 20, " " + I18n.format("fml.configgui.tooltip.undoChanges"), UNDO_CHAR, 2.0F)); + } + + @Override + protected void actionPerformed(GuiButton button) + { + if (button.id == 2000) + { + try + { + this.entriesList.saveChanges(); + } + catch (Throwable e) + { + e.printStackTrace(); + } + this.mc.displayGuiScreen(this.parentScreen); + } + else if (button.id == 2001) + { + this.currentValue = configElement.getDefault(); + this.entriesList = new GuiSelectStringEntries(this, this.mc, this.configElement, this.selectableValues); + } + else if (button.id == 2002) + { + this.currentValue = beforeValue; + this.entriesList = new GuiSelectStringEntries(this, this.mc, this.configElement, this.selectableValues); + } + } + + @Override + protected void mouseMovedOrUp(int x, int y, int mouseEvent) + { + if (mouseEvent != 0 || !this.entriesList.func_148181_b(x, y, mouseEvent)) + { + super.mouseMovedOrUp(x, y, mouseEvent); + } + } + + @Override + public void drawScreen(int par1, int par2, float par3) + { + this.drawDefaultBackground(); + this.entriesList.drawScreen(par1, par2, par3); + this.drawCenteredString(this.fontRendererObj, this.title, this.width / 2, 8, 16777215); + + if (this.titleLine2 != null) + this.drawCenteredString(this.fontRendererObj, this.titleLine2, this.width / 2, 18, 16777215); + + if (this.titleLine3 != null) + this.drawCenteredString(this.fontRendererObj, this.titleLine3, this.width / 2, 28, 16777215); + + this.btnDone.enabled = currentValue != null; + this.btnDefault.enabled = enabled && !this.entriesList.isDefault(); + this.btnUndoChanges.enabled = enabled && this.entriesList.isChanged(); + super.drawScreen(par1, par2, par3); + + if (this.tooltipHoverChecker != null && this.tooltipHoverChecker.checkHover(par1, par2)) + drawToolTip(this.toolTip, par1, par2); + } + + public void drawToolTip(List stringList, int x, int y) + { + this.func_146283_a(stringList, x, y); + } +} \ No newline at end of file diff --git a/src/main/java/cpw/mods/fml/client/config/GuiSelectStringEntries.java b/src/main/java/cpw/mods/fml/client/config/GuiSelectStringEntries.java new file mode 100644 index 0000000..3b889af --- /dev/null +++ b/src/main/java/cpw/mods/fml/client/config/GuiSelectStringEntries.java @@ -0,0 +1,196 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2014 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors (this class): + * bspkrs - implementation + */ + +package cpw.mods.fml.client.config; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiListExtended; +import net.minecraft.client.renderer.Tessellator; +import cpw.mods.fml.client.config.GuiConfigEntries.SelectValueEntry; + +/** + * This class implements the scrolling list functionality of the GuiSelectString screen. + * + * @author bspkrs + */ +public class GuiSelectStringEntries extends GuiListExtended +{ + public GuiSelectString owningScreen; + public Minecraft mc; + public IConfigElement configElement; + public List listEntries; + public final Map selectableValues; + public int selectedIndex = -1; + public int maxEntryWidth = 0; + + public GuiSelectStringEntries(GuiSelectString owningScreen, Minecraft mc, IConfigElement configElement, Map selectableValues) + { + super(mc, owningScreen.width, owningScreen.height, owningScreen.titleLine2 != null ? (owningScreen.titleLine3 != null ? 43 : 33) : 23, + owningScreen.height - 32, 11); + this.owningScreen = owningScreen; + this.mc = mc; + this.configElement = configElement; + this.selectableValues = selectableValues; + this.setShowSelectionBox(true); + + listEntries = new ArrayList(); + + int index = 0; + List> sortedList = new ArrayList>(selectableValues.entrySet()); + Collections.sort(sortedList, new EntryComparator()); + + for (Entry entry : sortedList) + { + listEntries.add(new ListEntry(this, entry)); + if (mc.fontRenderer.getStringWidth(entry.getValue()) > maxEntryWidth) + maxEntryWidth = mc.fontRenderer.getStringWidth(entry.getValue()); + + if (owningScreen.currentValue.equals(entry.getKey())) + { + this.selectedIndex = index; + } + + index++; + } + } + + public static class EntryComparator implements Comparator> + { + @Override + public int compare(Entry o1, Entry o2) + { + int compare = o1.getValue().toLowerCase(Locale.US).compareTo(o2.getValue().toLowerCase(Locale.US)); + + if (compare == 0) + compare = o1.getKey().toString().toLowerCase(Locale.US).compareTo(o2.getKey().toString().toLowerCase(Locale.US)); + + return compare; + } + } + + /** + * The element in the slot that was clicked, boolean for whether it was double clicked or not + */ + @Override + protected void elementClicked(int index, boolean doubleClick, int mouseX, int mouseY) + { + selectedIndex = index; + owningScreen.currentValue = listEntries.get(index).getValue(); + } + + /** + * Returns true if the element passed in is currently selected + */ + @Override + protected boolean isSelected(int index) + { + return index == selectedIndex; + } + + @Override + protected int getScrollBarX() + { + return width / 2 + this.maxEntryWidth / 2 + 5; + } + + /** + * Gets the width of the list + */ + @Override + public int getListWidth() + { + return maxEntryWidth + 5; + } + + @Override + public IGuiSelectStringListEntry getListEntry(int index) + { + return listEntries.get(index); + } + + @Override + protected int getSize() + { + return listEntries.size(); + } + + public boolean isChanged() + { + return owningScreen.beforeValue != null ? !owningScreen.beforeValue.equals(owningScreen.currentValue) : owningScreen.currentValue != null; + } + + public boolean isDefault() + { + return owningScreen.currentValue != null ? owningScreen.currentValue.equals(configElement.getDefault()) : configElement.getDefault() == null; + } + + public void saveChanges() + { + if (owningScreen.slotIndex != -1 && owningScreen.parentScreen != null + && owningScreen.parentScreen instanceof GuiConfig + && ((GuiConfig) owningScreen.parentScreen).entryList.getListEntry(owningScreen.slotIndex) instanceof SelectValueEntry) + { + SelectValueEntry entry = (SelectValueEntry) ((GuiConfig) owningScreen.parentScreen).entryList.getListEntry(owningScreen.slotIndex); + + entry.setValueFromChildScreen(owningScreen.currentValue); + } + else + configElement.set(owningScreen.currentValue); + } + + public static class ListEntry implements IGuiSelectStringListEntry + { + protected final GuiSelectStringEntries owningList; + protected final Entry value; + + public ListEntry(GuiSelectStringEntries owningList, Entry value) + { + this.owningList = owningList; + this.value = value; + } + + @Override + public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, Tessellator tessellator, int mouseX, int mouseY, boolean isSelected) + { + owningList.mc.fontRenderer.drawString(value.getValue(), x + 1, y, slotIndex == owningList.selectedIndex ? 16777215 : 14737632); + } + + @Override + public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) + { + return false; + } + + @Override + public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) + {} + + @Override + public Object getValue() + { + return value.getKey(); + } + } + + public static interface IGuiSelectStringListEntry extends GuiListExtended.IGuiListEntry + { + public Object getValue(); + } +} \ No newline at end of file diff --git a/src/main/java/cpw/mods/fml/client/config/GuiUnicodeGlyphButton.java b/src/main/java/cpw/mods/fml/client/config/GuiUnicodeGlyphButton.java new file mode 100644 index 0000000..5f06563 --- /dev/null +++ b/src/main/java/cpw/mods/fml/client/config/GuiUnicodeGlyphButton.java @@ -0,0 +1,83 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2014 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors (this class): + * bspkrs - implementation + */ + +package cpw.mods.fml.client.config; + +import net.minecraft.client.Minecraft; + +import org.lwjgl.opengl.GL11; + +/** + * This class provides a button that shows a string glyph at the beginning. The glyph can be scaled using the glyphScale parameter. + * + * @author bspkrs + */ +public class GuiUnicodeGlyphButton extends GuiButtonExt +{ + public String glyph; + public float glyphScale; + + public GuiUnicodeGlyphButton(int id, int xPos, int yPos, int width, int height, String displayString, String glyph, float glyphScale) + { + super(id, xPos, yPos, width, height, displayString); + this.glyph = glyph; + this.glyphScale = glyphScale; + } + + @Override + public void drawButton(Minecraft mc, int mouseX, int mouseY) + { + if (this.visible) + { + this.field_146123_n = mouseX >= this.xPosition && mouseY >= this.yPosition && mouseX < this.xPosition + this.width && mouseY < this.yPosition + this.height; + int k = this.getHoverState(this.field_146123_n); + GuiUtils.drawContinuousTexturedBox(buttonTextures, this.xPosition, this.yPosition, 0, 46 + k * 20, this.width, this.height, 200, 20, 2, 3, 2, 2, this.zLevel); + this.mouseDragged(mc, mouseX, mouseY); + int color = 14737632; + + if (packedFGColour != 0) + { + color = packedFGColour; + } + else if (!this.enabled) + { + color = 10526880; + } + else if (this.field_146123_n) + { + color = 16777120; + } + + String buttonText = this.displayString; + int glyphWidth = (int) (mc.fontRenderer.getStringWidth(glyph) * glyphScale); + int strWidth = mc.fontRenderer.getStringWidth(buttonText); + int elipsisWidth = mc.fontRenderer.getStringWidth("..."); + int totalWidth = strWidth + glyphWidth; + + if (totalWidth > width - 6 && totalWidth > elipsisWidth) + buttonText = mc.fontRenderer.trimStringToWidth(buttonText, width - 6 - elipsisWidth).trim() + "..."; + + strWidth = mc.fontRenderer.getStringWidth(buttonText); + totalWidth = glyphWidth + strWidth; + + GL11.glPushMatrix(); + GL11.glScalef(glyphScale, glyphScale, 1.0F); + this.drawCenteredString(mc.fontRenderer, glyph, + (int) (((this.xPosition + (this.width / 2) - (strWidth / 2)) / glyphScale) - (glyphWidth / (2 * glyphScale)) + 2), + (int) (((this.yPosition + ((this.height - 8) / glyphScale) / 2) - 1) / glyphScale), color); + GL11.glPopMatrix(); + + this.drawCenteredString(mc.fontRenderer, buttonText, (int) (this.xPosition + (this.width / 2) + (glyphWidth / glyphScale)), + this.yPosition + (this.height - 8) / 2, color); + } + } +} \ No newline at end of file diff --git a/src/main/java/cpw/mods/fml/client/config/GuiUtils.java b/src/main/java/cpw/mods/fml/client/config/GuiUtils.java new file mode 100644 index 0000000..2798282 --- /dev/null +++ b/src/main/java/cpw/mods/fml/client/config/GuiUtils.java @@ -0,0 +1,194 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2014 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors (this class): + * bspkrs - implementation + */ + +package cpw.mods.fml.client.config; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.util.ResourceLocation; + +import org.lwjgl.opengl.GL11; + +/** + * This class provides several methods and constants used by the Config GUI classes. + * + * @author bspkrs + */ +public class GuiUtils +{ + public static final String UNDO_CHAR = "\u21B6"; + public static final String RESET_CHAR = "\u2604"; + public static final String VALID = "\u2714"; + public static final String INVALID = "\u2715"; + + private static int[] colorCodes = new int[] { 0, 170, 43520, 43690, 11141120, 11141290, 16755200, 11184810, 5592405, 5592575, 5635925, 5636095, 16733525, 16733695, 16777045, 16777215, + 0, 42, 10752, 10794, 2752512, 2752554, 2763264, 2763306, 1381653, 1381695, 1392405, 1392447, 4134165, 4134207, 4144917, 4144959 }; + + public static int getColorCode(char c, boolean isLighter) + { + return colorCodes[isLighter ? "0123456789abcdef".indexOf(c) : "0123456789abcdef".indexOf(c) + 16]; + } + /** + * Draws a textured box of any size (smallest size is borderSize * 2 square) based on a fixed size textured box with continuous borders + * and filler. It is assumed that the desired texture ResourceLocation object has been bound using + * Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation). + * + * @param x x axis offset + * @param y y axis offset + * @param u bound resource location image x offset + * @param v bound resource location image y offset + * @param width the desired box width + * @param height the desired box height + * @param textureWidth the width of the box texture in the resource location image + * @param textureHeight the height of the box texture in the resource location image + * @param borderSize the size of the box's borders + * @param zLevel the zLevel to draw at + */ + public static void drawContinuousTexturedBox(int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight, + int borderSize, float zLevel) + { + drawContinuousTexturedBox(x, y, u, v, width, height, textureWidth, textureHeight, borderSize, borderSize, borderSize, borderSize, zLevel); + } + + /** + * Draws a textured box of any size (smallest size is borderSize * 2 square) based on a fixed size textured box with continuous borders + * and filler. The provided ResourceLocation object will be bound using + * Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation). + * + * @param res the ResourceLocation object that contains the desired image + * @param x x axis offset + * @param y y axis offset + * @param u bound resource location image x offset + * @param v bound resource location image y offset + * @param width the desired box width + * @param height the desired box height + * @param textureWidth the width of the box texture in the resource location image + * @param textureHeight the height of the box texture in the resource location image + * @param borderSize the size of the box's borders + * @param zLevel the zLevel to draw at + */ + public static void drawContinuousTexturedBox(ResourceLocation res, int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight, + int borderSize, float zLevel) + { + drawContinuousTexturedBox(res, x, y, u, v, width, height, textureWidth, textureHeight, borderSize, borderSize, borderSize, borderSize, zLevel); + } + + /** + * Draws a textured box of any size (smallest size is borderSize * 2 square) based on a fixed size textured box with continuous borders + * and filler. The provided ResourceLocation object will be bound using + * Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation). + * + * @param res the ResourceLocation object that contains the desired image + * @param x x axis offset + * @param y y axis offset + * @param u bound resource location image x offset + * @param v bound resource location image y offset + * @param width the desired box width + * @param height the desired box height + * @param textureWidth the width of the box texture in the resource location image + * @param textureHeight the height of the box texture in the resource location image + * @param topBorder the size of the box's top border + * @param bottomBorder the size of the box's bottom border + * @param leftBorder the size of the box's left border + * @param rightBorder the size of the box's right border + * @param zLevel the zLevel to draw at + */ + public static void drawContinuousTexturedBox(ResourceLocation res, int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight, + int topBorder, int bottomBorder, int leftBorder, int rightBorder, float zLevel) + { + Minecraft.getMinecraft().getTextureManager().bindTexture(res); + drawContinuousTexturedBox(x, y, u, v, width, height, textureWidth, textureHeight, topBorder, bottomBorder, leftBorder, rightBorder, zLevel); + } + + /** + * Draws a textured box of any size (smallest size is borderSize * 2 square) based on a fixed size textured box with continuous borders + * and filler. It is assumed that the desired texture ResourceLocation object has been bound using + * Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation). + * + * @param x x axis offset + * @param y y axis offset + * @param u bound resource location image x offset + * @param v bound resource location image y offset + * @param width the desired box width + * @param height the desired box height + * @param textureWidth the width of the box texture in the resource location image + * @param textureHeight the height of the box texture in the resource location image + * @param topBorder the size of the box's top border + * @param bottomBorder the size of the box's bottom border + * @param leftBorder the size of the box's left border + * @param rightBorder the size of the box's right border + * @param zLevel the zLevel to draw at + */ + public static void drawContinuousTexturedBox(int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight, + int topBorder, int bottomBorder, int leftBorder, int rightBorder, float zLevel) + { + GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); + GL11.glEnable(GL11.GL_BLEND); + OpenGlHelper.glBlendFunc(770, 771, 1, 0); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + + int fillerWidth = textureWidth - leftBorder - rightBorder; + int fillerHeight = textureHeight - topBorder - bottomBorder; + int canvasWidth = width - leftBorder - rightBorder; + int canvasHeight = height - topBorder - bottomBorder; + int xPasses = canvasWidth / fillerWidth; + int remainderWidth = canvasWidth % fillerWidth; + int yPasses = canvasHeight / fillerHeight; + int remainderHeight = canvasHeight % fillerHeight; + + // Draw Border + // Top Left + drawTexturedModalRect(x, y, u, v, leftBorder, topBorder, zLevel); + // Top Right + drawTexturedModalRect(x + leftBorder + canvasWidth, y, u + leftBorder + fillerWidth, v, rightBorder, topBorder, zLevel); + // Bottom Left + drawTexturedModalRect(x, y + topBorder + canvasHeight, u, v + topBorder + fillerHeight, leftBorder, bottomBorder, zLevel); + // Bottom Right + drawTexturedModalRect(x + leftBorder + canvasWidth, y + topBorder + canvasHeight, u + leftBorder + fillerWidth, v + topBorder + fillerHeight, rightBorder, bottomBorder, zLevel); + + for (int i = 0; i < xPasses + (remainderWidth > 0 ? 1 : 0); i++) + { + // Top Border + drawTexturedModalRect(x + leftBorder + (i * fillerWidth), y, u + leftBorder, v, (i == xPasses ? remainderWidth : fillerWidth), topBorder, zLevel); + // Bottom Border + drawTexturedModalRect(x + leftBorder + (i * fillerWidth), y + topBorder + canvasHeight, u + leftBorder, v + topBorder + fillerHeight, (i == xPasses ? remainderWidth : fillerWidth), bottomBorder, zLevel); + + // Throw in some filler for good measure + for (int j = 0; j < yPasses + (remainderHeight > 0 ? 1 : 0); j++) + drawTexturedModalRect(x + leftBorder + (i * fillerWidth), y + topBorder + (j * fillerHeight), u + leftBorder, v + topBorder, (i == xPasses ? remainderWidth : fillerWidth), (j == yPasses ? remainderHeight : fillerHeight), zLevel); + } + + // Side Borders + for (int j = 0; j < yPasses + (remainderHeight > 0 ? 1 : 0); j++) + { + // Left Border + drawTexturedModalRect(x, y + topBorder + (j * fillerHeight), u, v + topBorder, leftBorder, (j == yPasses ? remainderHeight : fillerHeight), zLevel); + // Right Border + drawTexturedModalRect(x + leftBorder + canvasWidth, y + topBorder + (j * fillerHeight), u + leftBorder + fillerWidth, v + topBorder, rightBorder, (j == yPasses ? remainderHeight : fillerHeight), zLevel); + } + } + + public static void drawTexturedModalRect(int x, int y, int u, int v, int width, int height, float zLevel) + { + float var7 = 0.00390625F; + float var8 = 0.00390625F; + Tessellator tessellator = Tessellator.instance; + tessellator.startDrawingQuads(); + tessellator.addVertexWithUV((x + 0), (y + height), zLevel, ((u + 0) * var7), ((v + height) * var8)); + tessellator.addVertexWithUV((x + width), (y + height), zLevel, ((u + width) * var7), ((v + height) * var8)); + tessellator.addVertexWithUV((x + width), (y + 0), zLevel, ((u + width) * var7), ((v + 0) * var8)); + tessellator.addVertexWithUV((x + 0), (y + 0), zLevel, ((u + 0) * var7), ((v + 0) * var8)); + tessellator.draw(); + } + +} \ No newline at end of file diff --git a/src/main/java/cpw/mods/fml/client/config/HoverChecker.java b/src/main/java/cpw/mods/fml/client/config/HoverChecker.java new file mode 100644 index 0000000..077e34b --- /dev/null +++ b/src/main/java/cpw/mods/fml/client/config/HoverChecker.java @@ -0,0 +1,96 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2014 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors (this class): + * bspkrs - implementation + */ + +package cpw.mods.fml.client.config; + +import net.minecraft.client.gui.GuiButton; + +/** + * This class implements an easy way to check if the mouse has hovered within a certain region of the screen for a given + * period of time. The region can be defined manually or by supplying a GuiButton object. + * + * @author bspkrs + */ +public class HoverChecker +{ + private int top, bottom, left, right, threshold; + private GuiButton button; + private long hoverStart; + + public HoverChecker(int top, int bottom, int left, int right, int threshold) + { + this.top = top; + this.bottom = bottom; + this.left = left; + this.right = right; + this.threshold = threshold; + this.hoverStart = -1; + } + + public HoverChecker(GuiButton button, int threshold) + { + this.button = button; + this.threshold = threshold; + } + + /** + * Call this method if the intended region has changed such as if the region must follow a scrolling list. + * It is not necessary to call this method if a GuiButton defines the hover region. + */ + public void updateBounds(int top, int bottom, int left, int right) + { + this.top = top; + this.bottom = bottom; + this.left = left; + this.right = right; + } + + /** + * Checks if the mouse is in the hover region. If the specified time period has elapsed the method returns true. + * The hover timer is reset if the mouse is not within the region. + */ + public boolean checkHover(int mouseX, int mouseY) + { + return checkHover(mouseX, mouseY, true); + } + + /** + * Checks if the mouse is in the hover region. If the specified time period has elapsed the method returns true. + * The hover timer is reset if the mouse is not within the region. + */ + public boolean checkHover(int mouseX, int mouseY, boolean canHover) + { + if (this.button != null) + { + this.top = button.yPosition; + this.bottom = button.yPosition + button.height; + this.left = button.xPosition; + this.right = button.xPosition + button.width; + canHover = canHover && button.visible; + } + + if (canHover && hoverStart == -1 && mouseY >= top && mouseY <= bottom && mouseX >= left && mouseX <= right) + hoverStart = System.currentTimeMillis(); + else if (!canHover || mouseY < top || mouseY > bottom || mouseX < left || mouseX > right) + resetHoverTimer(); + + return canHover && hoverStart != -1 && System.currentTimeMillis() - hoverStart >= threshold; + } + + /** + * Manually resets the hover timer. + */ + public void resetHoverTimer() + { + hoverStart = -1; + } +} \ No newline at end of file diff --git a/src/main/java/cpw/mods/fml/client/config/IConfigElement.java b/src/main/java/cpw/mods/fml/client/config/IConfigElement.java new file mode 100644 index 0000000..454ec64 --- /dev/null +++ b/src/main/java/cpw/mods/fml/client/config/IConfigElement.java @@ -0,0 +1,170 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2014 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors (this class): + * bspkrs - implementation + */ + +package cpw.mods.fml.client.config; + +import java.util.List; +import java.util.regex.Pattern; + +import cpw.mods.fml.client.config.GuiConfigEntries.IConfigEntry; +import cpw.mods.fml.client.config.GuiEditArrayEntries.IArrayEntry; + +/** + * This interface provides the information needed by GuiConfig and GuiConfigEntries to display config elements for editing. + * + * @author bspkrs + */ +public interface IConfigElement +{ + /** + * [Property, Category] Is this object a property object? + */ + public boolean isProperty(); + + /** + * This method returns a class that implements {@code IConfigEntry} or null. This class MUST + * provide a constructor with the following parameter types: {@code GuiConfig}, {@code GuiConfigEntries}, {@code IConfigElement} + */ + public Class getConfigEntryClass(); + + /** + * This method returns a class that implements {@code IArrayEntry}. This class MUST provide a constructor with the + * following parameter types: {@code GuiEditArray}, {@code GuiEditArrayEntries}, {@code IConfigElement}, {@code Object} + */ + public Class getArrayEntryClass(); + + /** + * [Property, Category] Gets the name of this object. + */ + public String getName(); + + /** + * [Category] Gets the qualified name of this object. This is typically only used for category objects. + */ + public String getQualifiedName(); + + /** + * [Property, Category] Gets a language key for localization of config GUI entry names. If the same key is specified with .tooltip + * appended to the end, that key will return a localized tooltip when the mouse hovers over the property label/category button. + */ + public String getLanguageKey(); + + /** + * [Property, Category] Gets the comment for this object. Used for the tooltip if getLanguageKey() + ".tooltip" is not defined in the + * .lang file. + */ + public String getComment(); + + /** + * [Category] Gets this category's child categories/properties. + */ + public List getChildElements(); + + /** + * [Property, Category] Gets the ConfigGuiType value corresponding to the type of this property object, or CONFIG_CATEGORY if this is a + * category object. + */ + public ConfigGuiType getType(); + + /** + * [Property] Is this property object a list? + */ + public boolean isList(); + + /** + * [Property] Does this list property have to remain a fixed length? + */ + public boolean isListLengthFixed(); + + /** + * [Property] Gets the max length of this list property, or -1 if the length is unlimited. + */ + public int getMaxListLength(); + + /** + * [Property] Is this property value equal to the default value? + */ + public boolean isDefault(); + + /** + * [Property] Gets this property's default value. If this element is an array, this method should return a String + * representation of that array using Arrays.toString() + */ + public Object getDefault(); + + /** + * [Property] Gets this property's default values. + */ + public Object[] getDefaults(); + + /** + * [Property] Sets this property's value to the default value. + */ + public void setToDefault(); + + /** + * [Property, Category] Whether or not this element is safe to modify while a world is running. For Categories return false if ANY properties + * in the category are modifiable while a world is running, true if all are not. + */ + public boolean requiresWorldRestart(); + + /** + * [Property, Category] Whether or not this element should be allowed to show on config GUIs. + */ + public boolean showInGui(); + + /** + * [Property, Category] Whether or not this element requires Minecraft to be restarted when changed. + */ + public boolean requiresMcRestart(); + + /** + * [Property] Gets this property value. + */ + public Object get(); + + /** + * [Property] Gets this property value as a list. Generally you should be sure of whether the property is a list before calling this. + */ + public Object[] getList(); + + /** + * [Property] Sets this property's value. + */ + public void set(T value); + + /** + * [Property] Sets this property's value to the specified array. + */ + public void set(T[] aVal); + + /** + * [Property] Gets a String array of valid values for this property. This is generally used for String properties to allow the user to + * select a value from a list of valid values. + */ + public String[] getValidValues(); + + /** + * [Property] Gets this property's minimum value. + */ + public T getMinValue(); + + /** + * [Property] Gets this property's maximum value. + */ + public T getMaxValue(); + + /** + * [Property] Gets a Pattern object used in String property input validation. + */ + public Pattern getValidationPattern(); +} \ No newline at end of file diff --git a/src/main/java/cpw/mods/fml/client/event/ConfigChangedEvent.java b/src/main/java/cpw/mods/fml/client/event/ConfigChangedEvent.java new file mode 100644 index 0000000..3ed5434 --- /dev/null +++ b/src/main/java/cpw/mods/fml/client/event/ConfigChangedEvent.java @@ -0,0 +1,85 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2014 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors (this class): + * bspkrs - implementation + */ + +package cpw.mods.fml.client.event; + +import cpw.mods.fml.common.eventhandler.Event; +import cpw.mods.fml.common.eventhandler.Event.HasResult; + +/** + * These events are posted from the GuiConfig screen when the done button is pressed. The events are only posted + * if the parent screen is not an instance of GuiConfig or if the configID field has been set for + * the GuiConfig screen. + * + * Listeners for this event should use OnConfigChanged or PostConfigChanged and check for a specific mod ID. + * For best results the listener should refresh any objects/fields that are set based on the mod's config + * and should serialize the modified config. + * + * @author bspkrs + */ +@HasResult +public class ConfigChangedEvent extends Event +{ + /** + * The Mod ID of the mod whose configuration just changed. + */ + public final String modID; + /** + * Whether or not a world is currently running. + */ + public final boolean isWorldRunning; + /** + * Will be set to true if any elements were changed that require a restart of Minecraft. + */ + public final boolean requiresMcRestart; + /** + * A String identifier for this ConfigChangedEvent. + */ + public final String configID; + + public ConfigChangedEvent(String modID, String configID, boolean isWorldRunning, boolean requiresMcRestart) + { + this.modID = modID; + this.configID = configID; + this.isWorldRunning = isWorldRunning; + this.requiresMcRestart = requiresMcRestart; + } + + /** + * This event is intended to be consumed by the mod whose config has been changed. It fires when the Done button + * has been clicked on a GuiConfig screen and the following conditions are met:
+ * - at least one config element has been changed
+ * - one of these 2 conditions are met:
+ * 1) the parent screen is null or is not an instance of GuiConfig
+ * 2) the configID field has been set to a non-null value for the GuiConfig screen

+ * Modders should check the modID field of the event to ensure they are only acting on their own config screen's event! + */ + public static class OnConfigChangedEvent extends ConfigChangedEvent + { + public OnConfigChangedEvent(String modID, String configID, boolean isWorldRunning, boolean requiresMcRestart) + { + super(modID, configID, isWorldRunning, requiresMcRestart); + } + } + + /** + * This event is provided for mods to consume if they want to be able to check if other mods' configs have been changed. + * This event only fires if the OnConfigChangedEvent result is not DENY. + */ + public static class PostConfigChangedEvent extends ConfigChangedEvent + { + public PostConfigChangedEvent(String modID, String configID, boolean isWorldRunning, boolean requiresMcRestart) + { + super(modID, configID, isWorldRunning, requiresMcRestart); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraft/client/gui/GuiButton.java b/src/main/java/net/minecraft/client/gui/GuiButton.java index e0c749a..3188582 100644 --- a/src/main/java/net/minecraft/client/gui/GuiButton.java +++ b/src/main/java/net/minecraft/client/gui/GuiButton.java @@ -13,8 +13,8 @@ public class GuiButton extends Gui { protected static final ResourceLocation buttonTextures = new ResourceLocation("textures/gui/widgets.png"); - protected int width; - protected int height; + public int width; + public int height; public int xPosition; public int yPosition; public String displayString; diff --git a/src/main/java/net/minecraft/client/gui/GuiSlot.java b/src/main/java/net/minecraft/client/gui/GuiSlot.java index 4ab124e..e308d80 100644 --- a/src/main/java/net/minecraft/client/gui/GuiSlot.java +++ b/src/main/java/net/minecraft/client/gui/GuiSlot.java @@ -12,13 +12,13 @@ public abstract class GuiSlot { private final Minecraft mc; - protected int width; - private int height; - protected int top; - protected int bottom; - protected int right; - protected int left; - protected final int slotHeight; + public int width; + public int height; + public int top; + public int bottom; + public int right; + public int left; + public final int slotHeight; private int scrollUpButtonID; private int scrollDownButtonID; protected int mouseX; @@ -31,7 +31,7 @@ private long lastClicked; private boolean showSelectionBox = true; private boolean hasListHeader; - protected int headerPadding; + public int headerPadding; private boolean field_148164_v = true; private static final String __OBFID = "CL_00000679"; diff --git a/src/main/java/net/minecraft/client/gui/GuiTextField.java b/src/main/java/net/minecraft/client/gui/GuiTextField.java index 231b8ef..58d083d 100644 --- a/src/main/java/net/minecraft/client/gui/GuiTextField.java +++ b/src/main/java/net/minecraft/client/gui/GuiTextField.java @@ -10,10 +10,10 @@ public class GuiTextField extends Gui { private final FontRenderer field_146211_a; - private final int xPosition; - private final int yPosition; - private final int width; - private final int height; + public int xPosition; + public int yPosition; + public int width; + public int height; private String text = ""; private int maxStringLength = 32; private int cursorCounter; diff --git a/src/main/java/net/minecraft/network/rcon/RConThreadClient.java b/src/main/java/net/minecraft/network/rcon/RConThreadClient.java index ba0c809..74cc342 100644 --- a/src/main/java/net/minecraft/network/rcon/RConThreadClient.java +++ b/src/main/java/net/minecraft/network/rcon/RConThreadClient.java @@ -53,16 +53,16 @@ BufferedInputStream bufferedinputstream = new BufferedInputStream(this.clientSocket.getInputStream()); int i = bufferedinputstream.read(this.buffer, 0, 1460); - if (10 <= i) + if (10 > i) { - byte b0 = 0; - int j = RConUtils.getBytesAsLEInt(this.buffer, 0, i); + return; + } - if (j != i - 4) - { - return; - } + byte b0 = 0; + int j = RConUtils.getBytesAsLEInt(this.buffer, 0, i); + if (j == i - 4) + { int i1 = b0 + 4; int k = RConUtils.getBytesAsLEInt(this.buffer, i1, i); i1 += 4; diff --git a/src/main/java/net/minecraft/util/HttpUtil.java b/src/main/java/net/minecraft/util/HttpUtil.java index 0b02dfe..dfd1aa5 100644 --- a/src/main/java/net/minecraft/util/HttpUtil.java +++ b/src/main/java/net/minecraft/util/HttpUtil.java @@ -143,104 +143,108 @@ try { - byte[] abyte = new byte[4096]; - URL url = new URL(p_151223_1_); - urlconnection = url.openConnection(p_151223_6_); - float f = 0.0F; - float f1 = (float)p_151223_3_.entrySet().size(); - Iterator iterator = p_151223_3_.entrySet().iterator(); - - while (iterator.hasNext()) + try { - Entry entry = (Entry)iterator.next(); - urlconnection.setRequestProperty((String)entry.getKey(), (String)entry.getValue()); + byte[] abyte = new byte[4096]; + URL url = new URL(p_151223_1_); + urlconnection = url.openConnection(p_151223_6_); + float f = 0.0F; + float f1 = (float)p_151223_3_.entrySet().size(); + Iterator iterator = p_151223_3_.entrySet().iterator(); + + while (iterator.hasNext()) + { + Entry entry = (Entry)iterator.next(); + urlconnection.setRequestProperty((String)entry.getKey(), (String)entry.getValue()); + + if (p_151223_5_ != null) + { + p_151223_5_.setLoadingProgress((int)(++f / f1 * 100.0F)); + } + } + + inputstream = urlconnection.getInputStream(); + f1 = (float)urlconnection.getContentLength(); + int i = urlconnection.getContentLength(); if (p_151223_5_ != null) { - p_151223_5_.setLoadingProgress((int)(++f / f1 * 100.0F)); + p_151223_5_.resetProgresAndWorkingMessage(String.format("Downloading file (%.2f MB)...", new Object[] {Float.valueOf(f1 / 1000.0F / 1000.0F)})); } - } - inputstream = urlconnection.getInputStream(); - f1 = (float)urlconnection.getContentLength(); - int i = urlconnection.getContentLength(); - - if (p_151223_5_ != null) - { - p_151223_5_.resetProgresAndWorkingMessage(String.format("Downloading file (%.2f MB)...", new Object[] {Float.valueOf(f1 / 1000.0F / 1000.0F)})); - } - - if (p_151223_0_.exists()) - { - long j = p_151223_0_.length(); - - if (j == (long)i) + if (p_151223_0_.exists()) { - p_151223_2_.func_148522_a(p_151223_0_); + long j = p_151223_0_.length(); + if (j == (long)i) + { + p_151223_2_.func_148522_a(p_151223_0_); + + if (p_151223_5_ != null) + { + p_151223_5_.func_146586_a(); + } + + return; + } + + HttpUtil.logger.warn("Deleting " + p_151223_0_ + " as it does not match what we currently have (" + i + " vs our " + j + ")."); + p_151223_0_.delete(); + } + else if (p_151223_0_.getParentFile() != null) + { + p_151223_0_.getParentFile().mkdirs(); + } + + dataoutputstream = new DataOutputStream(new FileOutputStream(p_151223_0_)); + + if (p_151223_4_ > 0 && f1 > (float)p_151223_4_) + { if (p_151223_5_ != null) { p_151223_5_.func_146586_a(); } - return; + throw new IOException("Filesize is bigger than maximum allowed (file is " + f + ", limit is " + p_151223_4_ + ")"); } - HttpUtil.logger.warn("Deleting " + p_151223_0_ + " as it does not match what we currently have (" + i + " vs our " + j + ")."); - p_151223_0_.delete(); - } - else if (p_151223_0_.getParentFile() != null) - { - p_151223_0_.getParentFile().mkdirs(); - } + boolean flag = false; + int k; - dataoutputstream = new DataOutputStream(new FileOutputStream(p_151223_0_)); + while ((k = inputstream.read(abyte)) >= 0) + { + f += (float)k; - if (p_151223_4_ > 0 && f1 > (float)p_151223_4_) - { + if (p_151223_5_ != null) + { + p_151223_5_.setLoadingProgress((int)(f / f1 * 100.0F)); + } + + if (p_151223_4_ > 0 && f > (float)p_151223_4_) + { + if (p_151223_5_ != null) + { + p_151223_5_.func_146586_a(); + } + + throw new IOException("Filesize was bigger than maximum allowed (got >= " + f + ", limit was " + p_151223_4_ + ")"); + } + + dataoutputstream.write(abyte, 0, k); + } + + p_151223_2_.func_148522_a(p_151223_0_); + if (p_151223_5_ != null) { p_151223_5_.func_146586_a(); + return; } - - throw new IOException("Filesize is bigger than maximum allowed (file is " + f + ", limit is " + p_151223_4_ + ")"); } - - boolean flag = false; - int k; - - while ((k = inputstream.read(abyte)) >= 0) + catch (Throwable throwable) { - f += (float)k; - - if (p_151223_5_ != null) - { - p_151223_5_.setLoadingProgress((int)(f / f1 * 100.0F)); - } - - if (p_151223_4_ > 0 && f > (float)p_151223_4_) - { - if (p_151223_5_ != null) - { - p_151223_5_.func_146586_a(); - } - - throw new IOException("Filesize was bigger than maximum allowed (got >= " + f + ", limit was " + p_151223_4_ + ")"); - } - - dataoutputstream.write(abyte, 0, k); + throwable.printStackTrace(); } - - p_151223_2_.func_148522_a(p_151223_0_); - - if (p_151223_5_ != null) - { - p_151223_5_.func_146586_a(); - } - } - catch (Throwable throwable) - { - throwable.printStackTrace(); } finally { diff --git a/src/main/java/net/minecraftforge/client/gui/ForgeGuiFactory.java b/src/main/java/net/minecraftforge/client/gui/ForgeGuiFactory.java new file mode 100644 index 0000000..8504d1a --- /dev/null +++ b/src/main/java/net/minecraftforge/client/gui/ForgeGuiFactory.java @@ -0,0 +1,352 @@ +/** + * This software is provided under the terms of the Minecraft Forge Public + * License v1.0. + */ + +package net.minecraftforge.client.gui; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.regex.Pattern; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.resources.I18n; +import net.minecraftforge.common.ForgeChunkManager; +import net.minecraftforge.common.ForgeModContainer; +import net.minecraftforge.common.config.ConfigCategory; +import net.minecraftforge.common.config.ConfigElement; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; +import cpw.mods.fml.client.IModGuiFactory; +import cpw.mods.fml.client.IModGuiFactory.RuntimeOptionCategoryElement; +import cpw.mods.fml.client.IModGuiFactory.RuntimeOptionGuiHandler; +import cpw.mods.fml.client.config.ConfigGuiType; +import cpw.mods.fml.client.config.DummyConfigElement; +import cpw.mods.fml.client.config.DummyConfigElement.DummyCategoryElement; +import cpw.mods.fml.client.config.GuiButtonExt; +import cpw.mods.fml.client.config.GuiConfig; +import cpw.mods.fml.client.config.GuiConfigEntries; +import cpw.mods.fml.client.config.GuiConfigEntries.CategoryEntry; +import cpw.mods.fml.client.config.GuiConfigEntries.IConfigEntry; +import cpw.mods.fml.client.config.GuiConfigEntries.SelectValueEntry; +import cpw.mods.fml.client.config.GuiConfigEntries.BooleanEntry; +import cpw.mods.fml.client.config.HoverChecker; +import cpw.mods.fml.client.config.IConfigElement; +import cpw.mods.fml.client.config.GuiConfigEntries.ListEntryBase; +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +/** + * This is the base GuiConfig screen class that all the other Forge-specific config screens will be called from. + * Since Forge has multiple config files I thought I would use that opportunity to show some of the ways + * that the config GUI system can be extended to create custom config GUIs that have additional features + * over the base functionality of just displaying Properties and ConfigCategories. + * + * The concepts implemented here are: + * - using custom IConfigEntry objects to define child-screens that have specific Properties listed + * - using custom IConfigEntry objects to define a dummy property that can be used to generate new ConfigCategory objects + * - defining the configID string for a GuiConfig object so that the config changed events will be posted when that GuiConfig screen is closed + * (the configID string is optional; if it is not defined the config changed events will be posted when the top-most GuiConfig screen + * is closed, eg when the parent is null or is not an instance of GuiConfig) + * - overriding the IConfigEntry.enabled() method to control the enabled state of one list entry based on the value of another entry + * - overriding the IConfigEntry.onGuiClosed() method to perform custom actions when the screen that owns the entry is closed (in this + * case a new ConfigCategory is added to the Configuration object) + * + * The config file structure looks like this: + * forge.cfg (general settings all in one category) + * forgeChunkLoading.cfg + * - Forge (category) + * - defaults (category) + * - [optional mod override categories]... + * + * The GUI structure is this: + * Base Screen + * - General Settings (from forge.cfg) + * - Chunk Loader Settings (from forgeChunkLoading.cfg) + * - Defaults (these elements are listed directly on this screen) + * - Mod Overrides + * - Add New Mod Override + * - Mod1 + * - Mod2 + * - etc. + * + * Other things to check out: + * ForgeModContainer.syncConfig() + * ForgeModContainer.onConfigChanged() + * ForgeChunkManager.syncConfigDefaults() + * ForgeChunkManager.loadConfiguration() + */ +public class ForgeGuiFactory implements IModGuiFactory +{ + @Override + public void initialize(Minecraft minecraftInstance) {} + + @Override + public Class mainConfigGuiClass() { return ForgeConfigGui.class; } + + @Override + public Set runtimeGuiCategories() { return null; } + + @Override + public RuntimeOptionGuiHandler getHandlerFor(RuntimeOptionCategoryElement element) { return null; } + + public static class ForgeConfigGui extends GuiConfig + { + public ForgeConfigGui(GuiScreen parentScreen) + { + super(parentScreen, getConfigElements(), "Forge", false, false, I18n.format("forge.configgui.forgeConfigTitle")); + } + + private static List getConfigElements() + { + List list = new ArrayList(); + list.add(new DummyCategoryElement("forgeCfg", "forge.configgui.ctgy.forgeGeneralConfig", GeneralEntry.class)); + list.add(new DummyCategoryElement("forgeChunkLoadingCfg", "forge.configgui.ctgy.forgeChunkLoadingConfig", ChunkLoaderEntry.class)); + return list; + } + + /** + * This custom list entry provides the General Settings entry on the Minecraft Forge Configuration screen. + * It extends the base Category entry class and defines the IConfigElement objects that will be used to build the child screen. + */ + public static class GeneralEntry extends CategoryEntry + { + public GeneralEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop) + { + super(owningScreen, owningEntryList, prop); + } + + @Override + protected GuiScreen buildChildScreen() + { + // This GuiConfig object specifies the configID of the object and as such will force-save when it is closed. The parent + // GuiConfig object's entryList will also be refreshed to reflect the changes. + return new GuiConfig(this.owningScreen, + (new ConfigElement(ForgeModContainer.getConfig().getCategory(Configuration.CATEGORY_GENERAL))).getChildElements(), + this.owningScreen.modID, Configuration.CATEGORY_GENERAL, this.configElement.requiresWorldRestart() || this.owningScreen.allRequireWorldRestart, + this.configElement.requiresMcRestart() || this.owningScreen.allRequireMcRestart, + GuiConfig.getAbridgedConfigPath(ForgeModContainer.getConfig().toString())); + } + } + + /** + * This custom list entry provides the Forge Chunk Manager Config entry on the Minecraft Forge Configuration screen. + * It extends the base Category entry class and defines the IConfigElement objects that will be used to build the child screen. + */ + public static class ChunkLoaderEntry extends CategoryEntry + { + public ChunkLoaderEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop) + { + super(owningScreen, owningEntryList, prop); + } + + @Override + protected GuiScreen buildChildScreen() + { + List list = new ArrayList(); + + list.add(new DummyCategoryElement("forgeChunkLoadingModCfg", "forge.configgui.ctgy.forgeChunkLoadingModConfig", + ModOverridesEntry.class)); + list.addAll((new ConfigElement(ForgeChunkManager.getDefaultsCategory())).getChildElements()); + + // This GuiConfig object specifies the configID of the object and as such will force-save when it is closed. The parent + // GuiConfig object's propertyList will also be refreshed to reflect the changes. + return new GuiConfig(this.owningScreen, list, this.owningScreen.modID, "chunkLoader", + this.configElement.requiresWorldRestart() || this.owningScreen.allRequireWorldRestart, + this.configElement.requiresMcRestart() || this.owningScreen.allRequireMcRestart, + GuiConfig.getAbridgedConfigPath(ForgeChunkManager.getConfig().toString()), + I18n.format("forge.configgui.ctgy.forgeChunkLoadingConfig")); + } + } + + /** + * This custom list entry provides the Mod Overrides entry on the Forge Chunk Loading config screen. + * It extends the base Category entry class and defines the IConfigElement objects that will be used to build the child screen. + * In this case it adds the custom entry for adding a new mod override and lists the existing mod overrides. + */ + public static class ModOverridesEntry extends CategoryEntry + { + public ModOverridesEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop) + { + super(owningScreen, owningEntryList, prop); + } + + /** + * This method is called in the constructor and is used to set the childScreen field. + */ + @Override + protected GuiScreen buildChildScreen() + { + List list = new ArrayList(); + + list.add(new DummyCategoryElement("addForgeChunkLoadingModCfg", "forge.configgui.ctgy.forgeChunkLoadingAddModConfig", + AddModOverrideEntry.class)); + for (ConfigCategory cc : ForgeChunkManager.getModCategories()) + list.add(new ConfigElement(cc)); + + return new GuiConfig(this.owningScreen, list, this.owningScreen.modID, + this.configElement.requiresWorldRestart() || this.owningScreen.allRequireWorldRestart, + this.configElement.requiresMcRestart() || this.owningScreen.allRequireMcRestart, this.owningScreen.title, + I18n.format("forge.configgui.ctgy.forgeChunkLoadingModConfig")); + } + + /** + * By overriding the enabled() method and checking the value of the "enabled" entry this entry is enabled/disabled based on the value of + * the other entry. + */ + @Override + public boolean enabled() + { + for (IConfigEntry entry : this.owningEntryList.listEntries) + { + if (entry.getName().equals("enabled") && entry instanceof BooleanEntry) + { + return Boolean.valueOf(entry.getCurrentValue().toString()); + } + } + + return true; + } + + /** + * Check to see if the child screen's entry list has changed. + */ + @Override + public boolean isChanged() + { + if (childScreen instanceof GuiConfig) + { + GuiConfig child = (GuiConfig) childScreen; + return child.entryList.listEntries.size() != child.initEntries.size() || child.entryList.hasChangedEntry(true); + } + return false; + } + + /** + * Since adding a new entry to the child screen is what constitutes a change here, reset the child + * screen listEntries to the saved list. + */ + @Override + public void undoChanges() + { + if (childScreen instanceof GuiConfig) + { + GuiConfig child = (GuiConfig) childScreen; + for (IConfigEntry ice : child.entryList.listEntries) + if (!child.initEntries.contains(ice) && ForgeChunkManager.getConfig().hasCategory(ice.getName())) + ForgeChunkManager.getConfig().removeCategory(ForgeChunkManager.getConfig().getCategory(ice.getName())); + + child.entryList.listEntries = new ArrayList(child.initEntries); + } + } + } + + /** + * This custom list entry provides a button that will open to a screen that will allow a user to define a new mod override. + */ + public static class AddModOverrideEntry extends CategoryEntry + { + public AddModOverrideEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop) + { + super(owningScreen, owningEntryList, prop); + } + + @Override + protected GuiScreen buildChildScreen() + { + List list = new ArrayList(); + + list.add(new DummyConfigElement("modID", "", ConfigGuiType.STRING, "forge.configgui.modID").setCustomListEntryClass(ModIDEntry.class)); + list.add(new ConfigElement(new Property("maximumTicketCount", "200", Property.Type.INTEGER, "forge.configgui.maximumTicketCount"))); + list.add(new ConfigElement(new Property("maximumChunksPerTicket", "25", Property.Type.INTEGER, "forge.configgui.maximumChunksPerTicket"))); + + return new GuiConfig(this.owningScreen, list, this.owningScreen.modID, + this.configElement.requiresWorldRestart() || this.owningScreen.allRequireWorldRestart, + this.configElement.requiresMcRestart() || this.owningScreen.allRequireMcRestart, this.owningScreen.title, + I18n.format("forge.configgui.ctgy.forgeChunkLoadingAddModConfig")); + } + + @Override + public boolean isChanged() + { + return false; + } + } + + /** + * This custom list entry provides a Mod ID selector. The control is a button that opens a list of values to select from. + * This entry also overrides onGuiClosed() to run code to save the data to a new ConfigCategory when the user is done. + */ + public static class ModIDEntry extends SelectValueEntry + { + public ModIDEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop) + { + super(owningScreen, owningEntryList, prop, getSelectableValues()); + if (this.selectableValues.size() == 0) + this.btnValue.enabled = false; + } + + private static Map getSelectableValues() + { + Map selectableValues = new TreeMap(); + + for (ModContainer mod : Loader.instance().getActiveModList()) + // only add mods to the list that have a non-immutable ModContainer + if (!mod.isImmutable() && mod.getMod() != null) + selectableValues.put(mod.getModId(), mod.getName()); + + return selectableValues; + } + + /** + * By overriding onGuiClosed() for this entry we can perform additional actions when the user is done such as saving + * a new ConfigCategory object to the Configuration object. + */ + @Override + public void onGuiClosed() + { + Object modObject = Loader.instance().getModObjectList().get(Loader.instance().getIndexedModList().get(currentValue)); + int maxTickets = 200; + int maxChunks = 25; + if (modObject != null) + { + this.owningEntryList.saveConfigElements(); + for(IConfigElement ice : this.owningScreen.configElements) + if ("maximumTicketCount".equals(ice.getName())) + maxTickets = Integer.valueOf(ice.get().toString()); + else if ("maximumChunksPerTicket".equals(ice.getName())) + maxChunks = Integer.valueOf(ice.get().toString()); + + ForgeChunkManager.addConfigProperty(modObject, "maximumTicketCount", String.valueOf(maxTickets), Property.Type.INTEGER); + ForgeChunkManager.addConfigProperty(modObject, "maximumChunksPerTicket", String.valueOf(maxChunks), Property.Type.INTEGER); + + if (this.owningScreen.parentScreen instanceof GuiConfig) + { + GuiConfig superParent = (GuiConfig) this.owningScreen.parentScreen; + ConfigCategory modCtgy = ForgeChunkManager.getConfigFor(modObject); + modCtgy.setPropertyOrder(ForgeChunkManager.MOD_PROP_ORDER); + ConfigElement modConfig = new ConfigElement(modCtgy); + + boolean found = false; + for (IConfigElement ice : superParent.configElements) + if (ice.getName().equals(currentValue)) + found = true; + + if (!found) + superParent.configElements.add(modConfig); + + superParent.needsRefresh = true; + superParent.initGui(); + } + } + } + } + } +} diff --git a/src/main/java/net/minecraftforge/common/ForgeChunkManager.java b/src/main/java/net/minecraftforge/common/ForgeChunkManager.java index 8757cf6..311c4cb 100644 --- a/src/main/java/net/minecraftforge/common/ForgeChunkManager.java +++ b/src/main/java/net/minecraftforge/common/ForgeChunkManager.java @@ -1,7 +1,13 @@ +/** + * This software is provided under the terms of the Minecraft Forge Public + * License v1.0. + */ + package net.minecraftforge.common; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -87,8 +93,17 @@ private static Configuration config; private static int playerTicketLength; private static int dormantChunkCacheSize; + + public static final List MOD_PROP_ORDER = new ArrayList(2); private static Set warnedMods = Sets.newHashSet(); + + static + { + MOD_PROP_ORDER.add("maximumTicketCount"); + MOD_PROP_ORDER.add("maximumChunksPerTicket"); + } + /** * All mods requiring chunkloading need to implement this to handle the * re-registration of chunk tickets at world loading time @@ -773,6 +788,8 @@ static void loadConfiguration() { + ticketConstraints.clear(); + chunkConstraints.clear(); for (String mod : config.getCategoryNames()) { if (mod.equals("Forge") || mod.equals("defaults")) @@ -924,51 +941,109 @@ cfgFile.renameTo(dest); FMLLog.log(Level.ERROR, e, "A critical error occured reading the forgeChunkLoading.cfg file, defaults will be used - the invalid file is backed up at forgeChunkLoading.cfg.bak"); } - config.addCustomCategoryComment("defaults", "Default configuration for forge chunk loading control"); - Property maxTicketCount = config.get("defaults", "maximumTicketCount", 200); - maxTicketCount.comment = "The default maximum ticket count for a mod which does not have an override\n" + - "in this file. This is the number of chunk loading requests a mod is allowed to make."; - defaultMaxCount = maxTicketCount.getInt(200); + syncConfigDefaults(); + } + + /** + * Synchronizes the local fields with the values in the Configuration object. + */ + public static void syncConfigDefaults() + { + // By adding a property order list we are defining the order that the properties will appear both in the config file and on the GUIs. + // Property order lists are defined per-ConfigCategory. + List propOrder = new ArrayList(); - Property maxChunks = config.get("defaults", "maximumChunksPerTicket", 25); - maxChunks.comment = "The default maximum number of chunks a mod can force, per ticket, \n" + + config.setCategoryComment("defaults", "Default configuration for forge chunk loading control") + .setCategoryRequiresWorldRestart("defaults", true); + + Property temp = config.get("defaults", "enabled", true); + temp.comment = "Are mod overrides enabled?"; + temp.setLanguageKey("forge.configgui.enableModOverrides"); + overridesEnabled = temp.getBoolean(true); + propOrder.add("enabled"); + + temp = config.get("defaults", "maximumChunksPerTicket", 25); + temp.comment = "The default maximum number of chunks a mod can force, per ticket, \n" + "for a mod without an override. This is the maximum number of chunks a single ticket can force."; - defaultMaxChunks = maxChunks.getInt(25); + temp.setLanguageKey("forge.configgui.maximumChunksPerTicket"); + temp.setMinValue(0); + defaultMaxChunks = temp.getInt(25); + propOrder.add("maximumChunksPerTicket"); + + temp = config.get("defaults", "maximumTicketCount", 200); + temp.comment = "The default maximum ticket count for a mod which does not have an override\n" + + "in this file. This is the number of chunk loading requests a mod is allowed to make."; + temp.setLanguageKey("forge.configgui.maximumTicketCount"); + temp.setMinValue(0); + defaultMaxCount = temp.getInt(200); + propOrder.add("maximumTicketCount"); - Property playerTicketCount = config.get("defaults", "playerTicketCount", 500); - playerTicketCount.comment = "The number of tickets a player can be assigned instead of a mod. This is shared across all mods and it is up to the mods to use it."; - playerTicketLength = playerTicketCount.getInt(500); + temp = config.get("defaults", "playerTicketCount", 500); + temp.comment = "The number of tickets a player can be assigned instead of a mod. This is shared across all mods and it is up to the mods to use it."; + temp.setLanguageKey("forge.configgui.playerTicketCount"); + temp.setMinValue(0); + playerTicketLength = temp.getInt(500); + propOrder.add("playerTicketCount"); - Property dormantChunkCacheSizeProperty = config.get("defaults", "dormantChunkCacheSize", 0); - dormantChunkCacheSizeProperty.comment = "Unloaded chunks can first be kept in a dormant cache for quicker\n" + + temp = config.get("defaults", "dormantChunkCacheSize", 0); + temp.comment = "Unloaded chunks can first be kept in a dormant cache for quicker\n" + "loading times. Specify the size (in chunks) of that cache here"; - dormantChunkCacheSize = dormantChunkCacheSizeProperty.getInt(0); - FMLLog.info("Configured a dormant chunk cache size of %d", dormantChunkCacheSizeProperty.getInt(0)); - - Property modOverridesEnabled = config.get("defaults", "enabled", true); - modOverridesEnabled.comment = "Are mod overrides enabled?"; - overridesEnabled = modOverridesEnabled.getBoolean(true); + temp.setLanguageKey("forge.configgui.dormantChunkCacheSize"); + temp.setMinValue(0); + dormantChunkCacheSize = temp.getInt(0); + propOrder.add("dormantChunkCacheSize"); + FMLLog.info("Configured a dormant chunk cache size of %d", temp.getInt(0)); + + config.setCategoryPropertyOrder("defaults", propOrder); config.addCustomCategoryComment("Forge", "Sample mod specific control section.\n" + "Copy this section and rename the with the modid for the mod you wish to override.\n" + "A value of zero in either entry effectively disables any chunkloading capabilities\n" + "for that mod"); - Property sampleTC = config.get("Forge", "maximumTicketCount", 200); - sampleTC.comment = "Maximum ticket count for the mod. Zero disables chunkloading capabilities."; - sampleTC = config.get("Forge", "maximumChunksPerTicket", 25); - sampleTC.comment = "Maximum chunks per ticket for the mod."; + temp = config.get("Forge", "maximumTicketCount", 200); + temp.comment = "Maximum ticket count for the mod. Zero disables chunkloading capabilities."; + temp = config.get("Forge", "maximumChunksPerTicket", 25); + temp.comment = "Maximum chunks per ticket for the mod."; for (String mod : config.getCategoryNames()) { if (mod.equals("Forge") || mod.equals("defaults")) { continue; } - config.get(mod, "maximumTicketCount", 200); - config.get(mod, "maximumChunksPerTicket", 25); + config.get(mod, "maximumTicketCount", 200).setLanguageKey("forge.configgui.maximumTicketCount").setMinValue(0); + config.get(mod, "maximumChunksPerTicket", 25).setLanguageKey("forge.configgui.maximumChunksPerTicket").setMinValue(0); + } + + if (config.hasChanged()) + { + config.save(); } } - + + public static Configuration getConfig() + { + return config; + } + + public static ConfigCategory getDefaultsCategory() + { + return config.getCategory("defaults"); + } + + public static List getModCategories() + { + List list = new ArrayList(); + for (String mod : config.getCategoryNames()) + { + if (mod.equals("Forge") || mod.equals("defaults")) + { + continue; + } + list.add(config.getCategory(mod)); + } + return list; + } public static ConfigCategory getConfigFor(Object mod) { @@ -987,7 +1062,12 @@ if (container != null) { ConfigCategory cat = config.getCategory(container.getModId()); - cat.put(propertyName, new Property(propertyName, value, type)); + Property prop = new Property(propertyName, value, type).setLanguageKey("forge.configgui." + propertyName); + if (type == Property.Type.INTEGER) + { + prop.setMinValue(0); + } + cat.put(propertyName, prop); } } } diff --git a/src/main/java/net/minecraftforge/common/ForgeModContainer.java b/src/main/java/net/minecraftforge/common/ForgeModContainer.java index a80f2e0..3b3b89a 100644 --- a/src/main/java/net/minecraftforge/common/ForgeModContainer.java +++ b/src/main/java/net/minecraftforge/common/ForgeModContainer.java @@ -1,3 +1,8 @@ +/** + * This software is provided under the terms of the Minecraft Forge Public + * License v1.0. + */ + package net.minecraftforge.common; import static net.minecraftforge.common.ForgeVersion.buildVersion; @@ -7,6 +12,7 @@ import static net.minecraftforge.common.config.Configuration.CATEGORY_GENERAL; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -30,7 +36,9 @@ import cpw.mods.fml.client.FMLFileResourcePack; import cpw.mods.fml.client.FMLFolderResourcePack; +import cpw.mods.fml.client.event.ConfigChangedEvent.OnConfigChangedEvent; import cpw.mods.fml.common.DummyModContainer; +import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.LoadController; import cpw.mods.fml.common.Loader; @@ -42,6 +50,7 @@ import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.event.FMLServerStartingEvent; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; import cpw.mods.fml.common.network.NetworkRegistry; public class ForgeModContainer extends DummyModContainer implements WorldAccessContainer @@ -57,6 +66,8 @@ public static float zombieBabyChance = 0.05f; public static boolean shouldSortRecipies = true; public static boolean disableVersionCheck = false; + + private static Configuration config; public ForgeModContainer() { @@ -75,44 +86,91 @@ meta.screenshots = new String[0]; meta.logoFile = "/forge_logo.png"; - Configuration config = null; + config = null; File cfgFile = new File(Loader.instance().getConfigDir(), "forge.cfg"); - try - { - config = new Configuration(cfgFile); - } - catch (Exception e) - { - System.out.println("Error loading forge.cfg, deleting file and resetting: "); - e.printStackTrace(); + config = new Configuration(cfgFile); + + syncConfig(true); + } - if (cfgFile.exists()) - cfgFile.delete(); + @Override + public String getGuiClassName() + { + return "net.minecraftforge.client.gui.ForgeGuiFactory"; + } + + public static Configuration getConfig() + { + return config; + } - config = new Configuration(cfgFile); - } + /** + * Synchronizes the local fields with the values in the Configuration object. + */ + private static void syncConfig(boolean load) + { + // By adding a property order list we are defining the order that the properties will appear both in the config file and on the GUIs. + // Property order lists are defined per-ConfigCategory. + List propOrder = new ArrayList(); + if (!config.isChild) { - config.load(); - Property enableGlobalCfg = config.get(Configuration.CATEGORY_GENERAL, "enableGlobalConfig", false); + if (load) + { + config.load(); + } + Property enableGlobalCfg = config.get(Configuration.CATEGORY_GENERAL, "enableGlobalConfig", false).setShowInGui(false); if (enableGlobalCfg.getBoolean(false)) { Configuration.enableGlobalConfig(); } } + Property prop; - prop = config.get(Configuration.CATEGORY_GENERAL, "clumpingThreshold", 64); - prop.comment = "Controls the number threshold at which Packet51 is preferred over Packet52, default and minimum 64, maximum 1024"; + + prop = config.get(CATEGORY_GENERAL, "disableVersionCheck", false); + prop.comment = "Set to true to disable Forge's version check mechanics. Forge queries a small json file on our server for version information. For more details see the ForgeVersion class in our github."; + // Language keys are a good idea to implement if you are using config GUIs. This allows you to use a .lang file that will hold the + // "pretty" version of the property name as well as allow others to provide their own localizations. + // This language key is also used to get the tooltip for a property. The tooltip language key is langKey + ".tooltip". + // If no tooltip language key is defined in your .lang file, the tooltip will default to the property comment field. + prop.setLanguageKey("forge.configgui.disableVersionCheck"); + disableVersionCheck = prop.getBoolean(disableVersionCheck); + propOrder.add(prop.getName()); + + prop = config.get(Configuration.CATEGORY_GENERAL, "clumpingThreshold", 64, + "Controls the number threshold at which Packet51 is preferred over Packet52, default and minimum 64, maximum 1024", 64, 1024); + prop.setLanguageKey("forge.configgui.clumpingThreshold").setRequiresWorldRestart(true); clumpingThreshold = prop.getInt(64); if (clumpingThreshold > 1024 || clumpingThreshold < 64) { clumpingThreshold = 64; prop.set(64); } + propOrder.add(prop.getName()); + + prop = config.get(CATEGORY_GENERAL, "sortRecipies", true); + prop.comment = "Set to true to enable the post initialization sorting of crafting recipes using Forge's sorter. May cause desyncing on conflicting recipies. MUST RESTART MINECRAFT IF CHANGED FROM THE CONFIG GUI."; + prop.setLanguageKey("forge.configgui.sortRecipies").setRequiresMcRestart(true); + shouldSortRecipies = prop.getBoolean(shouldSortRecipies); + propOrder.add(prop.getName()); + + prop = config.get(Configuration.CATEGORY_GENERAL, "forceDuplicateFluidBlockCrash", true); + prop.comment = "Set this to true to force a crash if more than one block attempts to link back to the same Fluid. Enabled by default."; + prop.setLanguageKey("forge.configgui.forceDuplicateFluidBlockCrash").setRequiresMcRestart(true); + forceDuplicateFluidBlockCrash = prop.getBoolean(true); + propOrder.add(prop.getName()); + + if (!forceDuplicateFluidBlockCrash) + { + FMLLog.warning("Disabling forced crashes on duplicate Fluid Blocks - USE AT YOUR OWN RISK"); + } prop = config.get(Configuration.CATEGORY_GENERAL, "removeErroringEntities", false); - prop.comment = "Set this to just remove any TileEntity that throws a error in there update method instead of closing the server and reporting a crash log. BE WARNED THIS COULD SCREW UP EVERYTHING USE SPARINGLY WE ARE NOT RESPONSIBLE FOR DAMAGES."; + prop.comment = "Set this to true to remove any Entity that throws an error in its update method instead of closing the server and reporting a crash log. BE WARNED THIS COULD SCREW UP EVERYTHING USE SPARINGLY WE ARE NOT RESPONSIBLE FOR DAMAGES."; + prop.setLanguageKey("forge.configgui.removeErroringEntities").setRequiresWorldRestart(true); removeErroringEntities = prop.getBoolean(false); + propOrder.add(prop.getName()); if (removeErroringEntities) { @@ -120,8 +178,10 @@ } prop = config.get(Configuration.CATEGORY_GENERAL, "removeErroringTileEntities", false); - prop.comment = "Set this to just remove any TileEntity that throws a error in there update method instead of closing the server and reporting a crash log. BE WARNED THIS COULD SCREW UP EVERYTHING USE SPARINGLY WE ARE NOT RESPONSIBLE FOR DAMAGES."; + prop.comment = "Set this to true to remove any TileEntity that throws an error in its update method instead of closing the server and reporting a crash log. BE WARNED THIS COULD SCREW UP EVERYTHING USE SPARINGLY WE ARE NOT RESPONSIBLE FOR DAMAGES."; + prop.setLanguageKey("forge.configgui.removeErroringTileEntities").setRequiresWorldRestart(true); removeErroringTileEntities = prop.getBoolean(false); + propOrder.add(prop.getName()); if (removeErroringTileEntities) { @@ -133,43 +193,57 @@ //disableStitchedFileSaving = prop.getBoolean(true); prop = config.get(Configuration.CATEGORY_GENERAL, "fullBoundingBoxLadders", false); - prop.comment = "Set this to check the entire entity's collision bounding box for ladders instead of just the block they are in. Causes noticable differences in mechanics so default is vanilla behavior. Default: false"; + prop.comment = "Set this to true to check the entire entity's collision bounding box for ladders instead of just the block they are in. Causes noticable differences in mechanics so default is vanilla behavior. Default: false"; + prop.setLanguageKey("forge.configgui.fullBoundingBoxLadders").setRequiresWorldRestart(true); fullBoundingBoxLadders = prop.getBoolean(false); - - prop = config.get(Configuration.CATEGORY_GENERAL, "forceDuplicateFluidBlockCrash", true); - prop.comment = "Set this to force a crash if more than one block attempts to link back to the same Fluid. Enabled by default."; - forceDuplicateFluidBlockCrash = prop.getBoolean(true); - - if (!forceDuplicateFluidBlockCrash) - { - FMLLog.warning("Disabling forced crashes on duplicate Fluid Blocks - USE AT YOUR OWN RISK"); - } + propOrder.add(prop.getName()); prop = config.get(Configuration.CATEGORY_GENERAL, "biomeSkyBlendRange", new int[] { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34 }); prop.comment = "Control the range of sky blending for colored skies in biomes."; + prop.setLanguageKey("forge.configgui.biomeSkyBlendRange"); blendRanges = prop.getIntList(); + propOrder.add(prop.getName()); - prop = config.get(Configuration.CATEGORY_GENERAL, "zombieBaseSummonChance", 0.1); - prop.comment = "Base zombie summoning spawn chance. Allows changing the bonus zombie summoning mechanic."; + prop = config.get(Configuration.CATEGORY_GENERAL, "zombieBaseSummonChance", 0.1, + "Base zombie summoning spawn chance. Allows changing the bonus zombie summoning mechanic.", 0.0D, 1.0D); + prop.setLanguageKey("forge.configgui.zombieBaseSummonChance").setRequiresWorldRestart(true); zombieSummonBaseChance = prop.getDouble(0.1); + propOrder.add(prop.getName()); - prop = config.get(Configuration.CATEGORY_GENERAL, "zombieBabyChance", 0.05); - prop.comment = "Chance that a zombie (or subclass) is a baby. Allows changing the zombie spawning mechanic."; + prop = config.get(Configuration.CATEGORY_GENERAL, "zombieBabyChance", 0.05, + "Chance that a zombie (or subclass) is a baby. Allows changing the zombie spawning mechanic.", 0.0D, 1.0D); + prop.setLanguageKey("forge.configgui.zombieBabyChance").setRequiresWorldRestart(true); zombieBabyChance = (float) prop.getDouble(0.05); - - prop = config.get(CATEGORY_GENERAL, "sortRecipies", shouldSortRecipies); - prop.comment = "Set to true to enable the post initlization sorting of crafting recipes using Froge's sorter. May cause desyncing on conflicting recipies. ToDo: Set to true by default in 1.7"; - shouldSortRecipies = prop.getBoolean(shouldSortRecipies); - - prop = config.get(CATEGORY_GENERAL, "disableVersionCheck", disableVersionCheck); - prop.comment = "Set to true to disable Forge's version check mechanics, Forge queries a small json file on our server for version information. For more details see the ForgeVersion class in our github."; - disableVersionCheck = prop.getBoolean(disableVersionCheck); + propOrder.add(prop.getName()); + + config.setCategoryPropertyOrder(CATEGORY_GENERAL, propOrder); if (config.hasChanged()) { config.save(); } } + + /** + * By subscribing to the OnConfigChangedEvent we are able to execute code when our config screens are closed. + * This implementation uses the optional configID string to handle multiple Configurations using one event handler. + */ + @SubscribeEvent + public void onConfigChanged(OnConfigChangedEvent event) + { + if (getMetadata().modId.equals(event.modID) && !event.isWorldRunning) + { + if (Configuration.CATEGORY_GENERAL.equals(event.configID)) + { + syncConfig(false); + } + else if ("chunkLoader".equals(event.configID)) + { + ForgeChunkManager.syncConfigDefaults(); + ForgeChunkManager.loadConfiguration(); + } + } + } @Override public boolean registerBus(EventBus bus, LoadController controller) @@ -190,6 +264,7 @@ { MinecraftForge.EVENT_BUS.register(MinecraftForge.INTERNAL_HANDLER); ForgeChunkManager.captureConfig(evt.getModConfigurationDirectory()); + FMLCommonHandler.instance().bus().register(this); } @Subscribe @@ -200,7 +275,7 @@ } @Subscribe - public void onAvalible(FMLLoadCompleteEvent evt) + public void onAvailable(FMLLoadCompleteEvent evt) { if (shouldSortRecipies) { diff --git a/src/main/java/net/minecraftforge/common/config/ConfigCategory.java b/src/main/java/net/minecraftforge/common/config/ConfigCategory.java index 994f11b..84e6795 100644 --- a/src/main/java/net/minecraftforge/common/config/ConfigCategory.java +++ b/src/main/java/net/minecraftforge/common/config/ConfigCategory.java @@ -1,5 +1,11 @@ +/** + * This software is provided under the terms of the Minecraft Forge Public + * License v1.0. + */ + package net.minecraftforge.common.config; +import static net.minecraftforge.common.config.Configuration.COMMENT_SEPARATOR; import static net.minecraftforge.common.config.Configuration.NEW_LINE; import static net.minecraftforge.common.config.Configuration.allowedProperties; @@ -7,22 +13,34 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.base.Splitter; + +import cpw.mods.fml.client.config.GuiConfigEntries.IConfigEntry; public class ConfigCategory implements Map { private String name; private String comment; + private String languagekey; private ArrayList children = new ArrayList(); private Map properties = new TreeMap(); + private int propNumber = 0; public final ConfigCategory parent; private boolean changed = false; + private boolean requiresWorldRestart = false; + private boolean showInGui = true; + private boolean requiresMcRestart = false; + private Class customEntryClass = null; + private List propertyOrder = null; public ConfigCategory(String name) { @@ -45,12 +63,17 @@ if (obj instanceof ConfigCategory) { ConfigCategory cat = (ConfigCategory)obj; - return name.equals(cat.name) && children.equals(cat.children); + return name.equals(cat.name) && children.equals(cat.children); } - + return false; } + public String getName() + { + return name; + } + public String getQualifiedName() { return getQualifiedName(name, parent); @@ -76,11 +99,133 @@ return ImmutableMap.copyOf(properties); } + public List getOrderedValues() + { + if (this.propertyOrder != null) + { + ArrayList set = new ArrayList(); + for (String key : this.propertyOrder) + if (properties.containsKey(key)) + set.add(properties.get(key)); + + return ImmutableList.copyOf(set); + } + else + return ImmutableList.copyOf(properties.values()); + } + + public ConfigCategory setConfigEntryClass(Class clazz) + { + this.customEntryClass = clazz; + return this; + } + + public Class getConfigEntryClass() + { + return this.customEntryClass; + } + + public ConfigCategory setLanguageKey(String languagekey) + { + this.languagekey = languagekey; + return this; + } + + public String getLanguagekey() + { + if (this.languagekey != null) + return this.languagekey; + else + return getQualifiedName(); + } + public void setComment(String comment) { this.comment = comment; } + public String getComment() + { + return this.comment; + } + + /** + * Sets the flag for whether or not this category can be edited while a world is running. Care should be taken to ensure + * that only properties that are truly dynamic can be changed from the in-game options menu. Only set this flag to + * true if all child properties/categories are unable to be modified while a world is running. + */ + public ConfigCategory setRequiresWorldRestart(boolean requiresWorldRestart) + { + this.requiresWorldRestart = requiresWorldRestart; + return this; + } + + /** + * Returns whether or not this category is able to be edited while a world is running using the in-game Mod Options screen + * as well as the Mods list screen, or only from the Mods list screen. + */ + public boolean requiresWorldRestart() + { + return this.requiresWorldRestart; + } + + /** + * Sets whether or not this ConfigCategory should be allowed to show on config GUIs. + * Defaults to true. + */ + public ConfigCategory setShowInGui(boolean showInGui) + { + this.showInGui = showInGui; + return this; + } + + /** + * Gets whether or not this ConfigCategory should be allowed to show on config GUIs. + * Defaults to true unless set to false. + */ + public boolean showInGui() + { + return showInGui; + } + + /** + * Sets whether or not this ConfigCategory requires Minecraft to be restarted when changed. + * Defaults to false. Only set this flag to true if ALL child properties/categories require + * Minecraft to be restarted when changed. Setting this flag will also prevent modification + * of the child properties/categories while a world is running. + */ + public ConfigCategory setRequiresMcRestart(boolean requiresMcRestart) + { + this.requiresMcRestart = this.requiresWorldRestart = requiresMcRestart; + return this; + } + + /** + * Gets whether or not this ConfigCategory requires Minecraft to be restarted when changed. + * Defaults to false unless set to true. + */ + public boolean requiresMcRestart() + { + return this.requiresMcRestart; + } + + public ConfigCategory setPropertyOrder(List propertyOrder) + { + this.propertyOrder = propertyOrder; + for (String s : properties.keySet()) + if (!propertyOrder.contains(s)) + propertyOrder.add(s); + return this; + } + + public List getPropertyOrder() + { + if (this.propertyOrder != null) + return ImmutableList.copyOf(this.propertyOrder); + else + return ImmutableList.copyOf(properties.keySet()); + } + public boolean containsKey(String key) { return properties.containsKey(key); @@ -111,21 +256,20 @@ String pad1 = getIndent(indent + 1); String pad2 = getIndent(indent + 2); - write(out, pad0, "####################"); - write(out, pad0, "# ", name); - - if (comment != null) + if (comment != null && !comment.isEmpty()) { - write(out, pad0, "#==================="); + write(out, pad0, COMMENT_SEPARATOR); + write(out, pad0, "# ", name); + write(out, pad0, "#--------------------------------------------------------------------------------------------------------#"); Splitter splitter = Splitter.onPattern("\r?\n"); for (String line : splitter.split(comment)) { write(out, pad0, "# ", line); } - } - write(out, pad0, "####################", NEW_LINE); + write(out, pad0, COMMENT_SEPARATOR, NEW_LINE); + } if (!allowedProperties.matchesAllOf(name)) { @@ -134,13 +278,13 @@ write(out, pad0, name, " {"); - Property[] props = properties.values().toArray(new Property[properties.size()]); + Property[] props = getOrderedValues().toArray(new Property[] {}); for (int x = 0; x < props.length; x++) { Property prop = props[x]; - if (prop.comment != null) + if (prop.comment != null && !prop.comment.isEmpty()) { if (x != 0) { @@ -164,7 +308,7 @@ if (prop.isList()) { char type = prop.getType().getID(); - + write(out, pad1, String.valueOf(type), ":", propName, " <"); for (String line : prop.getStringList()) @@ -185,6 +329,9 @@ } } + if (children.size() > 0) + out.newLine(); + for (ConfigCategory child : children) { child.write(out, indent + 1); @@ -232,6 +379,8 @@ @Override public Property put(String key, Property value) { changed = true; + if (this.propertyOrder != null && !this.propertyOrder.contains(key)) + this.propertyOrder.add(key); return properties.put(key, value); } @Override public Property remove(Object key) @@ -242,6 +391,10 @@ @Override public void putAll(Map m) { changed = true; + if (this.propertyOrder != null) + for (String key : m.keySet()) + if (!this.propertyOrder.contains(key)) + this.propertyOrder.add(key); properties.putAll(m); } @Override public void clear() @@ -259,7 +412,7 @@ } public Set getChildren(){ return ImmutableSet.copyOf(children); } - + public void removeChild(ConfigCategory child) { if (children.contains(child)) diff --git a/src/main/java/net/minecraftforge/common/config/ConfigElement.java b/src/main/java/net/minecraftforge/common/config/ConfigElement.java new file mode 100644 index 0000000..aa72e65 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/config/ConfigElement.java @@ -0,0 +1,359 @@ +/** + * This software is provided under the terms of the Minecraft Forge Public + * License v1.0. + */ + +package net.minecraftforge.common.config; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Pattern; + +import cpw.mods.fml.client.config.ConfigGuiType; +import cpw.mods.fml.client.config.GuiConfigEntries.IConfigEntry; +import cpw.mods.fml.client.config.GuiEditArrayEntries.IArrayEntry; +import cpw.mods.fml.client.config.IConfigElement; + +/** + * This class bridges the gap between the FML config GUI classes and the Forge Configuration classes. + */ +public class ConfigElement implements IConfigElement +{ + private Property prop; + private Property.Type type; + private boolean isProperty; + private ConfigCategory ctgy; + private boolean categoriesFirst = true; + + public ConfigElement(ConfigCategory ctgy) + { + this.ctgy = ctgy; + isProperty = false; + } + + public ConfigElement(Property prop) + { + this.prop = prop; + this.type = prop.getType(); + this.isProperty = true; + } + + public ConfigElement listCategoriesFirst(boolean categoriesFirst) + { + this.categoriesFirst = categoriesFirst; + return this; + } + + @Override + public List getChildElements() + { + if (!isProperty) + { + List elements = new ArrayList(); + Iterator ccI = ctgy.getChildren().iterator(); + Iterator pI = ctgy.getOrderedValues().iterator(); + int index = 0; + + if (categoriesFirst) + while (ccI.hasNext()) + { + ConfigElement temp = new ConfigElement(ccI.next()); + if (temp.showInGui()) // don't bother adding elements that shouldn't show + elements.add(temp); + } + + while (pI.hasNext()) + { + ConfigElement temp = getTypedElement(pI.next()); + if (temp.showInGui()) + elements.add(temp); + } + + if (!categoriesFirst) + while (ccI.hasNext()) + { + ConfigElement temp = new ConfigElement(ccI.next()); + if (temp.showInGui()) + elements.add(temp); + } + + return elements; + } + return null; + } + + public static ConfigElement getTypedElement(Property prop) + { + switch (getType(prop)) + { + case BOOLEAN: + return new ConfigElement(prop); + case DOUBLE: + return new ConfigElement(prop); + case INTEGER: + return new ConfigElement(prop); + default: + return new ConfigElement(prop); + } + } + + @Override + public String getName() + { + return isProperty ? prop.getName() : ctgy.getName(); + } + + @Override + public boolean isProperty() + { + return isProperty; + } + + @Override + public Class getConfigEntryClass() + { + return isProperty ? prop.getConfigEntryClass() : ctgy.getConfigEntryClass(); + } + + @Override + public Class getArrayEntryClass() + { + return isProperty ? prop.getArrayEntryClass() : null; + } + + @Override + public String getQualifiedName() + { + return isProperty ? prop.getName() : ctgy.getQualifiedName(); + } + + @Override + public ConfigGuiType getType() + { + return isProperty ? getType(this.prop) : ConfigGuiType.CONFIG_CATEGORY; + } + + public static ConfigGuiType getType(Property prop) + { + return prop.getType() == Property.Type.BOOLEAN ? ConfigGuiType.BOOLEAN : prop.getType() == Property.Type.DOUBLE ? ConfigGuiType.DOUBLE : + prop.getType() == Property.Type.INTEGER ? ConfigGuiType.INTEGER : prop.getType() == Property.Type.COLOR ? ConfigGuiType.COLOR : + prop.getType() == Property.Type.MOD_ID ? ConfigGuiType.MOD_ID : ConfigGuiType.STRING; + } + + @Override + public boolean isList() + { + return isProperty && prop.isList(); + } + + @Override + public boolean isListLengthFixed() + { + return isProperty && prop.isListLengthFixed(); + } + + @Override + public int getMaxListLength() + { + return isProperty ? prop.getMaxListLength() : -1; + } + + @Override + public String getComment() + { + return isProperty ? prop.comment : ctgy.getComment(); + } + + @Override + public boolean isDefault() + { + return !isProperty || prop.isDefault(); + } + + @Override + public void setToDefault() + { + if (isProperty) + prop.setToDefault(); + } + + @Override + public boolean requiresWorldRestart() + { + return isProperty ? prop.requiresWorldRestart() : ctgy.requiresWorldRestart(); + } + + @Override + public boolean showInGui() + { + return isProperty ? prop.showInGui() : ctgy.showInGui(); + } + + @Override + public boolean requiresMcRestart() + { + return isProperty ? prop.requiresMcRestart() : ctgy.requiresMcRestart(); + } + + @Override + public String[] getValidValues() + { + return isProperty ? prop.getValidValues() : null; + } + + @Override + public String getLanguageKey() + { + return isProperty ? prop.getLanguageKey() : ctgy.getLanguagekey(); + } + + @Override + public Object getDefault() + { + return isProperty ? (T) prop.getDefault() : null; + } + + @Override + public Object[] getDefaults() + { + if (isProperty) + { + String[] aVal = prop.getDefaults(); + if (type == Property.Type.BOOLEAN) + { + Boolean[] ba = new Boolean[aVal.length]; + for(int i = 0; i < aVal.length; i++) + ba[i] = Boolean.valueOf(aVal[i]); + return ba; + } + else if (type == Property.Type.DOUBLE) + { + Double[] da = new Double[aVal.length]; + for(int i = 0; i < aVal.length; i++) + da[i] = Double.valueOf(aVal[i].toString()); + return da; + } + else if (type == Property.Type.INTEGER) + { + Integer[] ia = new Integer[aVal.length]; + for(int i = 0; i < aVal.length; i++) + ia[i] = Integer.valueOf(aVal[i].toString()); + return ia; + } + else + return aVal; + } + return null; + } + + @Override + public Pattern getValidationPattern() + { + return isProperty ? prop.getValidationPattern() : null; + } + + @Override + public Object get() + { + return isProperty ? (T) prop.getString() : null; + } + + @Override + public Object[] getList() + { + if (isProperty) + { + String[] aVal = prop.getStringList(); + if (type == Property.Type.BOOLEAN) + { + Boolean[] ba = new Boolean[aVal.length]; + for(int i = 0; i < aVal.length; i++) + ba[i] = Boolean.valueOf(aVal[i]); + return ba; + } + else if (type == Property.Type.DOUBLE) + { + Double[] da = new Double[aVal.length]; + for(int i = 0; i < aVal.length; i++) + da[i] = Double.valueOf(aVal[i].toString()); + return da; + } + else if (type == Property.Type.INTEGER) + { + Integer[] ia = new Integer[aVal.length]; + for(int i = 0; i < aVal.length; i++) + ia[i] = Integer.valueOf(aVal[i].toString()); + return ia; + } + else + return aVal; + } + return null; + } + + @Override + public void set(T value) + { + if (isProperty) + { + if (type == Property.Type.BOOLEAN) + prop.set(Boolean.parseBoolean(value.toString())); + else if (type == Property.Type.DOUBLE) + prop.set(Double.parseDouble(value.toString())); + else if (type == Property.Type.INTEGER) + prop.set(Integer.parseInt(value.toString())); + else + prop.set(value.toString()); + } + } + + @Override + public void set(T[] aVal) + { + if (isProperty) + { + if (type == Property.Type.BOOLEAN) + { + boolean[] ba = new boolean[aVal.length]; + for(int i = 0; i < aVal.length; i++) + ba[i] = Boolean.valueOf(aVal[i].toString()); + prop.set(ba); + } + else if (type == Property.Type.DOUBLE) + { + double[] da = new double[aVal.length]; + for(int i = 0; i < aVal.length; i++) + da[i] = Double.valueOf(aVal[i].toString()); + prop.set(da); + } + else if (type == Property.Type.INTEGER) + { + int[] ia = new int[aVal.length]; + for(int i = 0; i < aVal.length; i++) + ia[i] = Integer.valueOf(aVal[i].toString()); + prop.set(ia); + } + else + { + String[] is = new String[aVal.length]; + for(int i = 0; i < aVal.length; i++) + is[i] = aVal[i].toString(); + prop.set(is); + } + } + } + + @Override + public T getMinValue() + { + return isProperty ? (T) prop.getMinValue() : null; + } + + @Override + public T getMaxValue() + { + return isProperty ? (T) prop.getMaxValue() : null; + } +} diff --git a/src/main/java/net/minecraftforge/common/config/Configuration.java b/src/main/java/net/minecraftforge/common/config/Configuration.java index 94342df..03f46f6 100644 --- a/src/main/java/net/minecraftforge/common/config/Configuration.java +++ b/src/main/java/net/minecraftforge/common/config/Configuration.java @@ -21,9 +21,13 @@ import java.io.OutputStreamWriter; import java.io.PushbackInputStream; import java.io.Reader; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; +import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.regex.Matcher; @@ -32,6 +36,8 @@ import com.google.common.base.CharMatcher; import com.google.common.collect.ImmutableSet; +import cpw.mods.fml.client.config.GuiConfigEntries.IConfigEntry; +import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.Loader; import cpw.mods.fml.relauncher.FMLInjectionData; @@ -46,6 +52,8 @@ public static final String DEFAULT_ENCODING = "UTF-8"; public static final String CATEGORY_SPLITTER = "."; public static final String NEW_LINE; + public static final String COMMENT_SEPARATOR = "##########################################################################################################"; + private static final String CONFIG_VERSION_MARKER = "~CONFIG_VERSION"; private static final Pattern CONFIG_START = Pattern.compile("START: \"([^\\\"]+)\""); private static final Pattern CONFIG_END = Pattern.compile("END: \"([^\\\"]+)\""); public static final CharMatcher allowedProperties = CharMatcher.JAVA_LETTER_OR_DIGIT.or(CharMatcher.anyOf(ALLOWED_CHARS)); @@ -61,6 +69,8 @@ private String fileName = null; public boolean isChild = false; private boolean changed = false; + private String definedConfigVersion = null; + private String loadedConfigVersion = null; static { @@ -74,6 +84,14 @@ */ public Configuration(File file) { + this(file, null); + } + + /** + * Create a configuration file for the file given in parameter with the provided config version number. + */ + public Configuration(File file, String configVersion) + { this.file = file; String basePath = ((File)(FMLInjectionData.data()[6])).getAbsolutePath().replace(File.separatorChar, '/').replace("/.", ""); String path = file.getAbsolutePath().replace(File.separatorChar, '/').replace("/./", "/").replace(basePath, ""); @@ -85,149 +103,587 @@ else { fileName = path; - load(); + try + { + load(); + } + catch (Throwable e) + { + File fileBak = new File(file.getAbsolutePath() + "_" + + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".errored"); + FMLLog.severe("An exception occurred while loading config file %s. This file will be renamed to %s " + + "and a new config file will be generated.", file.getName(), fileBak.getName()); + e.printStackTrace(); + + file.renameTo(fileBak); + load(); + } } } + public Configuration(File file, String configVersion, boolean caseSensitiveCustomCategories) + { + this(file, configVersion); + this.caseSensitiveCustomCategories = caseSensitiveCustomCategories; + } + public Configuration(File file, boolean caseSensitiveCustomCategories) { - this(file); - this.caseSensitiveCustomCategories = caseSensitiveCustomCategories; + this(file, null, caseSensitiveCustomCategories); } - - public Property get(String category, String key, int defaultValue) + + @Override + public String toString() { - return get(category, key, defaultValue, null); + return file.getAbsolutePath(); } - public Property get(String category, String key, int defaultValue, String comment) + public String getDefinedConfigVersion() { - Property prop = get(category, key, Integer.toString(defaultValue), comment, INTEGER); - if (!prop.isIntValue()) - { - prop.set(defaultValue); - } - return prop; + return this.definedConfigVersion; } + public String getLoadedConfigVersion() + { + return this.loadedConfigVersion; + } + + /****************************************************************************************************************** + * + * BOOLEAN gets + * + *****************************************************************************************************************/ + + /** + * Gets a boolean Property object without a comment using the default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValue the default value + * @return a boolean Property object without a comment + */ public Property get(String category, String key, boolean defaultValue) { - return get(category, key, defaultValue, null); + return get(category, key, defaultValue, (String) null); } + /** + * Gets a boolean Property object with a comment using the default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValue the default value + * @param comment a String comment + * @return a boolean Property object without a comment + */ public Property get(String category, String key, boolean defaultValue, String comment) { Property prop = get(category, key, Boolean.toString(defaultValue), comment, BOOLEAN); + prop.setDefaultValue(Boolean.toString(defaultValue)); + if (!prop.isBooleanValue()) { - prop.set(defaultValue); + prop.setValue(defaultValue); + } + return prop; + + } + + /** + * Gets a boolean array Property without a comment using the default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValues an array containing the default values + * @return a boolean array Property without a comment using these defaults: isListLengthFixed = false, maxListLength = -1 + */ + public Property get(String category, String key, boolean[] defaultValues) + { + return get(category, key, defaultValues, (String) null); + } + + /** + * Gets a boolean array Property with a comment using the default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValues an array containing the default values + * @param comment a String comment + * @return a boolean array Property with a comment using these defaults: isListLengthFixed = false, maxListLength = -1 + */ + public Property get(String category, String key, boolean[] defaultValues, String comment) + { + return get(category, key, defaultValues, comment, false, -1); + } + + /** + * Gets a boolean array Property with all settings defined. + * + * @param category the config category + * @param key the Property key value + * @param defaultValues an array containing the default values + * @param comment a String comment + * @param isListLengthFixed boolean for whether this array is required to be a specific length (defined by the default value array + * length or maxListLength) + * @param maxListLength the maximum length of this array, use -1 for no max length + * @return a boolean array Property with all settings defined + */ + public Property get(String category, String key, boolean[] defaultValues, String comment, + boolean isListLengthFixed, int maxListLength) + { + String[] values = new String[defaultValues.length]; + for (int i = 0; i < defaultValues.length; i++) + { + values[i] = Boolean.toString(defaultValues[i]); + } + + Property prop = get(category, key, values, comment, BOOLEAN); + prop.setDefaultValues(values); + prop.setIsListLengthFixed(isListLengthFixed); + prop.setMaxListLength(maxListLength); + + if (!prop.isBooleanList()) + { + prop.setValues(values); + } + + return prop; + } + + /* **************************************************************************************************************** + * + * INTEGER gets + * + *****************************************************************************************************************/ + + /** + * Gets an integer Property object without a comment using default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValue the default value + * @return an integer Property object with default bounds of Integer.MIN_VALUE and Integer.MAX_VALUE + */ + public Property get(String category, String key, int defaultValue) + { + return get(category, key, defaultValue, (String) null, Integer.MIN_VALUE, Integer.MAX_VALUE); + } + + /** + * Gets an integer Property object with a comment using default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValue the default value + * @param comment a String comment + * @return an integer Property object with default bounds of Integer.MIN_VALUE and Integer.MAX_VALUE + */ + public Property get(String category, String key, int defaultValue, String comment) + { + return get(category, key, defaultValue, comment, Integer.MIN_VALUE, Integer.MAX_VALUE); + } + + /** + * Gets an integer Property object with the defined comment, minimum and maximum bounds. + * + * @param category the config category + * @param key the Property key value + * @param defaultValue the default value + * @param comment a String comment + * @param minValue minimum boundary + * @param maxValue maximum boundary + * @return an integer Property object with the defined comment, minimum and maximum bounds + */ + public Property get(String category, String key, int defaultValue, String comment, int minValue, int maxValue) + { + Property prop = get(category, key, Integer.toString(defaultValue), comment, INTEGER); + prop.setDefaultValue(Integer.toString(defaultValue)); + prop.setMinValue(minValue); + prop.setMaxValue(maxValue); + + if (!prop.isIntValue()) + { + prop.setValue(defaultValue); } return prop; } + /** + * Gets an integer array Property object without a comment using default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValues an array containing the default values + * @return an integer array Property object with default bounds of Integer.MIN_VALUE and Integer.MAX_VALUE, isListLengthFixed = false, + * maxListLength = -1 + */ + public Property get(String category, String key, int[] defaultValues) + { + return get(category, key, defaultValues, (String) null); + } + + /** + * Gets an integer array Property object with a comment using default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValues an array containing the default values + * @param comment a String comment + * @return an integer array Property object with default bounds of Integer.MIN_VALUE and Integer.MAX_VALUE, isListLengthFixed = false, + * maxListLength = -1 + */ + public Property get(String category, String key, int[] defaultValues, String comment) + { + return get(category, key, defaultValues, comment, Integer.MIN_VALUE, Integer.MAX_VALUE, false, -1); + } + + /** + * Gets an integer array Property object with the defined comment, minimum and maximum bounds. + * + * @param category the config category + * @param key the Property key value + * @param defaultValues an array containing the default values + * @param comment a String comment + * @param minValue minimum boundary + * @param maxValue maximum boundary + * @return an integer array Property object with the defined comment, minimum and maximum bounds, isListLengthFixed + * = false, maxListLength = -1 + */ + public Property get(String category, String key, int[] defaultValues, String comment, int minValue, int maxValue) + { + return get(category, key, defaultValues, comment, minValue, maxValue, false, -1); + } + + /** + * Gets an integer array Property object with all settings defined. + * + * @param category the config category + * @param key the Property key value + * @param defaultValues an array containing the default values + * @param comment a String comment + * @param minValue minimum boundary + * @param maxValue maximum boundary + * @param isListLengthFixed boolean for whether this array is required to be a specific length (defined by the default value array + * length or maxListLength) + * @param maxListLength the maximum length of this array, use -1 for no max length + * @return an integer array Property object with all settings defined + */ + public Property get(String category, String key, int[] defaultValues, String comment, int minValue, int maxValue, + boolean isListLengthFixed, int maxListLength) + { + String[] values = new String[defaultValues.length]; + for (int i = 0; i < defaultValues.length; i++) + { + values[i] = Integer.toString(defaultValues[i]); + } + + Property prop = get(category, key, values, comment, INTEGER); + prop.setDefaultValues(values); + prop.setMinValue(minValue); + prop.setMaxValue(maxValue); + prop.setIsListLengthFixed(isListLengthFixed); + prop.setMaxListLength(maxListLength); + + if (!prop.isIntList()) + { + prop.setValues(values); + } + + return prop; + } + + /* **************************************************************************************************************** + * + * DOUBLE gets + * + *****************************************************************************************************************/ + + /** + * Gets a double Property object without a comment using default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValue the default value + * @return a double Property object with default bounds of Double.MIN_VALUE and Double.MAX_VALUE + */ public Property get(String category, String key, double defaultValue) { - return get(category, key, defaultValue, null); + return get(category, key, defaultValue, (String) null); } + /** + * Gets a double Property object with a comment using default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValue the default value + * @param comment a String comment + * @return a double Property object with default bounds of Double.MIN_VALUE and Double.MAX_VALUE + */ public Property get(String category, String key, double defaultValue, String comment) { + return get(category, key, defaultValue, comment, -Double.MAX_VALUE, Double.MAX_VALUE); + } + + /** + * Gets a double Property object with the defined comment, minimum and maximum bounds + * + * @param category the config category + * @param key the Property key value + * @param defaultValue the default value + * @param comment a String comment + * @param minValue minimum boundary + * @param maxValue maximum boundary + * @return a double Property object with the defined comment, minimum and maximum bounds + */ + public Property get(String category, String key, double defaultValue, String comment, double minValue, double maxValue) + { Property prop = get(category, key, Double.toString(defaultValue), comment, DOUBLE); + prop.setDefaultValue(Double.toString(defaultValue)); + prop.setMinValue(minValue); + prop.setMaxValue(maxValue); + if (!prop.isDoubleValue()) { - prop.set(defaultValue); + prop.setValue(defaultValue); } return prop; } - public Property get(String category, String key, String defaultValue) + /** + * Gets a double array Property object without a comment using default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValues an array containing the default values + * @return a double array Property object with default bounds of Double.MIN_VALUE and Double.MAX_VALUE, isListLengthFixed = false, + * maxListLength = -1 + */ + public Property get(String category, String key, double[] defaultValues) { - return get(category, key, defaultValue, null); + return get(category, key, defaultValues, null); } + /** + * Gets a double array Property object without a comment using default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValues an array containing the default values + * @param comment a String comment + * @return a double array Property object with default bounds of Double.MIN_VALUE and Double.MAX_VALUE, isListLengthFixed = false, + * maxListLength = -1 + */ + public Property get(String category, String key, double[] defaultValues, String comment) + { + return get(category, key, defaultValues, comment, -Double.MAX_VALUE, Double.MAX_VALUE, false, -1); + } + + /** + * Gets a double array Property object with the defined comment, minimum and maximum bounds. + * + * @param category the config category + * @param key the Property key value + * @param defaultValues an array containing the default values + * @param comment a String comment + * @param minValue minimum boundary + * @param maxValue maximum boundary + * @return a double array Property object with the defined comment, minimum and maximum bounds, isListLengthFixed = + * false, maxListLength = -1 + */ + public Property get(String category, String key, double[] defaultValues, String comment, double minValue, double maxValue) + { + return get(category, key, defaultValues, comment, minValue, maxValue, false, -1); + } + + /** + * Gets a double array Property object with all settings defined. + * + * @param category the config category + * @param key the Property key value + * @param defaultValues an array containing the default values + * @param comment a String comment + * @param minValue minimum boundary + * @param maxValue maximum boundary + * @param isListLengthFixed boolean for whether this array is required to be a specific length (defined by the default value array + * length or maxListLength) + * @param maxListLength the maximum length of this array, use -1 for no max length + * @return a double array Property object with all settings defined + */ + public Property get(String category, String key, double[] defaultValues, String comment, double minValue, double maxValue, + boolean isListLengthFixed, int maxListLength) + { + String[] values = new String[defaultValues.length]; + for (int i = 0; i < defaultValues.length; i++) + { + values[i] = Double.toString(defaultValues[i]); + } + + + Property prop = get(category, key, values, comment, DOUBLE); + prop.setDefaultValues(values); + prop.setMinValue(minValue); + prop.setMaxValue(maxValue); + prop.setIsListLengthFixed(isListLengthFixed); + prop.setMaxListLength(maxListLength); + + if (!prop.isDoubleList()) + { + prop.setValues(values); + } + + return prop; + } + + /* **************************************************************************************************************** + * + * STRING gets + * + *****************************************************************************************************************/ + + /** + * Gets a string Property without a comment using the default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValue the default value + * @return a string Property with validationPattern = null, validValues = null + */ + public Property get(String category, String key, String defaultValue) + { + return get(category, key, defaultValue, (String) null); + } + + /** + * Gets a string Property with a comment using the default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValue the default value + * @param comment a String comment + * @return a string Property with validationPattern = null, validValues = null + */ public Property get(String category, String key, String defaultValue, String comment) { return get(category, key, defaultValue, comment, STRING); } - - public Property get(String category, String key, String[] defaultValue) + + /** + * Gets a string Property with a comment using the defined validationPattern and otherwise default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValue the default value + * @param comment a String comment + * @param validationPattern a Pattern object for input validation + * @return a string Property with the defined validationPattern, validValues = null + */ + public Property get(String category, String key, String defaultValue, String comment, Pattern validationPattern) { - return get(category, key, defaultValue, null); - } - - public Property get(String category, String key, String[] defaultValue, String comment) - { - return get(category, key, defaultValue, comment, STRING); - } - - public Property get(String category, String key, int[] defaultValue) - { - return get(category, key, defaultValue, null); - } - - public Property get(String category, String key, int[] defaultValue, String comment) - { - String[] values = new String[defaultValue.length]; - for (int i = 0; i < defaultValue.length; i++) - { - values[i] = Integer.toString(defaultValue[i]); - } - - Property prop = get(category, key, values, comment, INTEGER); - if (!prop.isIntList()) - { - prop.set(values); - } - + Property prop = get(category, key, defaultValue, comment, STRING); + prop.setValidationPattern(validationPattern); return prop; } - - public Property get(String category, String key, double[] defaultValue) + + /** + * Gets a string Property with a comment using the defined validValues array and otherwise default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValue the default value + * @param comment a String comment + * @param validValues an array of valid values that this Property can be set to. If an array is provided the Config GUI control will be + * a value cycle button. + * @return a string Property with the defined validValues array, validationPattern = null + */ + public Property get(String category, String key, String defaultValue, String comment, String[] validValues) { - return get(category, key, defaultValue, null); - } - - public Property get(String category, String key, double[] defaultValue, String comment) - { - String[] values = new String[defaultValue.length]; - for (int i = 0; i < defaultValue.length; i++) - { - values[i] = Double.toString(defaultValue[i]); - } - - Property prop = get(category, key, values, comment, DOUBLE); - - if (!prop.isDoubleList()) - { - prop.set(values); - } - + Property prop = get(category, key, defaultValue, comment, STRING); + prop.setValidValues(validValues); return prop; } - - public Property get(String category, String key, boolean[] defaultValue) + + /** + * Gets a string array Property without a comment using the default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValues an array containing the default values + * @return a string array Property with validationPattern = null, isListLengthFixed = false, maxListLength = -1 + */ + public Property get(String category, String key, String[] defaultValues) { - return get(category, key, defaultValue, null); + return get(category, key, defaultValues, (String) null, false, -1, (Pattern) null); } - - public Property get(String category, String key, boolean[] defaultValue, String comment) + + /** + * Gets a string array Property with a comment using the default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValues an array containing the default values + * @param comment a String comment + * @return a string array Property with validationPattern = null, isListLengthFixed = false, maxListLength = -1 + */ + public Property get(String category, String key, String[] defaultValues, String comment) { - String[] values = new String[defaultValue.length]; - for (int i = 0; i < defaultValue.length; i++) - { - values[i] = Boolean.toString(defaultValue[i]); - } - - Property prop = get(category, key, values, comment, BOOLEAN); - - if (!prop.isBooleanList()) - { - prop.set(values); - } - + return get(category, key, defaultValues, comment, false, -1, (Pattern) null); + } + + /** + * Gets a string array Property with a comment using the defined validationPattern and otherwise default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValues an array containing the default values + * @param comment a String comment + * @param validationPattern a Pattern object for input validation + * @return a string array Property with the defined validationPattern, isListLengthFixed = false, maxListLength = -1 + */ + public Property get(String category, String key, String[] defaultValues, String comment, Pattern validationPattern) + { + return get(category, key, defaultValues, comment, false, -1, validationPattern); + } + + /** + * Gets a string array Property with a comment with all settings defined. + * + * @param category the config category + * @param key the Property key value + * @param defaultValues an array containing the default values + * @param comment a String comment + * @param isListLengthFixed boolean for whether this array is required to be a specific length (defined by the default value array + * length or maxListLength) + * @param maxListLength the maximum length of this array, use -1 for no max length + * @param validationPattern a Pattern object for input validation + * @return a string array Property with a comment with all settings defined + */ + public Property get(String category, String key, String[] defaultValues, String comment, + boolean isListLengthFixed, int maxListLength, Pattern validationPattern) + { + Property prop = get(category, key, defaultValues, comment, STRING); + prop.setIsListLengthFixed(isListLengthFixed); + prop.setMaxListLength(maxListLength); + prop.setValidationPattern(validationPattern); return prop; } - + + /* **************************************************************************************************************** + * + * GENERIC gets + * + *****************************************************************************************************************/ + + /** + * Gets a Property object of the specified type using default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValue the default value + * @param comment a String comment + * @param type a Property.Type enum value + * @return a Property object of the specified type using default settings + */ public Property get(String category, String key, String defaultValue, String comment, Property.Type type) { if (!caseSensitiveCustomCategories) @@ -247,14 +703,16 @@ cat.put(key, prop); } + prop.setDefaultValue(defaultValue); prop.comment = comment; return prop; } else if (defaultValue != null) { Property prop = new Property(key, defaultValue, type); - prop.set(defaultValue); //Set and mark as dirty to signify it should save + prop.setValue(defaultValue); //Set and mark as dirty to signify it should save cat.put(key, prop); + prop.setDefaultValue(defaultValue); prop.comment = comment; return prop; } @@ -264,7 +722,17 @@ } } - public Property get(String category, String key, String[] defaultValue, String comment, Property.Type type) + /** + * Gets a list (array) Property object of the specified type using default settings. + * + * @param category the config category + * @param key the Property key value + * @param defaultValues an array containing the default values + * @param comment a String comment + * @param type a Property.Type enum value + * @return a list (array) Property object of the specified type using default settings + */ + public Property get(String category, String key, String[] defaultValues, String comment, Property.Type type) { if (!caseSensitiveCustomCategories) { @@ -283,13 +751,15 @@ cat.put(key, prop); } + prop.setDefaultValues(defaultValues); prop.comment = comment; return prop; } - else if (defaultValue != null) + else if (defaultValues != null) { - Property prop = new Property(key, defaultValue, type); + Property prop = new Property(key, defaultValues, type); + prop.setDefaultValues(defaultValues); prop.comment = comment; cat.put(key, prop); return prop; @@ -300,6 +770,12 @@ } } + /* **************************************************************************************************************** + * + * Other methods + * + *************************************************************************************************************** */ + public boolean hasCategory(String category) { return categories.get(category) != null; @@ -327,9 +803,13 @@ file.getParentFile().mkdirs(); } - if (!file.exists() && !file.createNewFile()) + if (!file.exists()) { - return; + // Either a previous load attempt failed or the file is new; clear maps + categories.clear(); + children.clear(); + if (!file.createNewFile()) + return; } if (file.canRead()) @@ -344,6 +824,7 @@ ArrayList tmpList = null; int lineNum = 0; String name = null; + loadedConfigVersion = null; while (true) { @@ -352,6 +833,8 @@ if (line == null) { + if (lineNum == 1) + loadedConfigVersion = definedConfigVersion; break; } @@ -376,6 +859,7 @@ int nameStart = -1, nameEnd = -1; boolean skip = false; boolean quoted = false; + boolean isFirstNonWhitespaceCharOnLine = true; for (int i = 0; i < line.length() && !skip; ++i) { @@ -387,6 +871,7 @@ } nameEnd = i; + isFirstNonWhitespaceCharOnLine = false; } else if (Character.isWhitespace(line.charAt(i))) { @@ -397,10 +882,14 @@ switch (line.charAt(i)) { case '#': + if (tmpList != null) // allow special characters as part of string lists + break; skip = true; continue; case '"': + if (tmpList != null) // allow special characters as part of string lists + break; if (quoted) { quoted = false; @@ -412,6 +901,8 @@ break; case '{': + if (tmpList != null) // allow special characters as part of string lists + break; name = line.substring(nameStart, nameEnd + 1); String qualifiedName = ConfigCategory.getQualifiedName(name, currentCat); @@ -430,6 +921,8 @@ break; case '}': + if (tmpList != null) // allow special characters as part of string lists + break; if (currentCat == null) { throw new RuntimeException(String.format("Config file corrupt, attepted to close to many categories '%s:%d'", fileName, lineNum)); @@ -438,6 +931,8 @@ break; case '=': + if (tmpList != null) // allow special characters as part of string lists + break; name = line.substring(nameStart, nameEnd + 1); if (currentCat == null) @@ -453,26 +948,30 @@ break; case ':': + if (tmpList != null) // allow special characters as part of string lists + break; type = Property.Type.tryParse(line.substring(nameStart, nameEnd + 1).charAt(0)); nameStart = nameEnd = -1; break; case '<': - if (tmpList != null) + if ((tmpList != null && i + 1 == line.length()) || (tmpList == null && i + 1 != line.length())) { throw new RuntimeException(String.format("Malformed list property \"%s:%d\"", fileName, lineNum)); } - - name = line.substring(nameStart, nameEnd + 1); - - if (currentCat == null) + else if (i + 1 == line.length()) { - throw new RuntimeException(String.format("'%s' has no scope in '%s:%d'", name, fileName, lineNum)); + name = line.substring(nameStart, nameEnd + 1); + + if (currentCat == null) + { + throw new RuntimeException(String.format("'%s' has no scope in '%s:%d'", name, fileName, lineNum)); + } + + tmpList = new ArrayList(); + + skip = true; } - - tmpList = new ArrayList(); - - skip = true; break; @@ -482,15 +981,35 @@ throw new RuntimeException(String.format("Malformed list property \"%s:%d\"", fileName, lineNum)); } - currentCat.put(name, new Property(name, tmpList.toArray(new String[tmpList.size()]), type)); - name = null; - tmpList = null; - type = null; + if (isFirstNonWhitespaceCharOnLine) + { + currentCat.put(name, new Property(name, tmpList.toArray(new String[tmpList.size()]), type)); + name = null; + tmpList = null; + type = null; + } // else allow special characters as part of string lists break; + case '~': + if (tmpList != null) // allow special characters as part of string lists + break; + + if (line.startsWith(CONFIG_VERSION_MARKER)) + { + int colon = line.indexOf(':'); + if (colon != -1) + loadedConfigVersion = line.substring(colon + 1).trim(); + + skip = true; + } + break; + default: + if (tmpList != null) // allow special characters as part of string lists + break; throw new RuntimeException(String.format("Unknown character '%s' in '%s:%d'", line.charAt(i), fileName, lineNum)); } + isFirstNonWhitespaceCharOnLine = false; } } @@ -557,6 +1076,9 @@ buffer.write("# Configuration file" + NEW_LINE + NEW_LINE); + if (this.definedConfigVersion != null) + buffer.write(CONFIG_VERSION_MARKER + ": " + this.definedConfigVersion + NEW_LINE + NEW_LINE); + if (children.isEmpty()) { save(buffer); @@ -656,13 +1178,93 @@ } } - public void addCustomCategoryComment(String category, String comment) + /** + * Adds a comment to the specified ConfigCategory object + * + * @param category the config category + * @param comment a String comment + */ + public Configuration setCategoryComment(String category, String comment) { if (!caseSensitiveCustomCategories) category = category.toLowerCase(Locale.ENGLISH); getCategory(category).setComment(comment); + return this; } + public void addCustomCategoryComment(String category, String comment) + { + this.setCategoryComment(category, comment); + } + + /** + * Adds a language key to the specified ConfigCategory object + * + * @param category the config category + * @param langKey a language key string such as configcategory.general + */ + public Configuration setCategoryLanguageKey(String category, String langKey) + { + if (!caseSensitiveCustomCategories) + category = category.toLowerCase(Locale.ENGLISH); + getCategory(category).setLanguageKey(langKey); + return this; + } + + /** + * Sets the custom IConfigEntry class that should be used in place of the standard entry class (which is just a button that + * navigates into the category). This class MUST provide a constructor with the following parameter types: {@code GuiConfig} (the parent + * GuiConfig screen will be provided), {@code GuiPropertyList} (the parent GuiPropertyList will be provided), {@code IConfigElement} + * (the IConfigElement for this Property will be provided). + */ + public Configuration setCategoryConfigEntryClass(String category, Class clazz) + { + + if (!caseSensitiveCustomCategories) + category = category.toLowerCase(Locale.ENGLISH); + getCategory(category).setConfigEntryClass(clazz); + return this; + } + + /** + * Sets the flag for whether or not this category can be edited while a world is running. Care should be taken to ensure + * that only properties that are truly dynamic can be changed from the in-game options menu. Only set this flag to + * true if all child properties/categories are unable to be modified while a world is running. + */ + public Configuration setCategoryRequiresWorldRestart(String category, boolean requiresWorldRestart) + { + if (!caseSensitiveCustomCategories) + category = category.toLowerCase(Locale.ENGLISH); + getCategory(category).setRequiresWorldRestart(requiresWorldRestart); + return this; + } + + /** + * Sets whether or not this ConfigCategory requires Minecraft to be restarted when changed. + * Defaults to false. Only set this flag to true if ALL child properties/categories require + * Minecraft to be restarted when changed. Setting this flag will also prevent modification + * of the child properties/categories while a world is running. + */ + public Configuration setCategoryRequiresMcRestart(String category, boolean requiresMcRestart) + { + if (!caseSensitiveCustomCategories) + category = category.toLowerCase(Locale.ENGLISH); + getCategory(category).setRequiresMcRestart(requiresMcRestart); + return this; + } + + /** + * Sets the order that direct child properties of this config category will be written to the config file and will be displayed in + * config GUIs. + */ + public Configuration setCategoryPropertyOrder(String category, List propOrder) + { + if (!caseSensitiveCustomCategories) + category = category.toLowerCase(Locale.ENGLISH); + getCategory(category).setPropertyOrder(propOrder); + return this; + } + private void setChild(String name, Configuration child) { if (!children.containsKey(name)) @@ -791,4 +1393,330 @@ { return ImmutableSet.copyOf(categories.keySet()); } -} + + /** + * Renames a property in a given category. + * + * @param category the category in which the property resides + * @param oldPropName the existing property name + * @param newPropName the new property name + * @return true if the category and property exist, false otherwise + */ + public boolean renameProperty(String category, String oldPropName, String newPropName) + { + if (hasCategory(category)) + { + if (getCategory(category).containsKey(oldPropName) && !oldPropName.equalsIgnoreCase(newPropName)) + { + get(category, newPropName, getCategory(category).get(oldPropName).getString(), ""); + getCategory(category).remove(oldPropName); + return true; + } + } + return false; + } + + /** + * Moves a property from one category to another. + * + * @param oldCategory the category the property currently resides in + * @param propName the name of the property to move + * @param newCategory the category the property should be moved to + * @return true if the old category and property exist, false otherwise + */ + public boolean moveProperty(String oldCategory, String propName, String newCategory) + { + if (!oldCategory.equals(newCategory)) + if (hasCategory(oldCategory)) + if (getCategory(oldCategory).containsKey(propName)) + { + getCategory(newCategory).put(propName, getCategory(oldCategory).remove(propName)); + return true; + } + return false; + } + + /** + * Copies property objects from another Configuration object to this one using the list of category names. Properties that only exist in the + * "from" object are ignored. Pass null for the ctgys array to include all categories. + */ + public void copyCategoryProps(Configuration fromConfig, String[] ctgys) + { + if (ctgys == null) + ctgys = this.getCategoryNames().toArray(new String[this.getCategoryNames().size()]); + + for (String ctgy : ctgys) + if (fromConfig.hasCategory(ctgy) && this.hasCategory(ctgy)) + { + ConfigCategory thiscc = this.getCategory(ctgy); + ConfigCategory fromcc = fromConfig.getCategory(ctgy); + for (Entry entry : thiscc.getValues().entrySet()) + if (fromcc.containsKey(entry.getKey())) + thiscc.put(entry.getKey(), fromcc.get(entry.getKey())); + } + } + + /** + * Creates a string property. + * + * @param name Name of the property. + * @param category Category of the property. + * @param defaultValue Default value of the property. + * @param comment A brief description what the property does. + * @return The value of the new string property. + */ + public String getString(String name, String category, String defaultValue, String comment) + { + return getString(name, category, defaultValue, comment, name, null); + } + + /** + * Creates a string property. + * + * @param name Name of the property. + * @param category Category of the property. + * @param defaultValue Default value of the property. + * @param comment A brief description what the property does. + * @param langKey A language key used for localization of GUIs + * @return The value of the new string property. + */ + public String getString(String name, String category, String defaultValue, String comment, String langKey) + { + return getString(name, category, defaultValue, comment, langKey, null); + } + + /** + * Creates a string property. + * + * @param name Name of the property. + * @param category Category of the property. + * @param defaultValue Default value of the property. + * @param comment A brief description what the property does. + * @return The value of the new string property. + */ + public String getString(String name, String category, String defaultValue, String comment, Pattern pattern) + { + return getString(name, category, defaultValue, comment, name, pattern); + } + + /** + * Creates a string property. + * + * @param name Name of the property. + * @param category Category of the property. + * @param defaultValue Default value of the property. + * @param comment A brief description what the property does. + * @param langKey A language key used for localization of GUIs + * @return The value of the new string property. + */ + public String getString(String name, String category, String defaultValue, String comment, String langKey, Pattern pattern) + { + Property prop = this.get(category, name, defaultValue); + prop.setLanguageKey(langKey); + prop.setValidationPattern(pattern); + prop.comment = comment + " [default: " + defaultValue + "]"; + return prop.getString(); + } + + /** + * Creates a string property. + * + * @param name Name of the property. + * @param category Category of the property. + * @param defaultValue Default value of the property. + * @param comment A brief description what the property does. + * @param validValues A list of valid values that this property can be set to. + * @return The value of the new string property. + */ + public String getString(String name, String category, String defaultValue, String comment, String[] validValues) + { + return getString(name, category, defaultValue, comment, validValues, name); + } + + /** + * Creates a string property. + * + * @param name Name of the property. + * @param category Category of the property. + * @param defaultValue Default value of the property. + * @param comment A brief description what the property does. + * @param validValues A list of valid values that this property can be set to. + * @param langKey A language key used for localization of GUIs + * @return The value of the new string property. + */ + public String getString(String name, String category, String defaultValue, String comment, String[] validValues, String langKey) + { + Property prop = this.get(category, name, defaultValue); + prop.setValidValues(validValues); + prop.setLanguageKey(langKey); + prop.comment = comment + " [default: " + defaultValue + "]"; + return prop.getString(); + } + + /** + * Creates a string list property. + * + * @param name Name of the property. + * @param category Category of the property. + * @param defaultValue Default value of the property. + * @param comment A brief description what the property does. + * @return The value of the new string property. + */ + public String[] getStringList(String name, String category, String[] defaultValues, String comment) + { + return getStringList(name, category, defaultValues, comment, (String[]) null, name); + } + + /** + * Creates a string list property. + * + * @param name Name of the property. + * @param category Category of the property. + * @param defaultValue Default value of the property. + * @param comment A brief description what the property does. + * @return The value of the new string property. + */ + public String[] getStringList(String name, String category, String[] defaultValue, String comment, String[] validValues) + { + return getStringList(name, category, defaultValue, comment, validValues, name); + } + + /** + * Creates a string list property. + * + * @param name Name of the property. + * @param category Category of the property. + * @param defaultValue Default value of the property. + * @param comment A brief description what the property does. + * @return The value of the new string property. + */ + public String[] getStringList(String name, String category, String[] defaultValue, String comment, String[] validValues, String langKey) + { + Property prop = this.get(category, name, defaultValue); + prop.setLanguageKey(langKey); + prop.setValidValues(validValues); + prop.comment = comment + " [default: " + defaultValue + "]"; + return prop.getStringList(); + } + + /** + * Creates a boolean property. + * + * @param name Name of the property. + * @param category Category of the property. + * @param defaultValue Default value of the property. + * @param comment A brief description what the property does. + * @return The value of the new boolean property. + */ + public boolean getBoolean(String name, String category, boolean defaultValue, String comment) + { + return getBoolean(name, category, defaultValue, comment, name); + } + + /** + * Creates a boolean property. + * + * @param name Name of the property. + * @param category Category of the property. + * @param defaultValue Default value of the property. + * @param comment A brief description what the property does. + * @param langKey A language key used for localization of GUIs + * @return The value of the new boolean property. + */ + public boolean getBoolean(String name, String category, boolean defaultValue, String comment, String langKey) + { + Property prop = this.get(category, name, defaultValue); + prop.setLanguageKey(langKey); + prop.comment = comment + " [default: " + defaultValue + "]"; + return prop.getBoolean(defaultValue); + } + + /** + * Creates a integer property. + * + * @param name Name of the property. + * @param category Category of the property. + * @param defaultValue Default value of the property. + * @param minValue Minimum value of the property. + * @param maxValue Maximum value of the property. + * @param comment A brief description what the property does. + * @return The value of the new integer property. + */ + public int getInt(String name, String category, int defaultValue, int minValue, int maxValue, String comment) + { + return getInt(name, category, defaultValue, minValue, maxValue, comment, name); + } + + /** + * Creates a integer property. + * + * @param name Name of the property. + * @param category Category of the property. + * @param defaultValue Default value of the property. + * @param minValue Minimum value of the property. + * @param maxValue Maximum value of the property. + * @param comment A brief description what the property does. + * @param langKey A language key used for localization of GUIs + * @return The value of the new integer property. + */ + public int getInt(String name, String category, int defaultValue, int minValue, int maxValue, String comment, String langKey) + { + Property prop = this.get(category, name, defaultValue); + prop.setLanguageKey(langKey); + prop.comment = comment + " [range: " + minValue + " ~ " + maxValue + ", default: " + defaultValue + "]"; + prop.setMinValue(minValue); + prop.setMaxValue(maxValue); + return prop.getInt(defaultValue) < minValue ? minValue : (prop.getInt(defaultValue) > maxValue ? maxValue : prop.getInt(defaultValue)); + } + + /** + * Creates a float property. + * + * @param name Name of the property. + * @param category Category of the property. + * @param defaultValue Default value of the property. + * @param minValue Minimum value of the property. + * @param maxValue Maximum value of the property. + * @param comment A brief description what the property does. + * @return The value of the new float property. + */ + public float getFloat(String name, String category, float defaultValue, float minValue, float maxValue, String comment) + { + return getFloat(name, category, defaultValue, minValue, maxValue, comment, name); + } + + /** + * Creates a float property. + * + * @param name Name of the property. + * @param category Category of the property. + * @param defaultValue Default value of the property. + * @param minValue Minimum value of the property. + * @param maxValue Maximum value of the property. + * @param comment A brief description what the property does. + * @param langKey A language key used for localization of GUIs + * @return The value of the new float property. + */ + public float getFloat(String name, String category, float defaultValue, float minValue, float maxValue, String comment, String langKey) + { + Property prop = this.get(category, name, Float.toString(defaultValue), name); + prop.setLanguageKey(langKey); + prop.comment = comment + " [range: " + minValue + " ~ " + maxValue + ", default: " + defaultValue + "]"; + prop.setMinValue(minValue); + prop.setMaxValue(maxValue); + try + { + return Float.parseFloat(prop.getString()) < minValue ? minValue : (Float.parseFloat(prop.getString()) > maxValue ? maxValue : Float.parseFloat(prop.getString())); + } + catch (Exception e) + { + e.printStackTrace(); + } + return defaultValue; + } + + public File getConfigFile() + { + return file; + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/common/config/Property.java b/src/main/java/net/minecraftforge/common/config/Property.java index a54f96f..f800b28 100644 --- a/src/main/java/net/minecraftforge/common/config/Property.java +++ b/src/main/java/net/minecraftforge/common/config/Property.java @@ -6,6 +6,11 @@ package net.minecraftforge.common.config; import java.util.ArrayList; +import java.util.Arrays; +import java.util.regex.Pattern; + +import cpw.mods.fml.client.config.GuiConfigEntries.IConfigEntry; +import cpw.mods.fml.client.config.GuiEditArrayEntries.IArrayEntry; public class Property { @@ -14,17 +19,17 @@ STRING, INTEGER, BOOLEAN, - DOUBLE; - - private static Type[] values = {STRING, INTEGER, BOOLEAN, DOUBLE}; + DOUBLE, + COLOR, + MOD_ID; public static Type tryParse(char id) { - for (int x = 0; x < values.length; x++) + for (int x = 0; x < values().length; x++) { - if (values[x].getID() == id) + if (values()[x].getID() == id) { - return values[x]; + return values()[x]; } } @@ -39,33 +44,76 @@ private String name; private String value; + private String defaultValue; public String comment; private String[] values; + private String[] defaultValues; + private String[] validValues; + private String langKey; + private String minValue; + private String maxValue; + private Class configEntryClass = null; + private Class arrayEntryClass = null; + + private boolean requiresWorldRestart = false; + private boolean showInGui = true; + private boolean requiresMcRestart = false; + private Pattern validationPattern; private final boolean wasRead; private final boolean isList; + private boolean isListLengthFixed = false; + private int maxListLength = -1; private final Type type; private boolean changed = false; - public Property() - { - wasRead = false; - type = null; - isList = false; - } - public Property(String name, String value, Type type) { - this(name, value, type, false); + this(name, value, type, false, new String[0], name); } - Property(String name, String value, Type type, boolean read) + public Property(String name, String value, Type type, boolean read) + { + this(name, value, type, read, new String[0], name); + } + + public Property(String name, String value, Type type, String[] validValues) + { + this(name, value, type, false, validValues, name); + } + + public Property(String name, String value, Type type, String langKey) + { + this(name, value, type, false, new String[0], langKey); + } + + public Property(String name, String value, Type type, boolean read, String langKey) + { + this(name, value, type, read, new String[0], langKey); + } + + public Property(String name, String value, Type type, String[] validValues, String langKey) + { + this(name, value, type, false, validValues, langKey); + } + + Property(String name, String value, Type type, boolean read, String[] validValues, String langKey) { setName(name); this.value = value; + this.values = new String[0]; this.type = type; wasRead = read; isList = false; + this.defaultValue = value; + this.defaultValues = new String[0]; + this.validValues = validValues; + this.isListLengthFixed = false; + this.maxListLength = -1; + this.minValue = String.valueOf(Integer.MIN_VALUE); + this.maxValue = String.valueOf(Integer.MAX_VALUE); + this.langKey = langKey; + this.comment = ""; } public Property(String name, String[] values, Type type) @@ -75,16 +123,516 @@ Property(String name, String[] values, Type type, boolean read) { + this(name, values, type, read, new String[0], name); + } + + public Property(String name, String[] values, Type type, String langKey) + { + this(name, values, type, false, langKey); + } + + Property(String name, String[] values, Type type, boolean read, String langKey) + { + this(name, values, type, read, new String[0], langKey); + } + + Property(String name, String[] values, Type type, boolean read, String[] validValues, String langKey) + { setName(name); this.type = type; - this.values = values; + this.values = Arrays.copyOf(values, values.length); wasRead = read; isList = true; + this.value = ""; + this.defaultValue = ""; + for (String s : values) + this.defaultValue += ", [" + s + "]"; + this.defaultValue = this.defaultValue.replaceFirst(", ", ""); + this.defaultValues = Arrays.copyOf(values, values.length); + this.validValues = validValues; + this.isListLengthFixed = false; + this.maxListLength = -1; + this.minValue = String.valueOf(Integer.MIN_VALUE); + this.maxValue = String.valueOf(Integer.MAX_VALUE); + this.langKey = langKey; + this.comment = ""; + } + + /** + * Returns whether or not this Property is defaulted. + * + * @return true if the current value(s) is(are) deeply equal to the default value(s) + */ + public boolean isDefault() + { + if (this.isBooleanList()) + { + if (values.length == defaultValues.length) + { + for (int i = 0; i < values.length; i++) + if (Boolean.parseBoolean(values[i]) != Boolean.parseBoolean(defaultValues[i])) + return false; + + return true; + } + else + return false; + } + + if (this.isIntList()) + { + if (values.length == defaultValues.length) + { + for (int i = 0; i < values.length; i++) + if (Integer.parseInt(values[i]) != Integer.parseInt(defaultValues[i])) + return false; + + return true; + } + else + return false; + } + + if (this.isDoubleList()) + { + if (values.length == defaultValues.length) + { + for (int i = 0; i < values.length; i++) + if (Double.parseDouble(values[i]) != Double.parseDouble(defaultValues[i])) + return false; + + return true; + } + else + return false; + } + + if (this.isList()) + { + if (values.length == defaultValues.length) + { + for (int i = 0; i < values.length; i++) + if (!values[i].equals(defaultValues[i])) + return false; + + return true; + } + else + return false; + } + + if (this.type == Type.BOOLEAN && this.isBooleanValue()) + return Boolean.parseBoolean(value) == Boolean.parseBoolean(defaultValue); + + if (this.type == Type.INTEGER && this.isIntValue()) + return Integer.parseInt(value) == Integer.parseInt(defaultValue); + + if (this.type == Type.DOUBLE && this.isDoubleValue()) + return Double.parseDouble(value) == Double.parseDouble(defaultValue); + + return value.equals(defaultValue); + } + + /** + * Sets the current value(s) of this Property to the default value(s). + */ + public Property setToDefault() + { + this.value = this.defaultValue; + this.values = Arrays.copyOf(this.defaultValues, this.defaultValues.length); + return this; + } + + /** + * Gets the raw String default value of this Property. Check for isList() == false first. + * + * @return the default value String + */ + public String getDefault() + { + return defaultValue; + } + + /** + * Gets the raw String[] default values of this Property. Check for isList() == true first. + * + * @return the default values String[] + */ + public String[] getDefaults() + { + return Arrays.copyOf(this.defaultValues, this.defaultValues.length); + } + + /** + * Sets the flag for whether or not this Property can be edited while a world is running. Care should be taken to ensure + * that only properties that are truly dynamic can be changed from the in-game options menu. When set to false the Property will be + * editable from both the main menu Mods list config screen and the in-game Mod Options config screen. When set to true the Property + * will only be editable from the main menu Mods list config screen. + */ + public Property setRequiresWorldRestart(boolean requiresWorldRestart) + { + this.requiresWorldRestart = requiresWorldRestart; + return this; + } + + /** + * Returns whether or not this Property is able to be edited while a world is running using the in-game Mod Options screen + * as well as the Mods list screen, or only from the Mods list screen. Setting this flag to true will disable editing of + * this property while a world is running. + */ + public boolean requiresWorldRestart() + { + return this.requiresWorldRestart; + } + + /** + * Sets whether or not this Property should be allowed to show on config GUIs. + * Defaults to true. + */ + public Property setShowInGui(boolean showInGui) + { + this.showInGui = showInGui; + return this; + } + + /** + * Gets whether or not this Property should be allowed to show on config GUIs. + * Defaults to true unless set to false. + */ + public boolean showInGui() + { + return showInGui; + } + + /** + * Sets whether or not this Property requires Minecraft to be restarted when changed. + * Defaults to false. Setting this flag to true will also disable editing of + * this property while a world is running. + */ + public Property setRequiresMcRestart(boolean requiresMcRestart) + { + this.requiresMcRestart = this.requiresWorldRestart = requiresMcRestart; + return this; + } + + /** + * Gets whether or not this Property requires Minecraft to be restarted when changed. + * Defaults to false unless set to true. + */ + public boolean requiresMcRestart() + { + return this.requiresMcRestart; + } + + /** + * Sets the maximum length of this list/array Property. Only important if isList() == true. If the current values array or default + * values array is longer than the new maximum it will be resized. If calling both this method and setIsListLengthFixed(true), this + * method should be called afterwards (but is not required). + */ + public Property setMaxListLength(int max) + { + this.maxListLength = max; + if (this.maxListLength != -1) + { + if (values != null && values.length != maxListLength) + if (this.isListLengthFixed || values.length > maxListLength) + values = Arrays.copyOf(values, maxListLength); + + if (defaultValues != null && defaultValues.length != maxListLength) + if (this.isListLengthFixed || defaultValues.length > maxListLength) + defaultValues = Arrays.copyOf(defaultValues, maxListLength); + } + return this; + } + + /** + * Gets the maximum length of this list/array Property. Only important if isList() == true. + */ + public int getMaxListLength() + { + return this.maxListLength; + } + + /** + * Sets the flag for whether this list/array Property has a fixed length. Only important if isList() == true. If calling both this + * method and setMaxListLength(), this method should be called first (but is not required). + */ + public Property setIsListLengthFixed(boolean isListLengthFixed) + { + this.isListLengthFixed = isListLengthFixed; + return this; + } + + /** + * Returns whether or not this list/array has a fixed length. Only important if isList() == true. + */ + public boolean isListLengthFixed() + { + return this.isListLengthFixed; + } + + /** + * Sets a custom IConfigEntry class that should be used in place of the standard entry class for this Property type. This class + * MUST provide a constructor with the following parameter types: {@code GuiConfig} (the owning GuiConfig screen will be provided), + * {@code GuiConfigEntries} (the owning GuiConfigEntries will be provided), {@code IConfigElement} (the IConfigElement for this Property + * will be provided). + */ + public Property setConfigEntryClass(Class clazz) + { + this.configEntryClass = clazz; + return this; + } + + /** + * Gets the custom IConfigEntry class that should be used in place of the standard entry class for this Property type, or null if + * none has been set. + * + * @return a class that implements IConfigEntry + */ + public Class getConfigEntryClass() + { + return this.configEntryClass; + } + + /** + * Sets a custom IGuiEditListEntry class that should be used in place of the standard entry class for this Property type. This class + * MUST provide a constructor with the following parameter types: {@code GuiEditList} (the owning GuiEditList screen will be provided), + * {@code GuiPropertyList} (the parent GuiPropertyList will be provided), {@code IConfigProperty} (the IConfigProperty for this Property + * will be provided). + * + * @param clazz a class that implements IConfigEntry + */ + public Property setArrayEntryClass(Class clazz) + { + this.arrayEntryClass = clazz; + return this; + } + + /** + * Gets the custom IArrayEntry class that should be used in place of the standard entry class for this Property type, or null if + * none has been set. + * + * @return a class that implements IArrayEntry + */ + public Class getArrayEntryClass() + { + return this.arrayEntryClass; + } + + /** + * Sets a regex Pattern object used to validate user input for formatted String or String[] properties. + * + * @param validationPattern + */ + public Property setValidationPattern(Pattern validationPattern) + { + this.validationPattern = validationPattern; + return this; + } + + /** + * Gets the Pattern object used to validate user input for this Property. + * + * @return the user input validation Pattern object, or null if none is set + */ + public Pattern getValidationPattern() + { + return this.validationPattern; + } + + /** + * Sets the localization language key for this Property so that the config GUI screens are nice and pretty <3. The string languageKey + + * ".tooltip" is used for tooltips when a user hovers the mouse over a GUI property label. + * + * @param langKey a string language key such as myawesomemod.config.myPropName + */ + public Property setLanguageKey(String langKey) + { + this.langKey = langKey; + return this; + } + + /** + * Gets the language key string for this Property. + * + * @return the language key + */ + public String getLanguageKey() + { + return this.langKey; + } + + /** + * Sets the default string value of this Property. + * + * @param defaultValue a String value + */ + public Property setDefaultValue(String defaultValue) + { + this.defaultValue = defaultValue; + return this; + } + + /** + * Sets the default String[] values of this Property. + * + * @param defaultValues an array of String values + */ + public Property setDefaultValues(String[] defaultValues) + { + this.defaultValue = ""; + for (String s : defaultValues) + this.defaultValue += ", [" + s + "]"; + this.defaultValue = this.defaultValue.replaceFirst(", ", ""); + this.defaultValues = Arrays.copyOf(defaultValues, defaultValues.length); + return this; + } + + /** + * Sets the default int value of this Property. + * + * @param defaultValue an int value + */ + public Property setDefaultValue(int defaultValue) + { + setDefaultValue(Integer.toString(defaultValue)); + return this; + } + + /** + * Sets the default int[] values of this Property. + * + * @param defaultValues an array of int values + */ + public Property setDefaultValues(int[] defaultValues) + { + String[] temp = new String[defaultValues.length]; + for (int i = 0; i < defaultValues.length; i++) + temp[i] = Integer.toString(defaultValues[i]); + + setDefaultValues(temp); + return this; + } + + /** + * Sets the default double value of this Property. + * + * @param defaultValue a double value + */ + public Property setDefaultValue(double defaultValue) + { + setDefaultValue(Double.toString(defaultValue)); + return this; + } + + /** + * Sets the default double[] values of this Property + * + * @param defaultValues an array of double values + */ + public Property setDefaultValues(double[] defaultValues) + { + String[] temp = new String[defaultValues.length]; + for (int i = 0; i < defaultValues.length; i++) + temp[i] = Double.toString(defaultValues[i]); + + setDefaultValues(temp); + return this; + } + + /** + * Sets the default boolean value of this Property. + * + * @param defaultValue a boolean value + */ + public Property setDefaultValue(boolean defaultValue) + { + setDefaultValue(Boolean.toString(defaultValue)); + return this; + } + + /** + * Sets the default boolean[] values of this Property. + * + * @param defaultValues an array of boolean values + */ + public Property setDefaultValues(boolean[] defaultValues) + { + String[] temp = new String[defaultValues.length]; + for (int i = 0; i < defaultValues.length; i++) + temp[i] = Boolean.toString(defaultValues[i]); + + setDefaultValues(temp); + return this; + } + + /** + * Sets the minimum int value of this Property. + * + * @param minValue an int value + */ + public Property setMinValue(int minValue) + { + this.minValue = Integer.toString(minValue); + return this; + } + + /** + * Sets the maximum int value of this Property. + * + * @param maxValue an int value + */ + public Property setMaxValue(int maxValue) + { + this.maxValue = Integer.toString(maxValue); + return this; + } + + /** + * Sets the minimum double value of this Property. + * + * @param minValue a double value + */ + public Property setMinValue(double minValue) + { + this.minValue = Double.toString(minValue); + return this; + } + + /** + * Sets the maximum double value of this Property. + * + * @param maxValue a double value + */ + public Property setMaxValue(double maxValue) + { + this.maxValue = Double.toString(maxValue); + return this; + } + + /** + * Gets the minimum value. + * + * @return the minimum value bound + */ + public String getMinValue() + { + return minValue; + } + + /** + * Gets the maximum value. + * + * @return the maximum value bound + */ + public String getMaxValue() + { + return maxValue; } /** * Returns the value in this property as it's raw string. - * + * * @return current value */ public String getString() @@ -93,21 +641,50 @@ } /** + * Sets the array of valid values that this String Property can be set to. When an array of valid values is defined for a Property the + * GUI control for that property will be a value cycle button. + * + * @param validValues a String array of valid values + */ + public Property setValidValues(String[] validValues) + { + this.validValues = validValues; + return this; + } + + /** + * Gets the array of valid values that this String Property can be set to, or null if not defined. + * + * @return a String array of valid values + */ + public String[] getValidValues() + { + return this.validValues; + } + + /** * Returns the value in this property as an integer, - * if the value is not a valid integer, it will return -1. - * + * if the value is not a valid integer, it will return the initially provided default. + * * @return The value */ public int getInt() { - return getInt(-1); + try + { + return Integer.parseInt(value); + } + catch (NumberFormatException e) + { + return Integer.parseInt(defaultValue); + } } /** * Returns the value in this property as an integer, * if the value is not a valid integer, it will return the * provided default. - * + * * @param _default The default to provide if the current value is not a valid integer * @return The value */ @@ -122,7 +699,7 @@ return _default; } } - + /** * Checks if the current value stored in this property can be converted to an integer. * @return True if the type of the Property is an Integer @@ -144,7 +721,7 @@ * Returns the value in this property as a boolean, * if the value is not a valid boolean, it will return the * provided default. - * + * * @param _default The default to provide * @return The value as a boolean, or the default */ @@ -161,7 +738,25 @@ } /** + * Returns the value in this property as a boolean, if the value is not a valid boolean, it will return the provided default. + * + * @return The value as a boolean, or the default + */ + public boolean getBoolean() + { + if (isBooleanValue()) + { + return Boolean.parseBoolean(value); + } + else + { + return Boolean.parseBoolean(defaultValue); + } + } + + /** * Checks if the current value held by this property is a valid boolean value. + * * @return True if it is a boolean value */ public boolean isBooleanValue() @@ -190,7 +785,7 @@ * Returns the value in this property as a double, * if the value is not a valid double, it will return the * provided default. - * + * * @param _default The default to provide if the current value is not a valid double * @return The value */ @@ -206,6 +801,24 @@ } } + /** + * Returns the value in this property as a double, if the value is not a valid double, it will return the provided default. + * + * @param _default The default to provide if the current value is not a valid double + * @return The value + */ + public double getDouble() + { + try + { + return Double.parseDouble(value); + } + catch (NumberFormatException e) + { + return Double.parseDouble(defaultValue); + } + } + public String[] getStringList() { return values; @@ -214,13 +827,13 @@ /** * Returns the integer value of all values that can * be parsed in the list. - * + * * @return Array of length 0 if none of the values could be parsed. */ public int[] getIntList() { ArrayList nums = new ArrayList(); - + for (String value : values) { try @@ -246,24 +859,25 @@ */ public boolean isIntList() { - for (String value : values) - { - try + if (isList && type == Type.INTEGER) + for (String value : values) { - Integer.parseInt(value); + try + { + Integer.parseInt(value); + } + catch (NumberFormatException e) + { + return false; + } } - catch (NumberFormatException e) - { - return false; - } - } - return true; + return isList && type == Type.INTEGER; } /** * Returns the boolean value of all values that can * be parsed in the list. - * + * * @return Array of length 0 if none of the values could be parsed. */ public boolean[] getBooleanList() @@ -294,21 +908,22 @@ */ public boolean isBooleanList() { - for (String value : values) - { - if (!"true".equalsIgnoreCase(value) && !"false".equalsIgnoreCase(value)) + if (isList && type == Type.BOOLEAN) + for (String value : values) { - return false; + if (!"true".equalsIgnoreCase(value) && !"false".equalsIgnoreCase(value)) + { + return false; + } } - } - return true; + return isList && type == Type.BOOLEAN; } /** * Returns the double value of all values that can * be parsed in the list. - * + * * @return Array of length 0 if none of the values could be parsed. */ public double[] getDoubleList() @@ -339,26 +954,37 @@ */ public boolean isDoubleList() { - for (String value : values) - { - try + if (isList && type == Type.DOUBLE) + for (String value : values) { - Double.parseDouble(value); + try + { + Double.parseDouble(value); + } + catch (NumberFormatException e) + { + return false; + } } - catch (NumberFormatException e) - { - return false; - } - } - return true; + return isList && type == Type.DOUBLE; } + /** + * Gets the name/key for this Property. + * + * @return the Property name + */ public String getName() { return name; } + /** + * Sets the name/key for this Property. + * + * @param name a name + */ public void setName(String name) { this.name = name; @@ -366,9 +992,9 @@ /** * Determines if this config value was just created, or if it was read from the config file. - * This is useful for mods who auto-assign there blocks to determine if the ID returned is + * This is useful for mods who auto-assign their blocks to determine if the ID returned is * a configured one, or a automatically generated one. - * + * * @return True if this property was loaded from the config file with a value */ public boolean wasRead() @@ -376,31 +1002,141 @@ return wasRead; } + /** + * Gets the Property.Type enum value for this Property. + * + * @return the Property's type + */ public Type getType() { return type; } + /** + * Returns whether or not this Property is a list/array. + * + * @return true if this Property is a list/array, false otherwise + */ public boolean isList() { return isList; } + /** + * Gets the changed status of this Property. + * + * @return true if this Property has changed, false otherwise + */ public boolean hasChanged(){ return changed; } void resetChangedState(){ changed = false; } - public void set(String value) + /** + * Sets the value of this Property to the provided String value. + */ + public Property setValue(String value) { this.value = value; changed = true; + return this; + } + + public void set(String value) + { + this.setValue(value); + } + + /** + * Sets the values of this Property to the provided String[] values. + */ + public Property setValues(String[] values) + { + this.values = Arrays.copyOf(values, values.length); + changed = true; + return this; } public void set(String[] values) { - this.values = values; - changed = true; + this.setValues(values); } + /** + * Sets the value of this Property to the provided int value. + */ + public Property setValue(int value) + { + setValue(Integer.toString(value)); + return this; + } + + /** + * Sets the value of this Property to the provided boolean value. + */ + public Property setValue(boolean value) + { + setValue(Boolean.toString(value)); + return this; + } + + /** + * Sets the value of this Property to the provided double value. + */ + public Property setValue(double value) + { + setValue(Double.toString(value)); + return this; + } + + /** + * Sets the values of this Property to the provided boolean[] values. + */ + public Property setValues(boolean[] values) + { + this.values = new String[values.length]; + for (int i = 0; i < values.length; i++) + this.values[i] = String.valueOf(values[i]); + changed = true; + return this; + } + + public void set(boolean[] values) + { + this.setValues(values); + } + + /** + * Sets the values of this Property to the provided int[] values. + */ + public Property setValues(int[] values) + { + this.values = new String[values.length]; + for (int i = 0; i < values.length; i++) + this.values[i] = String.valueOf(values[i]); + changed = true; + return this; + } + + public void set(int[] values) + { + this.setValues(values); + } + + /** + * Sets the values of this Property to the provided double[] values. + */ + public Property setValues(double[] values) + { + this.values = new String[values.length]; + for (int i = 0; i < values.length; i++) + this.values[i] = String.valueOf(values[i]); + changed = true; + return this; + } + + public void set(double[] values) + { + this.setValues(values); + } public void set(int value){ set(Integer.toString(value)); } public void set(boolean value){ set(Boolean.toString(value)); } public void set(double value){ set(Double.toString(value)); } diff --git a/src/main/resources/assets/fml/lang/en_US.lang b/src/main/resources/assets/fml/lang/en_US.lang new file mode 100644 index 0000000..2274c18 --- /dev/null +++ b/src/main/resources/assets/fml/lang/en_US.lang @@ -0,0 +1,78 @@ +fml.config.sample.basicDouble.tooltip=A double property with no defined bounds. +fml.config.sample.basicDouble=Unbounded Double +fml.config.sample.basicInteger.tooltip=An integer property with no defined bounds. +fml.config.sample.basicInteger=Unbounded Integer +fml.config.sample.basicString.tooltip=A basic string property. The user can enter anything in this textbox. +fml.config.sample.basicString=Basic String +fml.config.sample.booleanList.tooltip=A boolean list with no max length and can be lengthened or shortened. +fml.config.sample.booleanList=Basic Boolean List +fml.config.sample.booleanListFixed.tooltip=A boolean list that has a fixed length of 8. +fml.config.sample.booleanListFixed=Fixed Length Boolean List +fml.config.sample.booleanListMax.tooltip=A boolean list that has a max length of 10. +fml.config.sample.booleanListMax=Max Length Boolean List +fml.config.sample.boundedDouble.tooltip=A double property with defined bounds. +fml.config.sample.boundedDouble=Bounded Double +fml.config.sample.boundedInteger.tooltip=An integer property with defined bounds. +fml.config.sample.boundedInteger=Bounded Integer +fml.config.sample.chatColorPicker.tooltip=A special property that allows the user to select a font color code. +fml.config.sample.chatColorPicker=Chat Color Picker +fml.config.sample.ctgy.lists.tooltip=Provides a variety of array-property controls to play with. +fml.config.sample.ctgy.lists=Sample Array Controls +fml.config.sample.ctgy.numbers.tooltip=Provides a listing of the numeric controls for toying with. +fml.config.sample.ctgy.numbers=Sample Numeric Controls +fml.config.sample.ctgy.strings.tooltip=Provides a listing of the different String-type controls to play with. +fml.config.sample.ctgy.strings=Sample String Controls +fml.config.sample.cycleString.tooltip=A string property that has a defined list of valid values that can be cycled through. +fml.config.sample.cycleString=Cycle Value String +fml.config.sample.doubleList.tooltip=A double list with no max length and can be lengthened or shortened. +fml.config.sample.doubleList=Basic Double List +fml.config.sample.doubleListBounded.tooltip=A double list with specific value bounds. +fml.config.sample.doubleListBounded=Double List with Value Bounds +fml.config.sample.doubleListFixed.tooltip=A double list that has a fixed length of 10. +fml.config.sample.doubleListFixed=Fixed Length Double List +fml.config.sample.doubleListMax.tooltip=A double list that has a max length of 15. +fml.config.sample.doubleListMax=Max Length Double List +fml.config.sample.imABoolean.tooltip=Yep, that's pretty much it... I'm a Boolean control. +fml.config.sample.imABoolean=I'm a Boolean +fml.config.sample.imADouble.tooltip=There are two of me. Or maybe I work for both the CIA and the KGB. Not sure. +fml.config.sample.imADouble=I'm a Double +fml.config.sample.imAString.tooltip=Aah. Now, I understand you want us to advertise your washing powder. <*String.*> String, washing powder, what's the difference? We can sell anything. <*Good. Well I have this large quantity of string, 122,000 miles of it to be exact, which I inherited, and I thought if I advertised it...*> Of course! A national campaign. Useful stuff, string, no trouble there. <*Ah, but there's a snag, you see. Due to bad planning, the 122,000 miles is in three inch lengths. So it's not very useful.*> Well, that's our selling point! SIMPSON'S INDIVIDUAL STRINGETTES! THE NOW STRING! READY CUT, EASY TO HANDLE, SIMPSON'S INDIVIDUAL EMPEROR STRINGETTES - JUST THE RIGHT LENGTH! +fml.config.sample.imAString=I'm a String +fml.config.sample.imAnInteger.tooltip=Did I say that I'm an Integer? Oh, I see. +fml.config.sample.imAnInteger=I'm an Integer +fml.config.sample.integerList.tooltip=An integer list with no max length and can be lengthened or shortened. +fml.config.sample.integerList=Basic Integer List +fml.config.sample.integerListBounded.tooltip=An integer list with specific value bounds. +fml.config.sample.integerListBounded=Integer List with Value Bounds +fml.config.sample.integerListFixed.tooltip=An integer list that has a fixed length of 10. +fml.config.sample.integerListFixed=Fixed Length Integer List +fml.config.sample.integerListMax.tooltip=An integer list that has a max length of 15. +fml.config.sample.integerListMax=Max Length Integer List +fml.config.sample.modIDSelector.tooltip=A special property that allows the user to select a mod ID from a list of all installed mods. There are many possibilities for how this could be used with a little custom code: selecting biomes, selecting entities, selecting items, selecting blocks, etc. +fml.config.sample.modIDSelector=Mod ID Selector +fml.config.sample.patternString.tooltip=A string property that is validated using a Pattern object. +fml.config.sample.patternString=Pattern Validated String +fml.config.sample.stringList.tooltip=A basic string list with no validation. +fml.config.sample.stringList=Basic String List +fml.config.sample.stringListFixed.tooltip=A string list that has a fixed length of 7. +fml.config.sample.stringListFixed=Fixed Length String List +fml.config.sample.stringListMax.tooltip=A string list that has a max length of 15. +fml.config.sample.stringListMax=Max Length String List +fml.config.sample.stringListPattern.tooltip=A string list that validates each value using a Pattern object. +fml.config.sample.stringListPattern=Pattern Validated String List +fml.config.sample.title=This is for playing with the Config GUI behavior. Changes are not saved. + +fml.configgui.applyGlobally=Apply globally +fml.configgui.confirmRestartMessage=I Understand +fml.configgui.gameRestartRequired=One or more changed settings requires that Minecraft be restarted before taking effect. +fml.configgui.gameRestartTitle=Minecraft Restart Required +fml.configgui.sampletext=Sample Text +fml.configgui.tooltip.addNewEntryAbove=Add New Entry Above +fml.configgui.tooltip.applyGlobally=Apply Undo Changes or Reset to Default globally. +fml.configgui.tooltip.removeEntry=Remove Entry +fml.configgui.tooltip.resetAll=Reset All to Default. If the Apply globally checkbox is checked values on child screens will also be reset. +fml.configgui.tooltip.resetToDefault=Reset to Default +fml.configgui.tooltip.undoAll=Undo All Changes. If the Apply globally checkbox is checked changes to child screens will also be undone. +fml.configgui.tooltip.undoChanges=Undo Changes +fml.configgui.tooltip.default=[default: %s] +fml.configgui.tooltip.defaultNumeric=[range: %s ~ %s, default: %s] diff --git a/src/main/resources/assets/forge/lang/en_US.lang b/src/main/resources/assets/forge/lang/en_US.lang index 6a02f70..3d2e685 100644 --- a/src/main/resources/assets/forge/lang/en_US.lang +++ b/src/main/resources/assets/forge/lang/en_US.lang @@ -7,4 +7,49 @@ forge.client.shutdown.internal=Shutting down internal server... forge.update.newversion=New Forge version available: %s forge.update.beta.1=%sWARNING: %sForge Beta -forge.update.beta.2=Major issues may arise, verify before reporting. \ No newline at end of file +forge.update.beta.2=Major issues may arise, verify before reporting. + +forge.configgui.forgeConfigTitle=Minecraft Forge Configuration +forge.configgui.ctgy.forgeGeneralConfig.tooltip=This is where you can edit the settings contained in forge.cfg. +forge.configgui.ctgy.forgeGeneralConfig=General Settings +forge.configgui.ctgy.forgeChunkLoadingConfig.tooltip=This is where you can edit the settings contained in forgeChunkLoading.cfg. +forge.configgui.ctgy.forgeChunkLoadingConfig=Forge Chunk Loader Default Settings +forge.configgui.ctgy.forgeChunkLoadingModConfig.tooltip=This is where you can define mod-specific override settings that will be used instead of the defaults. +forge.configgui.ctgy.forgeChunkLoadingModConfig=Mod Overrides +forge.configgui.ctgy.forgeChunkLoadingAddModConfig.tooltip=Allows you to define mod-specific settings that will override the defaults. A value of zero in either entry effectively disables any chunkloading capabilities for that mod. +forge.configgui.ctgy.forgeChunkLoadingAddModConfig=+ Add New Mod Override + +forge.configgui.biomeSkyBlendRange.tooltip=Control the range of sky blending for colored skies in biomes. +forge.configgui.biomeSkyBlendRange=Biome Sky Blend Range +forge.configgui.clumpingThreshold.tooltip=Controls the number threshold at which Packet51 is preferred over Packet52. +forge.configgui.clumpingThreshold=Packet Clumping Threshold +forge.configgui.disableVersionCheck.tooltip=Set to true to disable Forge's version check mechanics. Forge queries a small json file on our server for version information. For more details see the ForgeVersion class in our github. +forge.configgui.disableVersionCheck=Disable Forge Version Check +forge.configgui.enableGlobalConfig=Enable Global Config +forge.configgui.forceDuplicateFluidBlockCrash.tooltip=Set this to true to force a crash if more than one block attempts to link back to the same Fluid. +forge.configgui.forceDuplicateFluidBlockCrash=Force Dupe Fluid Block Crash +forge.configgui.fullBoundingBoxLadders.tooltip=Set this to true to check the entire entity's collision bounding box for ladders instead of just the block they are in. Causes noticeable differences in mechanics so default is vanilla behavior. +forge.configgui.fullBoundingBoxLadders=Full Bounding Box Ladders +forge.configgui.removeErroringEntities.tooltip=Set this to true to remove any Entity that throws an error in its update method instead of closing the server and reporting a crash log. BE WARNED THIS COULD SCREW UP EVERYTHING USE SPARINGLY WE ARE NOT RESPONSIBLE FOR DAMAGES. +forge.configgui.removeErroringEntities=Remove Erroring Entities +forge.configgui.removeErroringTileEntities.tooltip=Set this to true to remove any TileEntity that throws an error in its update method instead of closing the server and reporting a crash log. BE WARNED THIS COULD SCREW UP EVERYTHING USE SPARINGLY WE ARE NOT RESPONSIBLE FOR DAMAGES. +forge.configgui.removeErroringTileEntities=Remove Erroring Tile Entities +forge.configgui.sortRecipies.tooltip=Set to true to enable the post initialization sorting of crafting recipes using Forge's sorter. May cause desyncing on conflicting recipies. MUST RESTART MINECRAFT IF CHANGED FROM THE CONFIG GUI. +forge.configgui.sortRecipies=Sort Recipes +forge.configgui.zombieBabyChance.tooltip=Chance that a zombie (or subclass) is a baby. Allows changing the zombie spawning mechanic. +forge.configgui.zombieBabyChance=Zombie Baby Chance +forge.configgui.zombieBaseSummonChance.tooltip=Base zombie summoning spawn chance. Allows changing the bonus zombie summoning mechanic. +forge.configgui.zombieBaseSummonChance=Zombie Summon Chance + +forge.configgui.modID.tooltip=The mod ID that you want to define override settings for. +forge.configgui.modID=Mod ID +forge.configgui.dormantChunkCacheSize.tooltip=Unloaded chunks can first be kept in a dormant cache for quicker loading times. Specify the size (in chunks) of that cache here. +forge.configgui.dormantChunkCacheSize=Dormant Chunk Cache Size +forge.configgui.enableModOverrides.tooltip=Enable this setting to allow custom per-mod settings to be defined. +forge.configgui.enableModOverrides=Enable Mod Overrides +forge.configgui.maximumChunksPerTicket.tooltip=This is the maximum number of chunks a single ticket can force. +forge.configgui.maximumChunksPerTicket=Chunks Per Ticket Limit +forge.configgui.maximumTicketCount.tooltip=This is the number of chunk loading requests a mod is allowed to make. +forge.configgui.maximumTicketCount=Mod Ticket Limit +forge.configgui.playerTicketCount.tooltip=The number of tickets a player can be assigned instead of a mod. This is shared across all mods and it is up to the mods to use it. +forge.configgui.playerTicketCount=Player Ticket Limit diff --git a/src/main/resources/fml_at.cfg b/src/main/resources/fml_at.cfg index 8b54a7f..3b2642b 100644 --- a/src/main/resources/fml_at.cfg +++ b/src/main/resources/fml_at.cfg @@ -85,3 +85,21 @@ protected net.minecraft.util.ObjectIntIdentityMap field_148748_b # internal index list protected-f net.minecraft.util.RegistryNamespaced field_148759_a # identitymap protected net.minecraft.util.RegistryNamespaced func_148755_c(Ljava/lang/String;)Ljava/lang/String; # prefix if necessary + +# GuiButton +public net.minecraft.client.gui.GuiButton field_146120_f # width - needed for config GUI stuff +public net.minecraft.client.gui.GuiButton field_146121_g # height - needed for config GUI stuff +# GuiTextField +public-f net.minecraft.client.gui.GuiTextField field_146209_f # xPosition - needed for config GUI stuff +public-f net.minecraft.client.gui.GuiTextField field_146210_g # yPosition - needed for config GUI stuff +public-f net.minecraft.client.gui.GuiTextField field_146218_h # width - needed for config GUI stuff +public-f net.minecraft.client.gui.GuiTextField field_146219_i # height - needed for config GUI stuff +# GuiSlot +public net.minecraft.client.gui.GuiSlot field_148149_f # slotHeight - needed for config GUI stuff +public net.minecraft.client.gui.GuiSlot field_148151_d # right - needed for config GUI stuff +public net.minecraft.client.gui.GuiSlot field_148152_e # left - needed for config GUI stuff +public net.minecraft.client.gui.GuiSlot field_148153_b # top - needed for config GUI stuff +public net.minecraft.client.gui.GuiSlot field_148154_c # bottom - needed for config GUI stuff +public net.minecraft.client.gui.GuiSlot field_148155_a # width - needed for config GUI stuff +public net.minecraft.client.gui.GuiSlot field_148158_l # height - needed for config GUI stuff +public net.minecraft.client.gui.GuiSlot field_148160_j # headerPadding - needed for config GUI stuff \ No newline at end of file diff --git a/src/main/resources/fmlversion.properties b/src/main/resources/fmlversion.properties index 19de82e..b874346 100644 --- a/src/main/resources/fmlversion.properties +++ b/src/main/resources/fmlversion.properties @@ -1,6 +1,6 @@ fmlbuild.major.number=7 fmlbuild.minor.number=2 -fmlbuild.revision.number=214 -fmlbuild.build.number=35 +fmlbuild.revision.number=217 +fmlbuild.build.number=37 fmlbuild.mcversion=1.7.2 fmlbuild.mcpversion=9.03