diff --git a/fml b/fml index 5d6dc5dce..7eb36a148 160000 --- a/fml +++ b/fml @@ -1 +1 @@ -Subproject commit 5d6dc5dce37e488188d6fc468c16e8a6183a3610 +Subproject commit 7eb36a1481aea9f68fa46bc199195769b27d904b 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 000000000..ceb658aad --- /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 f51db26ae..f822bcb71 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 @@ public class ForgeChunkManager 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 @@ public class ForgeChunkManager static void loadConfiguration() { + ticketConstraints.clear(); + chunkConstraints.clear(); for (String mod : config.getCategoryNames()) { if (mod.equals("Forge") || mod.equals("defaults")) @@ -924,51 +941,109 @@ public class ForgeChunkManager 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 @@ public class ForgeChunkManager 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 d11ca48b3..4528fd6f0 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.ForgeVersion.revisionVersion; 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 com.google.common.eventbus.Subscribe; 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.FMLModIdMappingEvent; 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 class ForgeModContainer extends DummyModContainer implements WorldAccessC 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 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC 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 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC } 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 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC //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 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC { MinecraftForge.EVENT_BUS.register(MinecraftForge.INTERNAL_HANDLER); ForgeChunkManager.captureConfig(evt.getModConfigurationDirectory()); + FMLCommonHandler.instance().bus().register(this); } @Subscribe @@ -200,7 +275,7 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC } @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 4ccaed5ac..9600a91a9 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.BufferedWriter; 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) { @@ -51,6 +69,11 @@ public class ConfigCategory implements Map return false; } + public String getName() + { + return name; + } + public String getQualifiedName() { return getQualifiedName(name, parent); @@ -76,11 +99,134 @@ public class ConfigCategory implements Map return ImmutableMap.copyOf(properties); } - public void setComment(String comment) + 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 ConfigCategory setComment(String comment) { this.comment = comment; + return this; } + 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,22 +257,21 @@ public class ConfigCategory implements Map 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, COMMENT_SEPARATOR, NEW_LINE); } - write(out, pad0, "####################", NEW_LINE); - if (!allowedProperties.matchesAllOf(name)) { name = '"' + name + '"'; @@ -134,13 +279,13 @@ public class ConfigCategory implements Map 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) { @@ -185,6 +330,9 @@ public class ConfigCategory implements Map } } + if (children.size() > 0) + out.newLine(); + for (ConfigCategory child : children) { child.write(out, indent + 1); @@ -232,6 +380,8 @@ public class ConfigCategory implements Map @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 +392,10 @@ public class ConfigCategory implements Map @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() @@ -260,12 +414,13 @@ public class ConfigCategory implements Map public Set getChildren(){ return ImmutableSet.copyOf(children); } - public void removeChild(ConfigCategory child) + public ConfigCategory removeChild(ConfigCategory child) { if (children.contains(child)) { children.remove(child); changed = true; } + return this; } } \ No newline at end of file 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 000000000..d35ee4411 --- /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 de7f5f5e6..272984c21 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.InputStreamReader; 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 java.util.regex.Pattern; 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 class Configuration 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 @@ public class Configuration private String fileName = null; public boolean isChild = false; private boolean changed = false; + private String definedConfigVersion = null; + private String loadedConfigVersion = null; static { @@ -73,6 +83,14 @@ public class Configuration * Create a configuration file for the file given in parameter. */ 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("/.", ""); @@ -85,149 +103,587 @@ public class Configuration 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 @@ public class Configuration 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 class Configuration } } - 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 @@ public class Configuration 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 @@ public class Configuration } } + /* **************************************************************************************************************** + * + * Other methods + * + *************************************************************************************************************** */ + public boolean hasCategory(String category) { return categories.get(category) != null; @@ -327,9 +803,13 @@ public class Configuration 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 @@ public class Configuration ArrayList tmpList = null; int lineNum = 0; String name = null; + loadedConfigVersion = null; while (true) { @@ -352,6 +833,8 @@ public class Configuration if (line == null) { + if (lineNum == 1) + loadedConfigVersion = definedConfigVersion; break; } @@ -376,6 +859,7 @@ public class Configuration 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 @@ public class Configuration } nameEnd = i; + isFirstNonWhitespaceCharOnLine = false; } else if (Character.isWhitespace(line.charAt(i))) { @@ -397,10 +882,14 @@ public class Configuration 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 @@ public class Configuration 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 @@ public class Configuration 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 @@ public class Configuration 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 @@ public class Configuration 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 @@ public class Configuration 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 @@ public class Configuration 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 class Configuration } } - 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 @@ public class Configuration { 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 eb5f6deaa..8c7e9a9a0 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 @@ public class Property 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 @@ public class Property 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) @@ -74,14 +122,514 @@ public class Property } 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. * @@ -92,15 +640,44 @@ public class Property return value; } + /** + * 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); + } } /** @@ -160,8 +737,26 @@ public class Property } } + /** + * 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() @@ -206,6 +801,24 @@ public class Property } } + /** + * 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; @@ -246,18 +859,19 @@ public class Property */ 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; } /** @@ -294,15 +908,16 @@ public class Property */ 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; } /** @@ -339,34 +954,46 @@ public class Property */ 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; } - public void setName(String name) + /** + * Sets the name/key for this Property. + * + * @param name a name + */ + public Property setName(String name) { this.name = name; + return this; } /** * 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 @@ -376,31 +1003,141 @@ public class Property 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; + 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/forge/lang/en_US.lang b/src/main/resources/assets/forge/lang/en_US.lang index 6a02f70d9..3d2e6855f 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.texture.preload.warning=Warning: Texture %s not preloaded, will cause rend 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