Adding config GUIs to the @Config based configuration System (#3735)
Lots of internal API that modders should not touch. See test mods for example usages.
This commit is contained in:
parent
72937c90be
commit
25497d310b
20 changed files with 1665 additions and 439 deletions
|
@ -102,9 +102,16 @@ public class ForgeGuiFactory implements IModGuiFactory
|
|||
{
|
||||
@Override
|
||||
public void initialize(Minecraft minecraftInstance) {}
|
||||
|
||||
|
||||
@Override
|
||||
public Class<? extends GuiScreen> mainConfigGuiClass() { return ForgeConfigGui.class; }
|
||||
public boolean hasConfigGui() { return true; }
|
||||
|
||||
@Override
|
||||
public GuiScreen createConfigGui(GuiScreen parent) { return new ForgeConfigGui(parent); }
|
||||
|
||||
@Override
|
||||
public Class<? extends GuiScreen> mainConfigGuiClass() { return null; }
|
||||
|
||||
@Override
|
||||
public Set<RuntimeOptionCategoryElement> runtimeGuiCategories() { return null; }
|
||||
|
|
|
@ -428,7 +428,7 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC
|
|||
|
||||
NetworkRegistry.INSTANCE.register(this, this.getClass(), "*", evt.getASMHarvestedData());
|
||||
ForgeNetworkHandler.registerChannel(this, evt.getSide());
|
||||
ConfigManager.load(this.getModId(), Config.Type.INSTANCE);
|
||||
ConfigManager.sync(this.getModId(), Config.Type.INSTANCE);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
|
|
|
@ -66,7 +66,7 @@ public @interface Config
|
|||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
public @interface LangKey
|
||||
{
|
||||
String value();
|
||||
|
@ -101,4 +101,14 @@ public @interface Config
|
|||
{
|
||||
String value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
public @interface RequiresMcRestart
|
||||
{}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
public @interface RequiresWorldRestart
|
||||
{}
|
||||
}
|
||||
|
|
|
@ -348,6 +348,8 @@ public class ConfigCategory implements Map<String, Property>
|
|||
char type = prop.getType().getID();
|
||||
write(out, pad1, String.valueOf(type), ":", propName, "=", prop.getString());
|
||||
}
|
||||
|
||||
prop.resetChangedState();
|
||||
}
|
||||
|
||||
if (children.size() > 0)
|
||||
|
|
|
@ -27,9 +27,14 @@ package net.minecraftforge.common.config;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import net.minecraftforge.fml.client.config.ConfigGuiType;
|
||||
import net.minecraftforge.fml.client.config.DummyConfigElement.DummyCategoryElement;
|
||||
import net.minecraftforge.fml.client.config.GuiConfigEntries.IConfigEntry;
|
||||
import net.minecraftforge.fml.client.config.GuiEditArrayEntries.IArrayEntry;
|
||||
import net.minecraftforge.fml.client.config.IConfigElement;
|
||||
|
@ -360,4 +365,57 @@ public class ConfigElement implements IConfigElement
|
|||
{
|
||||
return isProperty ? prop.getMaxValue() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a ConfigElement derived from the annotation-based config system
|
||||
* @param configClass the class which contains the configuration
|
||||
* @return A ConfigElement based on the described category.
|
||||
*/
|
||||
public static IConfigElement from(Class<?> configClass)
|
||||
{
|
||||
Config annotation = configClass.getAnnotation(Config.class);
|
||||
if (annotation == null)
|
||||
throw new RuntimeException(String.format("The class '%s' has no @Config annotation!", configClass.getName()));
|
||||
|
||||
Configuration config = ConfigManager.getConfiguration(annotation.modid(), annotation.name());
|
||||
if (config == null)
|
||||
{
|
||||
String error = String.format("The configuration '%s' of mod '%s' isn't loaded with the ConfigManager!", annotation.name(), annotation.modid());
|
||||
throw new RuntimeException(error);
|
||||
}
|
||||
|
||||
String name = Strings.isNullOrEmpty(annotation.name()) ? annotation.modid() : annotation.name();
|
||||
String langKey = name;
|
||||
Config.LangKey langKeyAnnotation = configClass.getAnnotation(Config.LangKey.class);
|
||||
if (langKeyAnnotation != null)
|
||||
{
|
||||
langKey = langKeyAnnotation.value();
|
||||
}
|
||||
|
||||
if (annotation.category().isEmpty())
|
||||
{
|
||||
List<IConfigElement> elements = Lists.newArrayList();
|
||||
Set<String> catNames = config.getCategoryNames();
|
||||
for (String catName : catNames)
|
||||
{
|
||||
if (catName.isEmpty())
|
||||
continue;
|
||||
ConfigCategory category = config.getCategory(catName);
|
||||
DummyCategoryElement element = new DummyCategoryElement(category.getName(), category.getLanguagekey(), new ConfigElement(category).getChildElements());
|
||||
element.setRequiresMcRestart(category.requiresMcRestart());
|
||||
element.setRequiresWorldRestart(category.requiresWorldRestart());
|
||||
elements.add(element);
|
||||
}
|
||||
|
||||
return new DummyCategoryElement(name, langKey, elements);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConfigCategory category = config.getCategory(annotation.category());
|
||||
DummyCategoryElement element = new DummyCategoryElement(name, langKey, new ConfigElement(category).getChildElements());
|
||||
element.setRequiresMcRestart(category.requiresMcRestart());
|
||||
element.setRequiresWorldRestart(category.requiresWorldRestart());
|
||||
return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,27 +22,23 @@ package net.minecraftforge.common.config;
|
|||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import net.minecraftforge.common.config.Config.Comment;
|
||||
import net.minecraftforge.common.config.Config.LangKey;
|
||||
import net.minecraftforge.common.config.Config.Name;
|
||||
import net.minecraftforge.common.config.Config.RangeDouble;
|
||||
import net.minecraftforge.common.config.Config.RangeInt;
|
||||
import net.minecraftforge.fml.common.FMLLog;
|
||||
import net.minecraftforge.fml.common.Loader;
|
||||
import net.minecraftforge.fml.common.LoaderException;
|
||||
|
@ -53,9 +49,9 @@ import net.minecraftforge.fml.common.discovery.asm.ModAnnotation.EnumHolder;
|
|||
public class ConfigManager
|
||||
{
|
||||
private static Map<String, Multimap<Config.Type, ASMData>> asm_data = Maps.newHashMap();
|
||||
private static Map<Class<?>, ITypeAdapter> ADAPTERS = Maps.newHashMap();
|
||||
private static Map<Class<?>, ITypeAdapter.Map> MAP_ADAPTERS = Maps.newHashMap();
|
||||
static Map<Class<?>, ITypeAdapter> ADAPTERS = Maps.newHashMap();
|
||||
private static Map<String, Configuration> CONFIGS = Maps.newHashMap();
|
||||
private static Map<String, Set<Class<?>>> MOD_CONFIG_CLASSES = Maps.newHashMap();
|
||||
|
||||
static
|
||||
{
|
||||
|
@ -93,8 +89,6 @@ public class ConfigManager
|
|||
private static void register(Class<?> cls, ITypeAdapter adpt)
|
||||
{
|
||||
ADAPTERS.put(cls, adpt);
|
||||
if (adpt instanceof ITypeAdapter.Map)
|
||||
MAP_ADAPTERS.put(cls, (ITypeAdapter.Map)adpt);
|
||||
}
|
||||
|
||||
public static void loadData(ASMDataTable data)
|
||||
|
@ -116,8 +110,34 @@ public class ConfigManager
|
|||
map.put(type, target);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Bounces to sync().
|
||||
* TODO: remove
|
||||
*/
|
||||
public static void load(String modid, Config.Type type)
|
||||
{
|
||||
sync(modid, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes configuration data between the file on disk, the {@code Configuration} object and the annotated
|
||||
* mod classes containing the configuration variables.
|
||||
*
|
||||
* When first called, this method will try to load the configuration from disk. If this fails, because the file
|
||||
* does not exist, it will be created with default values derived from the mods config classes variable default values
|
||||
* and comments and ranges, as well as configuration names based on the appropriate annotations found in {@code @Config}.
|
||||
*
|
||||
* Note, that this method is being called by the {@link FMLModContaier}, so the mod needn't call it in init().
|
||||
*
|
||||
* If this method is called after the initial load, it will check whether the values in the Configuration object differ
|
||||
* from the values in the corresponding variables. If they differ, it will either overwrite the variables if the Configuration
|
||||
* object is marked as changed (e.g. if it was changed with the ConfigGui) or otherwise overwrite the Configuration object's values.
|
||||
* It then proceeds to saving the changes to disk.
|
||||
* @param modid the mod's ID for which the configuration shall be loaded
|
||||
* @param type the configuration type, currently always {@code Config.Type.INSTANCE}
|
||||
*/
|
||||
public static void sync(String modid, Config.Type type)
|
||||
{
|
||||
FMLLog.fine("Attempting to inject @Config classes into %s for type %s", modid, type);
|
||||
ClassLoader mcl = Loader.instance().getModClassLoader();
|
||||
|
@ -132,6 +152,11 @@ public class ConfigManager
|
|||
try
|
||||
{
|
||||
Class<?> cls = Class.forName(targ.getClassName(), true, mcl);
|
||||
|
||||
if (MOD_CONFIG_CLASSES.get(modid) == null)
|
||||
MOD_CONFIG_CLASSES.put(modid, Sets.<Class<?>>newHashSet());
|
||||
MOD_CONFIG_CLASSES.get(modid).add(cls);
|
||||
|
||||
String name = (String)targ.getAnnotationInfo().get("name");
|
||||
if (name == null)
|
||||
name = modid;
|
||||
|
@ -141,15 +166,17 @@ public class ConfigManager
|
|||
|
||||
File file = new File(configDir, name + ".cfg");
|
||||
|
||||
boolean loading = false;
|
||||
Configuration cfg = CONFIGS.get(file.getAbsolutePath());
|
||||
if (cfg == null)
|
||||
{
|
||||
cfg = new Configuration(file);
|
||||
cfg.load();
|
||||
CONFIGS.put(file.getAbsolutePath(), cfg);
|
||||
loading = true;
|
||||
}
|
||||
|
||||
createConfig(cfg, cls, modid, type == Config.Type.INSTANCE, category);
|
||||
sync(cfg, cls, modid, category, loading, null);
|
||||
|
||||
cfg.save();
|
||||
|
||||
|
@ -161,160 +188,171 @@ public class ConfigManager
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Class<?>[] getModConfigClasses(String modid)
|
||||
{
|
||||
return MOD_CONFIG_CLASSES.get(modid).toArray(new Class<?>[0]);
|
||||
}
|
||||
|
||||
// =======================================================
|
||||
// INTERNAL
|
||||
// =======================================================
|
||||
private static void createConfig(Configuration cfg, Class<?> cls, String modid, boolean isStatic, String category)
|
||||
static Configuration getConfiguration(String modid, String name) {
|
||||
if (Strings.isNullOrEmpty(name))
|
||||
name = modid;
|
||||
File configDir = Loader.instance().getConfigDir();
|
||||
File configFile = new File(configDir, name + ".cfg");
|
||||
return CONFIGS.get(configFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
private static void sync(Configuration cfg, Class<?> cls, String modid, String category, boolean loading, Object instance)
|
||||
{
|
||||
for (Field f : cls.getDeclaredFields())
|
||||
{
|
||||
if (!Modifier.isPublic(f.getModifiers()))
|
||||
continue;
|
||||
if (Modifier.isStatic(f.getModifiers()) != isStatic)
|
||||
if (Modifier.isStatic(f.getModifiers()) != (instance == null))
|
||||
continue;
|
||||
|
||||
String comment = null;
|
||||
Comment ca = f.getAnnotation(Comment.class);
|
||||
if (ca != null)
|
||||
comment = NEW_LINE.join(ca.value());
|
||||
|
||||
createConfig(modid, category, cfg, f.getType(), f, null);
|
||||
}
|
||||
}
|
||||
String langKey = modid + "." + (category.isEmpty() ? "" : category + ".") + f.getName().toLowerCase(Locale.ENGLISH);
|
||||
LangKey la = f.getAnnotation(LangKey.class);
|
||||
if (la != null)
|
||||
langKey = la.value();
|
||||
|
||||
boolean requiresMcRestart = f.isAnnotationPresent(Config.RequiresMcRestart.class);
|
||||
boolean requiresWorldRestart = f.isAnnotationPresent(Config.RequiresWorldRestart.class);
|
||||
|
||||
private static final Joiner NEW_LINE = Joiner.on('\n');
|
||||
private static final Joiner PIPE = Joiner.on('|');
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private static void createConfig(String modid, String category, Configuration cfg, Class<?> ftype, Field f, Object instance)
|
||||
{
|
||||
Property prop = null;
|
||||
|
||||
String comment = null;
|
||||
Comment ca = f.getAnnotation(Comment.class);
|
||||
if (ca != null)
|
||||
comment = NEW_LINE.join(ca.value());
|
||||
|
||||
String langKey = modid + "." + category + "." + f.getName().toLowerCase(Locale.ENGLISH);
|
||||
LangKey la = f.getAnnotation(LangKey.class);
|
||||
if (la != null)
|
||||
langKey = la.value();
|
||||
|
||||
ITypeAdapter adapter = ADAPTERS.get(ftype);
|
||||
|
||||
if (adapter != null)
|
||||
{
|
||||
if (category.isEmpty())
|
||||
throw new RuntimeException("Can not specify a primitive field when the category is empty: " + f.getDeclaringClass() +"/" + f.getName());
|
||||
prop = adapter.getProp(cfg, category, f, instance, comment);
|
||||
set(instance, f, adapter.getValue(prop));
|
||||
}
|
||||
else if (ftype.getSuperclass() == Enum.class)
|
||||
{
|
||||
if (category.isEmpty())
|
||||
throw new RuntimeException("Can not specify a primitive field when the category is empty: " + f.getDeclaringClass() +"/" + f.getName());
|
||||
Enum enu = (Enum)get(instance, f);
|
||||
prop = cfg.get(category, getName(f), enu.name(), comment);
|
||||
prop.setValidationPattern(makePattern((Class<? extends Enum>)ftype));
|
||||
set(instance, f, Enum.valueOf((Class<? extends Enum>)ftype, prop.getString()));
|
||||
}
|
||||
else if (ftype == Map.class)
|
||||
{
|
||||
if (category.isEmpty())
|
||||
throw new RuntimeException("Can not specify a primitive field when the category is empty: " + f.getDeclaringClass() +"/" + f.getName());
|
||||
String sub = category + "." + getName(f).toLowerCase(Locale.ENGLISH);
|
||||
Map<String, Object> m = (Map<String, Object>)get(instance, f);
|
||||
ParameterizedType type = (ParameterizedType)f.getGenericType();
|
||||
Type mtype = type.getActualTypeArguments()[1];
|
||||
|
||||
cfg.getCategory(sub).setComment(comment);
|
||||
|
||||
for (Entry<String, Object> e : m.entrySet())
|
||||
if (FieldWrapper.hasWrapperFor(f)) //Access the field
|
||||
{
|
||||
ITypeAdapter.Map adpt = MAP_ADAPTERS.get(mtype);
|
||||
|
||||
if (adpt != null)
|
||||
if (Strings.isNullOrEmpty(category))
|
||||
throw new RuntimeException("An empty category may not contain anything but objects representing categories!");
|
||||
try
|
||||
{
|
||||
prop = adpt.getProp(cfg, sub, e.getKey(), e.getValue());
|
||||
IFieldWrapper wrapper = FieldWrapper.get(instance, f, category);
|
||||
ITypeAdapter adapt = wrapper.getTypeAdapter();
|
||||
Property.Type propType = adapt.getType();
|
||||
|
||||
for (String key : wrapper.getKeys())
|
||||
{
|
||||
String suffix = key.replaceFirst(wrapper.getCategory() + ".", "");
|
||||
|
||||
boolean existed = exists(cfg, wrapper.getCategory(), suffix);
|
||||
if (!existed || loading) //Creates keys in category specified by the wrapper if new ones are programaticaly added
|
||||
{
|
||||
Property property = property(cfg, wrapper.getCategory(), suffix, propType, adapt.isArrayAdapter());
|
||||
|
||||
adapt.setDefaultValue(property, wrapper.getValue(key));
|
||||
if (!existed)
|
||||
adapt.setValue(property, wrapper.getValue(key));
|
||||
else
|
||||
wrapper.setValue(key, adapt.getValue(property));
|
||||
}
|
||||
else //If the key is not new, sync according to shoudlReadFromVar()
|
||||
{
|
||||
Property property = property(cfg, wrapper.getCategory(), suffix, propType, adapt.isArrayAdapter());
|
||||
Object propVal = adapt.getValue(property);
|
||||
Object mapVal = wrapper.getValue(key);
|
||||
if (shouldReadFromVar(property, propVal, mapVal))
|
||||
adapt.setValue(property, mapVal);
|
||||
else
|
||||
wrapper.setValue(key, propVal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ConfigCategory confCat = cfg.getCategory(wrapper.getCategory());
|
||||
|
||||
for (Property property : confCat.getOrderedValues())//Are new keys in the Configuration object?
|
||||
{
|
||||
if (!wrapper.handlesKey(property.getName()))
|
||||
continue;
|
||||
|
||||
if (loading || !wrapper.hasKey(property.getName()))
|
||||
{
|
||||
Object value = wrapper.getTypeAdapter().getValue(property);
|
||||
wrapper.setValue(confCat.getName() + "." + property.getName(), value);
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) //Doing this after the loops. The wrapper should set cosmetic stuff.
|
||||
wrapper.setupConfiguration(cfg, comment, langKey, requiresMcRestart, requiresWorldRestart);
|
||||
|
||||
}
|
||||
else if (mtype instanceof Class && ((Class<?>)mtype).getSuperclass() == Enum.class)
|
||||
catch (Exception e) //If anything goes wrong, add the errored field and class.
|
||||
{
|
||||
prop = TypeAdapters.Str.getProp(cfg, sub, e.getKey(), ((Enum)e.getValue()).name());
|
||||
prop.setValidationPattern(makePattern((Class<? extends Enum>)mtype));
|
||||
String format = "Error syncing field '%s' of class '%s'!";
|
||||
String error = String.format(format, f.getName(), cls.getName());
|
||||
throw new RuntimeException(error, e);
|
||||
}
|
||||
else
|
||||
throw new RuntimeException("Unknown type in map! " + f.getDeclaringClass() + "/" + f.getName() + " " + mtype);
|
||||
|
||||
prop.setLanguageKey(langKey + "." + e.getKey().toLowerCase(Locale.ENGLISH));
|
||||
|
||||
}
|
||||
else if (f.getType().getSuperclass() != null && f.getType().getSuperclass().equals(Object.class)) //Descend the object tree
|
||||
{
|
||||
Object newInstance = null;
|
||||
try
|
||||
{
|
||||
newInstance = f.get(instance);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//This should never happen. Previous checks should eliminate this.
|
||||
Throwables.propagate(e);
|
||||
}
|
||||
|
||||
String sub = (category.isEmpty() ? "" : category + ".") + getName(f).toLowerCase(Locale.ENGLISH);
|
||||
ConfigCategory confCat = cfg.getCategory(sub);
|
||||
confCat.setComment(comment);
|
||||
confCat.setLanguageKey(langKey);
|
||||
confCat.setRequiresMcRestart(requiresMcRestart);
|
||||
confCat.setRequiresWorldRestart(requiresWorldRestart);
|
||||
|
||||
sync(cfg, f.getType(), modid, sub, loading, newInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
String format = "Can't handle field '%s' of class '%s': Unknown type.";
|
||||
String error = String.format(format, f.getName(), cls.getCanonicalName());
|
||||
throw new RuntimeException(error);
|
||||
}
|
||||
prop = null;
|
||||
}
|
||||
else if (ftype.getSuperclass() == Object.class) //Only support classes that are one level below Object.
|
||||
}
|
||||
|
||||
static final Joiner NEW_LINE = Joiner.on('\n');
|
||||
static final Joiner PIPE = Joiner.on('|');
|
||||
|
||||
private static Property property(Configuration cfg, String category, String property, Property.Type type, boolean isList)
|
||||
{
|
||||
Property prop = cfg.getCategory(category).get(property);
|
||||
if (prop == null)
|
||||
{
|
||||
String sub = (category.isEmpty() ? "" : category + ".") + getName(f).toLowerCase(Locale.ENGLISH);
|
||||
cfg.getCategory(sub).setComment(comment);
|
||||
Object sinst = get(instance, f);
|
||||
for (Field sf : ftype.getDeclaredFields())
|
||||
{
|
||||
if (!Modifier.isPublic(sf.getModifiers()))
|
||||
continue;
|
||||
|
||||
createConfig(modid, sub, cfg, sf.getType(), sf, sinst);
|
||||
}
|
||||
if (isList)
|
||||
prop = new Property(property, new String[0], type);
|
||||
else
|
||||
prop = new Property(property, (String)null, type);
|
||||
cfg.getCategory(category).put(property, prop);
|
||||
}
|
||||
// TODO Lists ? other stuff
|
||||
else
|
||||
throw new RuntimeException("Unknown type in config! " + f.getDeclaringClass() + "/" + f.getName() + " " + ftype);
|
||||
|
||||
|
||||
if (prop != null)
|
||||
return prop;
|
||||
}
|
||||
|
||||
private static boolean exists(Configuration cfg, String category, String property)
|
||||
{
|
||||
return cfg.hasCategory(category) && cfg.getCategory(category).containsKey(property);
|
||||
}
|
||||
|
||||
private static boolean shouldReadFromVar(Property property, Object propValue, Object fieldValue)
|
||||
{
|
||||
if (!propValue.equals(fieldValue))
|
||||
{
|
||||
prop.setLanguageKey(langKey);
|
||||
RangeInt ia = f.getAnnotation(RangeInt.class);
|
||||
if (ia != null)
|
||||
{
|
||||
prop.setMinValue(ia.min());
|
||||
prop.setMaxValue(ia.max());
|
||||
if (comment != null)
|
||||
prop.setComment(NEW_LINE.join(new String[]{comment, "Min: " + ia.min(), "Max: " + ia.max()}));
|
||||
else
|
||||
prop.setComment(NEW_LINE.join(new String[]{"Min: " + ia.min(), "Max: " + ia.max()}));
|
||||
}
|
||||
RangeDouble da = f.getAnnotation(RangeDouble.class);
|
||||
if (da != null)
|
||||
{
|
||||
prop.setMinValue(da.min());
|
||||
prop.setMaxValue(da.max());
|
||||
if (comment != null)
|
||||
prop.setComment(NEW_LINE.join(new String[]{comment, "Min: " + da.min(), "Max: " + da.max()}));
|
||||
else
|
||||
prop.setComment(NEW_LINE.join(new String[]{"Min: " + da.min(), "Max: " + da.max()}));
|
||||
}
|
||||
|
||||
//TODO List length values
|
||||
if (property.hasChanged())
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
private static void set(Object instance, Field f, Object v)
|
||||
{
|
||||
try {
|
||||
f.set(instance, v);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
private static Object get(Object instance, Field f)
|
||||
{
|
||||
try {
|
||||
return f.get(instance);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static Pattern makePattern(Class<? extends Enum> cls)
|
||||
{
|
||||
List<String> lst = Lists.newArrayList();
|
||||
for (Enum e : cls.getEnumConstants())
|
||||
lst.add(e.name());
|
||||
return Pattern.compile(PIPE.join(lst));
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String getName(Field f)
|
||||
|
|
396
src/main/java/net/minecraftforge/common/config/FieldWrapper.java
Normal file
396
src/main/java/net/minecraftforge/common/config/FieldWrapper.java
Normal file
|
@ -0,0 +1,396 @@
|
|||
package net.minecraftforge.common.config;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import net.minecraftforge.common.config.Config.RangeDouble;
|
||||
import net.minecraftforge.common.config.Config.RangeInt;
|
||||
|
||||
import static net.minecraftforge.common.config.ConfigManager.*;
|
||||
|
||||
public abstract class FieldWrapper implements IFieldWrapper
|
||||
{
|
||||
protected String category, name;
|
||||
|
||||
protected Field field;
|
||||
protected Object instance;
|
||||
|
||||
public FieldWrapper(String category, Field field, Object instance)
|
||||
{
|
||||
this.instance = instance;
|
||||
this.field = field;
|
||||
this.category = category;
|
||||
this.name = field.getName();
|
||||
|
||||
if (field.isAnnotationPresent(Config.Name.class))
|
||||
this.name = field.getAnnotation(Config.Name.class).value();
|
||||
|
||||
this.field.setAccessible(true); // Just in case
|
||||
}
|
||||
|
||||
public static IFieldWrapper get(Object instance, Field field, String category)
|
||||
{
|
||||
if (ADAPTERS.get(field.getType()) != null)
|
||||
return new PrimitiveWrapper(category, field, instance);
|
||||
else if (Enum.class.isAssignableFrom(field.getType()))
|
||||
return new EnumWrapper(category, field, instance);
|
||||
else if (Map.class.isAssignableFrom(field.getType()))
|
||||
return new MapWrapper(category, field, instance);
|
||||
else if (field.getType().getSuperclass().equals(Object.class))
|
||||
throw new RuntimeException("Objects should not be handled by field wrappers");
|
||||
else
|
||||
throw new IllegalArgumentException(String.format("Fields of type '%s' are not supported!", field.getType().getCanonicalName()));
|
||||
}
|
||||
|
||||
public static boolean hasWrapperFor(Field field)
|
||||
{
|
||||
if (ADAPTERS.get(field.getType()) != null)
|
||||
return true;
|
||||
else if (Enum.class.isAssignableFrom(field.getType()))
|
||||
return true;
|
||||
else if (Map.class.isAssignableFrom(field.getType()))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class MapWrapper extends FieldWrapper
|
||||
{
|
||||
private Map<String, Object> theMap = null;
|
||||
private Type mType;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private MapWrapper(String category, Field field, Object instance)
|
||||
{
|
||||
super(category, field, instance);
|
||||
|
||||
try
|
||||
{
|
||||
theMap = (Map<String, Object>) field.get(instance);
|
||||
}
|
||||
catch (ClassCastException cce)
|
||||
{
|
||||
throw new IllegalArgumentException(String.format("The map '%s' of class '%s' must have the key type String!", field.getName(),
|
||||
field.getDeclaringClass().getCanonicalName()), cce);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Throwables.propagate(e);
|
||||
}
|
||||
|
||||
ParameterizedType type = (ParameterizedType) field.getGenericType();
|
||||
mType = type.getActualTypeArguments()[1];
|
||||
|
||||
if (ADAPTERS.get(mType) == null && !Enum.class.isAssignableFrom((Class<?>) mType))
|
||||
throw new IllegalArgumentException(String.format("The map '%s' of class '%s' has target values which are neither primitive nor an enum!",
|
||||
field.getName(), field.getDeclaringClass().getCanonicalName()));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITypeAdapter getTypeAdapter()
|
||||
{
|
||||
ITypeAdapter adapter = ADAPTERS.get(mType);
|
||||
if (adapter == null && Enum.class.isAssignableFrom((Class<?>) mType))
|
||||
adapter = TypeAdapters.Str;
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getKeys()
|
||||
{
|
||||
Set<String> keys = theMap.keySet();
|
||||
String[] keyArray = new String[keys.size()];
|
||||
|
||||
Iterator<String> it = keys.iterator();
|
||||
for (int i = 0; i < keyArray.length; i++)
|
||||
{
|
||||
keyArray[i] = category + "." + name + "." + it.next();
|
||||
}
|
||||
|
||||
return keyArray;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(String key)
|
||||
{
|
||||
return theMap.get(key.replaceFirst(category + "." + name + ".", ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String key, Object value)
|
||||
{
|
||||
String suffix = key.replaceFirst(category + "." + name + ".", "");
|
||||
theMap.put(suffix, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasKey(String name)
|
||||
{
|
||||
return theMap.containsKey(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handlesKey(String name)
|
||||
{
|
||||
if (name == null)
|
||||
return false;
|
||||
return name.startsWith(category + "." + name + ".");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupConfiguration(Configuration cfg, String desc, String langKey, boolean reqMCRestart, boolean reqWorldRestart)
|
||||
{
|
||||
ConfigCategory confCat = cfg.getCategory(category);
|
||||
confCat.setComment(desc);
|
||||
confCat.setLanguageKey(langKey);
|
||||
confCat.setRequiresMcRestart(reqMCRestart);
|
||||
confCat.setRequiresWorldRestart(reqWorldRestart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory()
|
||||
{
|
||||
return category + "." + name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class EnumWrapper extends SingleValueFieldWrapper
|
||||
{
|
||||
|
||||
private EnumWrapper(String category, Field field, Object instance)
|
||||
{
|
||||
super(category, field, instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITypeAdapter getTypeAdapter()
|
||||
{
|
||||
return TypeAdapters.Str;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(String key)
|
||||
{
|
||||
if (!hasKey(key))
|
||||
throw new IllegalArgumentException("Unsupported Key!");
|
||||
|
||||
try
|
||||
{
|
||||
@SuppressWarnings("rawtypes")
|
||||
Enum enu = (Enum) field.get(instance);
|
||||
return enu.name();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Throwables.propagate(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String key, Object value)
|
||||
{
|
||||
if (!hasKey(key))
|
||||
throw new IllegalArgumentException("Unsupported Key!");
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
Enum enu = Enum.valueOf((Class<? extends Enum>) field.getType(), (String) value);
|
||||
try
|
||||
{
|
||||
field.set(instance, enu);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Throwables.propagate(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@Override
|
||||
public void setupConfiguration(Configuration cfg, String desc, String langKey, boolean reqMCRestart, boolean reqWorldRestart)
|
||||
{
|
||||
super.setupConfiguration(cfg, desc, langKey, reqMCRestart, reqWorldRestart);
|
||||
|
||||
Property prop = cfg.getCategory(this.category).get(this.name); // Will be setup in general by ConfigManager
|
||||
|
||||
List<String> lst = Lists.newArrayList();
|
||||
for (Enum e : ((Class<? extends Enum>) field.getType()).getEnumConstants())
|
||||
lst.add(e.name());
|
||||
|
||||
prop.setValidationPattern(Pattern.compile(PIPE.join(lst)));
|
||||
prop.setValidValues(lst.toArray(new String[0]));
|
||||
|
||||
String validValues = NEW_LINE.join(lst);
|
||||
|
||||
if (desc != null)
|
||||
prop.setComment(NEW_LINE.join(new String[] { desc, "Valid values:" }) + "\n" + validValues);
|
||||
else
|
||||
prop.setComment("Valid values:" + "\n" + validValues);
|
||||
}
|
||||
}
|
||||
|
||||
private static class PrimitiveWrapper extends SingleValueFieldWrapper
|
||||
{
|
||||
|
||||
private PrimitiveWrapper(String category, Field field, Object instance)
|
||||
{
|
||||
super(category, field, instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITypeAdapter getTypeAdapter()
|
||||
{
|
||||
return ADAPTERS.get(field.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(String key)
|
||||
{
|
||||
if (!hasKey(key))
|
||||
throw new IllegalArgumentException("Unknown key!");
|
||||
try
|
||||
{
|
||||
return field.get(instance);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Throwables.propagate(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String key, Object value)
|
||||
{
|
||||
if (!hasKey(key))
|
||||
throw new IllegalArgumentException("Unknown key: " + key);
|
||||
try
|
||||
{
|
||||
field.set(instance, value);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Throwables.propagate(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setupConfiguration(Configuration cfg, String desc, String langKey, boolean reqMCRestart, boolean reqWorldRestart)
|
||||
{
|
||||
super.setupConfiguration(cfg, desc, langKey, reqMCRestart, reqWorldRestart);
|
||||
|
||||
Property prop = cfg.getCategory(this.category).get(this.name);
|
||||
|
||||
RangeInt ia = field.getAnnotation(RangeInt.class);
|
||||
if (ia != null)
|
||||
{
|
||||
prop.setMinValue(ia.min());
|
||||
prop.setMaxValue(ia.max());
|
||||
if (desc != null)
|
||||
prop.setComment(NEW_LINE.join(new String[] { desc, "Min: " + ia.min(), "Max: " + ia.max() }));
|
||||
else
|
||||
prop.setComment(NEW_LINE.join(new String[] { "Min: " + ia.min(), "Max: " + ia.max() }));
|
||||
}
|
||||
|
||||
RangeDouble da = field.getAnnotation(RangeDouble.class);
|
||||
if (da != null)
|
||||
{
|
||||
prop.setMinValue(da.min());
|
||||
prop.setMaxValue(da.max());
|
||||
if (desc != null)
|
||||
prop.setComment(NEW_LINE.join(new String[] { desc, "Min: " + da.min(), "Max: " + da.max() }));
|
||||
else
|
||||
prop.setComment(NEW_LINE.join(new String[] { "Min: " + da.min(), "Max: " + da.max() }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class SingleValueFieldWrapper extends FieldWrapper
|
||||
{
|
||||
private SingleValueFieldWrapper(String category, Field field, Object instance)
|
||||
{
|
||||
super(category, field, instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getKeys()
|
||||
{
|
||||
return asArray(this.category + "." + this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasKey(String name)
|
||||
{
|
||||
return (this.category + "." + this.name).equals(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handlesKey(String name)
|
||||
{
|
||||
return hasKey(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupConfiguration(Configuration cfg, String desc, String langKey, boolean reqMCRestart, boolean reqWorldRestart)
|
||||
{
|
||||
Property prop = cfg.getCategory(this.category).get(this.name); // Will be setup in general by ConfigManager
|
||||
|
||||
prop.setComment(desc);
|
||||
prop.setLanguageKey(langKey);
|
||||
prop.setRequiresMcRestart(reqMCRestart);
|
||||
prop.setRequiresWorldRestart(reqWorldRestart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory()
|
||||
{
|
||||
return category;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static <T> T[] asArray(T... in)
|
||||
{
|
||||
return in;
|
||||
}
|
||||
|
||||
public static class BeanEntry<K, V> implements Entry<K, V>
|
||||
{
|
||||
private K key;
|
||||
private V value;
|
||||
|
||||
public BeanEntry(K key, V value)
|
||||
{
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K getKey()
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V setValue(V value)
|
||||
{
|
||||
throw new UnsupportedOperationException("This is a static bean.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package net.minecraftforge.common.config;
|
||||
|
||||
/**
|
||||
* The objects are expected to get their wrapped field, the owning class, instance and category string on initialization.
|
||||
*/
|
||||
public interface IFieldWrapper
|
||||
{
|
||||
|
||||
/**
|
||||
* @return The type adapter to serialize the values returned by getValue. Null if non-primitive.
|
||||
*/
|
||||
public ITypeAdapter getTypeAdapter();
|
||||
|
||||
/**
|
||||
* @param field the field about which to retrieve information
|
||||
* @param instance The instance whose field shall be queried.
|
||||
* @return a list of keys handled by this field
|
||||
*/
|
||||
public String[] getKeys();
|
||||
|
||||
public Object getValue(String key);
|
||||
|
||||
public void setValue(String key, Object value);
|
||||
|
||||
public boolean hasKey(String name);
|
||||
|
||||
public boolean handlesKey(String name);
|
||||
|
||||
public void setupConfiguration(Configuration cfg, String desc, String langKey, boolean reqMCRestart, boolean reqWorldRestart);
|
||||
|
||||
/**
|
||||
* @return the category name in which the entries should be saved.
|
||||
*/
|
||||
public String getCategory();
|
||||
}
|
|
@ -18,16 +18,17 @@
|
|||
*/
|
||||
package net.minecraftforge.common.config;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import net.minecraftforge.common.config.Property.Type;
|
||||
|
||||
interface ITypeAdapter
|
||||
{
|
||||
Property getProp(Configuration cfg, String category, Field field, Object instance, String comment);
|
||||
void setDefaultValue(Property property, Object value);
|
||||
|
||||
void setValue(Property property, Object value);
|
||||
|
||||
Object getValue(Property prop);
|
||||
|
||||
public interface Map extends ITypeAdapter
|
||||
{
|
||||
Property getProp(Configuration cfg, String category, String name, Object value);
|
||||
}
|
||||
Type getType();
|
||||
|
||||
boolean isArrayAdapter();
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,63 @@
|
|||
package net.minecraftforge.fml.client;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraftforge.fml.client.config.GuiConfig;
|
||||
import net.minecraftforge.fml.common.ModContainer;
|
||||
|
||||
public class DefaultGuiFactory implements IModGuiFactory
|
||||
{
|
||||
|
||||
protected String modid, title;
|
||||
protected Minecraft minecraft;
|
||||
|
||||
protected DefaultGuiFactory(String modid, String title)
|
||||
{
|
||||
this.modid = modid;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConfigGui()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(Minecraft minecraftInstance)
|
||||
{
|
||||
this.minecraft = minecraftInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GuiScreen createConfigGui(GuiScreen parentScreen)
|
||||
{
|
||||
return new GuiConfig(parentScreen, modid, title);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public Class<? extends GuiScreen> mainConfigGuiClass()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RuntimeOptionCategoryElement> runtimeGuiCategories()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeOptionGuiHandler getHandlerFor(RuntimeOptionCategoryElement element)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IModGuiFactory forMod(ModContainer mod)
|
||||
{
|
||||
return new DefaultGuiFactory(mod.getModId(), mod.getName());
|
||||
}
|
||||
}
|
|
@ -371,6 +371,7 @@ public class FMLClientHandler implements IFMLSidedHandler
|
|||
String className = mc.getGuiClassName();
|
||||
if (Strings.isNullOrEmpty(className))
|
||||
{
|
||||
guiFactories.put(mc, DefaultGuiFactory.forMod(mc));
|
||||
continue;
|
||||
}
|
||||
try
|
||||
|
|
|
@ -104,16 +104,29 @@ public class FMLConfigGuiFactory implements IModGuiFactory
|
|||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean hasConfigGui()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(Minecraft minecraftInstance)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public GuiScreen createConfigGui(GuiScreen parentScreen)
|
||||
{
|
||||
return new FMLConfigGuiScreen(parentScreen);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends GuiScreen> mainConfigGuiClass()
|
||||
{
|
||||
return FMLConfigGuiScreen.class;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final Set<RuntimeOptionCategoryElement> fmlCategories = ImmutableSet.of(new RuntimeOptionCategoryElement("HELP", "FML"));
|
||||
|
|
|
@ -282,7 +282,15 @@ public class GuiModList extends GuiScreen
|
|||
try
|
||||
{
|
||||
IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedMod);
|
||||
GuiScreen newScreen = guiFactory.mainConfigGuiClass().getConstructor(GuiScreen.class).newInstance(this);
|
||||
GuiScreen newScreen = null;
|
||||
try
|
||||
{
|
||||
newScreen = guiFactory.createConfigGui(this);
|
||||
}
|
||||
catch (AbstractMethodError error)
|
||||
{
|
||||
newScreen = guiFactory.mainConfigGuiClass().getConstructor(GuiScreen.class).newInstance(this);
|
||||
}
|
||||
this.mc.displayGuiScreen(newScreen);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -415,8 +423,18 @@ public class GuiModList extends GuiScreen
|
|||
|
||||
IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedMod);
|
||||
configModButton.visible = true;
|
||||
configModButton.enabled = guiFactory != null && guiFactory.mainConfigGuiClass() != null;
|
||||
|
||||
configModButton.enabled = false;
|
||||
if (guiFactory != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
configModButton.enabled = guiFactory.hasConfigGui();
|
||||
}
|
||||
catch(AbstractMethodError error)
|
||||
{
|
||||
configModButton.enabled = guiFactory.mainConfigGuiClass() != null;
|
||||
}
|
||||
}
|
||||
lines.add(selectedMod.getMetadata().name);
|
||||
lines.add(String.format("Version: %s (%s)", selectedMod.getDisplayVersion(), selectedMod.getVersion()));
|
||||
lines.add(String.format("Mod ID: '%s' Mod State: %s", selectedMod.getModId(), Loader.instance().getModState(selectedMod)));
|
||||
|
|
|
@ -27,6 +27,11 @@ import net.minecraft.client.gui.GuiScreen;
|
|||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* This is the interface you need to implement if you want to provide a customized config screen.
|
||||
* {@link DefaultGuiFactory} provides a default implementation of this interface and will be used
|
||||
* if the mod does not specify anything else.
|
||||
*/
|
||||
public interface IModGuiFactory {
|
||||
/**
|
||||
* Called when instantiated to initialize with the active minecraft instance.
|
||||
|
@ -34,6 +39,35 @@ public interface IModGuiFactory {
|
|||
* @param minecraftInstance the instance
|
||||
*/
|
||||
public void initialize(Minecraft minecraftInstance);
|
||||
|
||||
/**
|
||||
* If this method returns false, the config button in the mod list will be disabled
|
||||
* @return true if this object provides a config gui screen, false otherwise
|
||||
*/
|
||||
public boolean hasConfigGui();
|
||||
|
||||
/**
|
||||
* Return an initialized {@link GuiScreen}. This screen will be displayed
|
||||
* when the "config" button is pressed in the mod list. It will
|
||||
* have a single argument constructor - the "parent" screen, the same as all
|
||||
* Minecraft GUIs. The expected behaviour is that this screen will replace the
|
||||
* "mod list" screen completely, and will return to the mod list screen through
|
||||
* the parent link, once the appropriate action is taken from the config screen.
|
||||
*
|
||||
* This config GUI is anticipated to provide configuration to the mod in a friendly
|
||||
* visual way. It should not be abused to set internals such as IDs (they're gonna
|
||||
* keep disappearing anyway), but rather, interesting behaviours. This config GUI
|
||||
* is never run when a server game is running, and should be used to configure
|
||||
* desired behaviours that affect server state. Costs, mod game modes, stuff like that
|
||||
* can be changed here.
|
||||
*
|
||||
* @param parentScreen The screen to which must be returned when closing the
|
||||
* returned screen.
|
||||
* @return A class that will be instantiated on clicks on the config button
|
||||
* or null if no GUI is desired.
|
||||
*/
|
||||
public GuiScreen createConfigGui(GuiScreen parentScreen);
|
||||
|
||||
/**
|
||||
* Return the name of a class extending {@link GuiScreen}. This class will
|
||||
* be instantiated when the "config" button is pressed in the mod list. It will
|
||||
|
@ -52,12 +86,13 @@ public interface IModGuiFactory {
|
|||
* desired behaviours that affect server state. Costs, mod game modes, stuff like that
|
||||
* can be changed here.
|
||||
*
|
||||
* @deprecated The method {@link IModGuiFactory.maingConfigGui(GuiScreen} is the recommended method.
|
||||
* @return A class that will be instantiated on clicks on the config button
|
||||
* or null if no GUI is desired.
|
||||
*/
|
||||
@Deprecated
|
||||
public Class<? extends GuiScreen> mainConfigGuiClass();
|
||||
|
||||
|
||||
/**
|
||||
* Return a list of the "runtime" categories this mod wishes to populate with
|
||||
* GUI elements.
|
||||
|
|
|
@ -25,6 +25,7 @@ import static net.minecraftforge.fml.client.config.GuiUtils.UNDO_CHAR;
|
|||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
@ -33,6 +34,8 @@ import net.minecraft.client.gui.GuiScreen;
|
|||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.util.text.TextComponentString;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.common.config.ConfigElement;
|
||||
import net.minecraftforge.common.config.ConfigManager;
|
||||
import net.minecraftforge.fml.client.config.GuiConfigEntries.IConfigEntry;
|
||||
import net.minecraftforge.fml.client.event.ConfigChangedEvent;
|
||||
import net.minecraftforge.fml.client.event.ConfigChangedEvent.OnConfigChangedEvent;
|
||||
|
@ -80,6 +83,59 @@ public class GuiConfig extends GuiScreen
|
|||
protected HoverChecker undoHoverChecker;
|
||||
protected HoverChecker resetHoverChecker;
|
||||
protected HoverChecker checkBoxHoverChecker;
|
||||
|
||||
/**
|
||||
* This constructor handles the {@code @Config} configuration classes
|
||||
* @param parentScreen the parent GuiScreen object
|
||||
* @param mod the mod for which to create a screen
|
||||
*/
|
||||
public GuiConfig(GuiScreen parentScreen, String modid, String title)
|
||||
{
|
||||
this(parentScreen, modid, false, false, title, ConfigManager.getModConfigClasses(modid));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param parentScreen the parrent GuiScreen object
|
||||
* @param modID the mod ID for the mod whose config settings will be editted
|
||||
* @param allRequireWorldRestart whether all config elements on this screen require a world restart
|
||||
* @param allRequireMcRestart whether all config elements on this screen require a game restart
|
||||
* @param title the desired title for this screen. For consistency it is recommended that you pass the path of the config file being
|
||||
* edited.
|
||||
* @param configClasses an array of classes annotated with {@code @Config} providing the configuration
|
||||
*/
|
||||
public GuiConfig(GuiScreen parentScreen, String modID, boolean allRequireWorldRestart, boolean allRequireMcRestart, String title,
|
||||
Class<?>... configClasses)
|
||||
{
|
||||
this(parentScreen, collectConfigElements(configClasses), modID, null, allRequireWorldRestart, allRequireMcRestart, title, null);
|
||||
}
|
||||
|
||||
private static List<IConfigElement> collectConfigElements(Class<?>[] configClasses)
|
||||
{
|
||||
List<IConfigElement> toReturn;
|
||||
if(configClasses.length == 1)
|
||||
{
|
||||
toReturn = ConfigElement.from(configClasses[0]).getChildElements();
|
||||
}
|
||||
else
|
||||
{
|
||||
toReturn = new ArrayList<IConfigElement>();
|
||||
for(Class<?> clazz : configClasses)
|
||||
{
|
||||
toReturn.add(ConfigElement.from(clazz));
|
||||
}
|
||||
}
|
||||
toReturn.sort(new Comparator<IConfigElement>(){
|
||||
|
||||
@Override
|
||||
public int compare(IConfigElement e1, IConfigElement e2)
|
||||
{
|
||||
return I18n.format(e1.getLanguageKey()).compareTo(I18n.format(e2.getLanguageKey()));
|
||||
}
|
||||
|
||||
});
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* GuiConfig constructor that will use ConfigChangedEvent when editing is concluded. If a non-null value is passed for configID,
|
||||
|
@ -167,7 +223,25 @@ public class GuiConfig extends GuiScreen
|
|||
this.entryList = new GuiConfigEntries(this, mc);
|
||||
this.initEntries = new ArrayList<IConfigEntry>(entryList.listEntries);
|
||||
this.allRequireWorldRestart = allRequireWorldRestart;
|
||||
IF:if (!allRequireWorldRestart)
|
||||
{
|
||||
for (IConfigElement element : configElements)
|
||||
{
|
||||
if (!element.requiresWorldRestart());
|
||||
break IF;
|
||||
}
|
||||
allRequireWorldRestart = true;
|
||||
}
|
||||
this.allRequireMcRestart = allRequireMcRestart;
|
||||
IF:if (!allRequireMcRestart)
|
||||
{
|
||||
for (IConfigElement element : configElements)
|
||||
{
|
||||
if (!element.requiresMcRestart());
|
||||
break IF;
|
||||
}
|
||||
allRequireMcRestart = true;
|
||||
}
|
||||
this.modID = modID;
|
||||
this.configID = configID;
|
||||
this.isWorldRunning = mc.world != null;
|
||||
|
|
|
@ -617,7 +617,7 @@ public class FMLModContainer implements ModContainer
|
|||
}
|
||||
ProxyInjector.inject(this, event.getASMHarvestedData(), FMLCommonHandler.instance().getSide(), getLanguageAdapter());
|
||||
AutomaticEventSubscriber.inject(this, event.getASMHarvestedData(), FMLCommonHandler.instance().getSide());
|
||||
ConfigManager.load(this.getModId(), Config.Type.INSTANCE);
|
||||
ConfigManager.sync(this.getModId(), Config.Type.INSTANCE);
|
||||
|
||||
processFieldAnnotations(event.getASMHarvestedData());
|
||||
}
|
||||
|
|
|
@ -1,21 +1,54 @@
|
|||
package net.minecraftforge.debug;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.common.config.Config;
|
||||
import net.minecraftforge.common.config.ConfigManager;
|
||||
import net.minecraftforge.common.config.Config.*;
|
||||
import net.minecraftforge.fml.client.IModGuiFactory;
|
||||
import net.minecraftforge.fml.client.config.GuiConfig;
|
||||
import net.minecraftforge.fml.client.event.ConfigChangedEvent.OnConfigChangedEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
|
||||
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
|
||||
@Mod(modid = ConfigTest.MODID, name = "ConfigTest", version = "1.0", acceptableRemoteVersions = "*")
|
||||
public class ConfigTest
|
||||
{
|
||||
public static final String MODID = "config_test";
|
||||
|
||||
|
||||
@Mod.EventHandler
|
||||
public void preInit(FMLPreInitializationEvent event) {
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
public void init(FMLInitializationEvent event) {
|
||||
System.out.println("Old: " + CONFIG_TYPES.bool);
|
||||
CONFIG_TYPES.bool = !CONFIG_TYPES.bool;
|
||||
System.out.println("New: " + CONFIG_TYPES.bool);
|
||||
ConfigManager.sync(MODID, Type.INSTANCE);
|
||||
System.out.println("After sync: " + CONFIG_TYPES.bool);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onConfigChangedEvent(OnConfigChangedEvent event) {
|
||||
if (event.getModID().equals(MODID))
|
||||
{
|
||||
ConfigManager.sync(MODID, Type.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
@LangKey("config_test.config.types")
|
||||
@Config(modid = MODID, type = Type.INSTANCE, name = MODID + "_types")
|
||||
public static class CONFIG_TYPES
|
||||
{
|
||||
|
@ -58,6 +91,7 @@ public class ConfigTest
|
|||
public String HeyLook = "I'm Inside!";
|
||||
}
|
||||
}
|
||||
@LangKey("config_test.config.annotations")
|
||||
@Config(modid = MODID)
|
||||
public static class CONFIG_ANNOTATIONS
|
||||
{
|
||||
|
@ -79,6 +113,7 @@ public class ConfigTest
|
|||
public String HeyLook = "Go in!";
|
||||
}
|
||||
}
|
||||
@LangKey("config_test.config.subcats")
|
||||
@Config(modid = MODID, name = MODID + "_subcats", category = "")
|
||||
public static class CONFIG_SUBCATS
|
||||
{
|
||||
|
@ -92,11 +127,34 @@ public class ConfigTest
|
|||
public static class SubCat
|
||||
{
|
||||
@Name("i_say")
|
||||
public static String value;
|
||||
public String value;
|
||||
public SubCat(String value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@LangKey("config_test.config.maps")
|
||||
@Config(modid = MODID, name = MODID + "_map")
|
||||
public static class CONFIG_MAP
|
||||
{
|
||||
@Name("map")
|
||||
@RequiresMcRestart
|
||||
public static Map<String, Integer[]> theMap;
|
||||
|
||||
static
|
||||
{
|
||||
theMap = Maps.newHashMap();
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
Integer[] array = new Integer[6];
|
||||
for (int x = 0; x < array.length; x++)
|
||||
{
|
||||
array[x] = i + x;
|
||||
}
|
||||
theMap.put("" + i, array);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
4
src/test/resources/assets/config_test/lang/en_US.lang
Normal file
4
src/test/resources/assets/config_test/lang/en_US.lang
Normal file
|
@ -0,0 +1,4 @@
|
|||
config_test.config.types=Field Types
|
||||
config_test.config.annotations=Annotations
|
||||
config_test.config.subcats=Subcategories
|
||||
config_test.config.maps=Maps
|
16
src/test/resources/mcmod.info
Normal file
16
src/test/resources/mcmod.info
Normal file
|
@ -0,0 +1,16 @@
|
|||
[
|
||||
{
|
||||
"modid": "config_test",
|
||||
"name": "ConfigTest",
|
||||
"description": "Tests config",
|
||||
"version": "1.0",
|
||||
"mcversion": "",
|
||||
"url": "",
|
||||
"updateUrl": "",
|
||||
"authorList": [""],
|
||||
"credits": "",
|
||||
"logoFile": "",
|
||||
"screenshots": [],
|
||||
"dependencies": []
|
||||
}
|
||||
]
|
Loading…
Reference in a new issue