diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml
index 212ac4d6ed..80381e9ae7 100644
--- a/runelite-client/pom.xml
+++ b/runelite-client/pom.xml
@@ -92,6 +92,12 @@
4.12
test
+
+ org.mockito
+ mockito-all
+ 1.10.19
+ test
+
diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java
index 058c3605f6..9ce6c50be1 100644
--- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java
+++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java
@@ -42,6 +42,7 @@ import javax.imageio.ImageIO;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import net.runelite.api.Client;
+import net.runelite.client.config.ConfigManager;
import net.runelite.client.account.AccountSession;
import net.runelite.client.events.SessionClose;
import net.runelite.client.events.SessionOpen;
@@ -57,6 +58,7 @@ public class RuneLite
private static final Logger logger = LoggerFactory.getLogger(RuneLite.class);
public static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite");
+ public static final File PROFILES_DIR = new File(RUNELITE_DIR, "profiles");
public static final File SESSION_FILE = new File(RUNELITE_DIR, "session");
public static Image ICON;
@@ -75,6 +77,7 @@ public class RuneLite
private WSClient wsclient;
private AccountSession accountSession;
+ private ConfigManager configManager = new ConfigManager(eventBus);
static
{
@@ -99,6 +102,8 @@ public class RuneLite
parser.accepts("no-rs");
options = parser.parse(args);
+ PROFILES_DIR.mkdirs();
+
runelite = new RuneLite();
runelite.start();
}
@@ -109,6 +114,8 @@ public class RuneLite
setupTrayIcon();
+ configManager.load();
+
eventBus.register(menuManager);
pluginManager = new PluginManager(this);
@@ -210,6 +217,14 @@ public class RuneLite
accountSession = session;
+ if (session.getUsername() != null)
+ {
+ // Initialize config for new session
+ // If the session isn't logged in yet, don't switch to the new config
+ configManager = new ConfigManager(eventBus, session);
+ configManager.load();
+ }
+
eventBus.post(new SessionOpen());
}
@@ -230,6 +245,10 @@ public class RuneLite
accountSession = null; // No more account
+ // Restore config
+ configManager = new ConfigManager(eventBus);
+ configManager.load();
+
eventBus.post(new SessionClose());
}
@@ -293,6 +312,11 @@ public class RuneLite
return trayIcon;
}
+ public ConfigManager getConfigManager()
+ {
+ return configManager;
+ }
+
public AccountSession getAccountSession()
{
return accountSession;
diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigDescriptor.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigDescriptor.java
new file mode 100644
index 0000000000..34db13d2e9
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigDescriptor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2017, Adam
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.config;
+
+import java.util.Collection;
+
+public class ConfigDescriptor
+{
+ private final ConfigGroup group;
+ private final Collection items;
+
+ public ConfigDescriptor(ConfigGroup group, Collection items)
+ {
+ this.group = group;
+ this.items = items;
+ }
+
+ public ConfigGroup getGroup()
+ {
+ return group;
+ }
+
+ public Collection getItems()
+ {
+ return items;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigGroup.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigGroup.java
new file mode 100644
index 0000000000..434fd4ca9d
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigGroup.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2017, Adam
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.config;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ConfigGroup
+{
+ String keyName();
+
+ String name();
+
+ String description();
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigInvocationHandler.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigInvocationHandler.java
new file mode 100644
index 0000000000..9cea084db8
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigInvocationHandler.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2017, Adam
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.config;
+
+import com.google.common.base.Objects;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class ConfigInvocationHandler implements InvocationHandler
+{
+ private static final Logger logger = LoggerFactory.getLogger(ConfigInvocationHandler.class);
+
+ private final ConfigManager manager;
+
+ public ConfigInvocationHandler(ConfigManager manager)
+ {
+ this.manager = manager;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
+ {
+ Class> iface = proxy.getClass().getInterfaces()[0];
+
+ ConfigGroup group = iface.getAnnotation(ConfigGroup.class);
+ ConfigItem item = method.getAnnotation(ConfigItem.class);
+
+ if (group == null)
+ {
+ logger.warn("Configuration proxy class {} has no @ConfigGroup!", proxy.getClass());
+ return null;
+ }
+
+ if (item == null)
+ {
+ logger.warn("Configuration method {} has no @ConfigItem!", method);
+ return null;
+ }
+
+ if (args == null)
+ {
+ // Getting configuration item
+ String value = manager.getConfiguration(group.keyName(), item.keyName());
+
+ if (value == null)
+ {
+ if (method.isDefault())
+ {
+ return callDefaultMethod(proxy, method, args);
+ }
+
+ return null;
+ }
+
+ // Convert value to return type
+ Class> returnType = method.getReturnType();
+ if (returnType == value.getClass())
+ {
+ return value;
+ }
+
+ if (returnType == Boolean.class)
+ {
+ return Boolean.valueOf(value);
+ }
+
+ logger.warn("Unknown return type for configuration item {}.{}: {}", group.keyName(), item.keyName(), returnType);
+ return null;
+ }
+ else
+ {
+ // Setting a configuration value
+
+ if (args.length != 1)
+ {
+ throw new RuntimeException("Invalid number of arguents to configuration method");
+ }
+
+ Object newValue = args[0];
+
+ if (method.isDefault())
+ {
+ Object defaultValue = callDefaultMethod(proxy, method, args);
+
+ if (Objects.equal(newValue, defaultValue))
+ {
+ // Just unset if it goes back to the default
+ manager.unsetConfiguration(group.keyName(), item.keyName());
+ return null;
+ }
+ }
+
+ manager.setConfiguration(group.keyName(), item.keyName(), args[0].toString());
+ return null;
+ }
+ }
+
+ private Object callDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable
+ {
+ // Call the default method implementation - https://rmannibucau.wordpress.com/2014/03/27/java-8-default-interface-methods-and-jdk-dynamic-proxies/
+ Constructor constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
+ constructor.setAccessible(true);
+
+ Class> declaringClass = method.getDeclaringClass();
+ return constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE)
+ .unreflectSpecial(method, declaringClass)
+ .bindTo(proxy)
+ .invokeWithArguments(args);
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigItem.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigItem.java
new file mode 100644
index 0000000000..9897e91bbd
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigItem.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2017, Adam
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.config;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface ConfigItem
+{
+ String keyName();
+
+ String name();
+
+ String description();
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigItemDescriptor.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigItemDescriptor.java
new file mode 100644
index 0000000000..1607f66571
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigItemDescriptor.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2017, Adam
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.config;
+
+public class ConfigItemDescriptor
+{
+ private final ConfigItem item;
+ private final Class> type;
+
+ public ConfigItemDescriptor(ConfigItem item, Class> type)
+ {
+ this.item = item;
+ this.type = type;
+ }
+
+ public ConfigItem getItem()
+ {
+ return item;
+ }
+
+ public Class> getType()
+ {
+ return type;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java
new file mode 100644
index 0000000000..24a5ff2810
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2017, Adam
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.config;
+
+import com.google.common.eventbus.EventBus;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+import java.util.stream.Collectors;
+import net.runelite.client.RuneLite;
+import net.runelite.client.account.AccountSession;
+import net.runelite.client.events.ConfigChanged;
+import net.runelite.http.api.config.ConfigClient;
+import net.runelite.http.api.config.ConfigEntry;
+import net.runelite.http.api.config.Configuration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ConfigManager
+{
+ private static final Logger logger = LoggerFactory.getLogger(ConfigManager.class);
+
+ private static final String SETTINGS_FILE_NAME = "settings.properties";
+
+ private final EventBus eventBus;
+ private AccountSession session;
+ private ConfigClient client;
+
+ private final File propertiesFile;
+ private final ConfigInvocationHandler handler = new ConfigInvocationHandler(this);
+ private final Properties properties = new Properties();
+
+ public ConfigManager(EventBus eventBus)
+ {
+ this.eventBus = eventBus;
+ this.propertiesFile = getPropertiesFile();
+ }
+
+ public ConfigManager(EventBus eventBus, AccountSession session)
+ {
+ this.eventBus = eventBus;
+ this.session = session;
+ // if session username is null then dont..
+ this.client = new ConfigClient(session.getUuid());
+ this.propertiesFile = getPropertiesFile();
+ }
+
+ private File getPropertiesFile()
+ {
+ // Sessions that aren't logged in have no username
+ if (session == null || session.getUsername() == null)
+ {
+ return new File(RuneLite.RUNELITE_DIR, SETTINGS_FILE_NAME);
+ }
+ else
+ {
+ File profileDir = new File(RuneLite.PROFILES_DIR, session.getUsername().toLowerCase());
+ return new File(profileDir, SETTINGS_FILE_NAME);
+ }
+ }
+
+ public void load()
+ {
+ if (client == null)
+ {
+ loadFromFile();
+ return;
+ }
+
+ Configuration configuration;
+
+ try
+ {
+ configuration = client.get();
+ }
+ catch (IOException ex)
+ {
+ logger.debug("Unable to load configuration from client, using saved configuration from disk", ex);
+ loadFromFile();
+ return;
+ }
+
+ if (configuration.getConfig().isEmpty())
+ {
+ logger.debug("No configuration from client, using saved configuration on disk");
+ loadFromFile();
+ return;
+ }
+
+ properties.clear();
+
+ for (ConfigEntry entry : configuration.getConfig())
+ {
+ properties.setProperty(entry.getKey(), entry.getValue());
+ }
+
+ try
+ {
+ saveToFile();
+
+ logger.debug("Updated configuration on disk with the latest version");
+ }
+ catch (IOException ex)
+ {
+ logger.warn("Unable to update configuration on disk", ex);
+ }
+ }
+
+ private void loadFromFile()
+ {
+ try (FileInputStream in = new FileInputStream(propertiesFile))
+ {
+ properties.load(in);
+ }
+ catch (FileNotFoundException ex)
+ {
+ logger.debug("Unable to load settings - no such file");
+ }
+ catch (IOException ex)
+ {
+ logger.warn("Unable to load settings", ex);
+ }
+ }
+
+ private void saveToFile() throws IOException
+ {
+ propertiesFile.getParentFile().mkdirs();
+
+ try (FileOutputStream out = new FileOutputStream(propertiesFile))
+ {
+ properties.store(out, "Runelite configuration");
+ }
+ }
+
+ public T getConfig(Class clazz)
+ {
+ if (!Modifier.isPublic(clazz.getModifiers()))
+ {
+ throw new RuntimeException("Non-public configuration classes can't have default methods invoked");
+ }
+
+ return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class>[]
+ {
+ clazz
+ }, handler);
+ }
+
+ public String getConfiguration(String groupName, String key)
+ {
+ return properties.getProperty(groupName + "." + key);
+ }
+
+ public void setConfiguration(String groupName, String key, String value)
+ {
+ logger.debug("Setting configuration value for {}.{} to {}", groupName, key, value);
+
+ String oldValue = (String) properties.setProperty(groupName + "." + key, value);
+
+ if (client != null)
+ {
+ try
+ {
+ client.set(groupName + "." + key, value);
+ }
+ catch (IOException ex)
+ {
+ logger.warn("unable to set configuration item", ex);
+ }
+ }
+
+ try
+ {
+ saveToFile();
+ }
+ catch (IOException ex)
+ {
+ logger.warn("unable to save configuration file", ex);
+ }
+
+ ConfigChanged configChanged = new ConfigChanged();
+ configChanged.setGroup(groupName);
+ configChanged.setKey(key);
+ configChanged.setOldValue(oldValue);
+ configChanged.setNewValue(value);
+
+ eventBus.post(configChanged);
+ }
+
+ public void unsetConfiguration(String groupName, String key)
+ {
+ logger.debug("Unsetting configuration value for {}.{}", groupName, key);
+
+ String oldValue = (String) properties.remove(groupName + "." + key);
+
+ if (client != null)
+ {
+ try
+ {
+ client.unset(groupName + "." + key);
+ }
+ catch (IOException ex)
+ {
+ logger.warn("unable to set configuration item", ex);
+ }
+ }
+
+ try
+ {
+ saveToFile();
+ }
+ catch (IOException ex)
+ {
+ logger.warn("unable to save configuration file", ex);
+ }
+
+ ConfigChanged configChanged = new ConfigChanged();
+ configChanged.setGroup(groupName);
+ configChanged.setKey(key);
+ configChanged.setOldValue(oldValue);
+
+ eventBus.post(configChanged);
+ }
+
+ public ConfigDescriptor getConfigDescriptor(Object configurationProxy)
+ {
+ Class> inter = configurationProxy.getClass().getInterfaces()[0];
+ ConfigGroup group = inter.getAnnotation(ConfigGroup.class);
+
+ if (group == null)
+ {
+ throw new IllegalArgumentException("Not a config group");
+ }
+
+ List items = Arrays.stream(inter.getMethods())
+ .filter(m -> m.getParameterCount() == 0)
+ .map(m -> new ConfigItemDescriptor(
+ m.getDeclaredAnnotation(ConfigItem.class),
+ m.getReturnType()
+ ))
+ .collect(Collectors.toList());
+ return new ConfigDescriptor(group, items);
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/events/ConfigChanged.java b/runelite-client/src/main/java/net/runelite/client/events/ConfigChanged.java
new file mode 100644
index 0000000000..ba30737994
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/events/ConfigChanged.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2017, Adam
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.events;
+
+public class ConfigChanged
+{
+ private String group;
+ private String key;
+ private String oldValue;
+ private String newValue;
+
+ public String getGroup()
+ {
+ return group;
+ }
+
+ public void setGroup(String group)
+ {
+ this.group = group;
+ }
+
+ public String getKey()
+ {
+ return key;
+ }
+
+ public void setKey(String key)
+ {
+ this.key = key;
+ }
+
+ public String getOldValue()
+ {
+ return oldValue;
+ }
+
+ public void setOldValue(String oldValue)
+ {
+ this.oldValue = oldValue;
+ }
+
+ public String getNewValue()
+ {
+ return newValue;
+ }
+
+ public void setNewValue(String newValue)
+ {
+ this.newValue = newValue;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java
index d2e4967592..6b4ae68652 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java
@@ -44,11 +44,17 @@ public abstract class Plugin extends AbstractIdleService
return overlay != null ? Arrays.asList(overlay) : Collections.EMPTY_LIST;
}
+ public Object getConfig()
+ {
+ return null;
+ }
+
/**
* Override AbstractIdleService's default executor to instead execute in
* the main thread. Prevents plugins from all being initialized from
* different threads, which causes the plugin order on the navbar to be
* undefined
+ *
* @return
*/
@Override
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java
index 9b76189696..b898cae92f 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java
@@ -36,6 +36,7 @@ import net.runelite.client.plugins.account.AccountPlugin;
import net.runelite.client.plugins.boosts.Boosts;
import net.runelite.client.plugins.bosstimer.BossTimers;
import net.runelite.client.plugins.clanchat.ClanChat;
+import net.runelite.client.plugins.config.ConfigPlugin;
import net.runelite.client.plugins.devtools.DevTools;
import net.runelite.client.plugins.fpsinfo.FPS;
import net.runelite.client.plugins.hiscore.Hiscore;
@@ -77,6 +78,7 @@ public class PluginManager
plugins.add(new ClanChat());
plugins.add(new Zulrah());
plugins.add(new AccountPlugin());
+ plugins.add(new ConfigPlugin());
if (RuneLite.getOptions().has("developer-mode"))
{
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java
new file mode 100644
index 0000000000..f9fbac101c
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2017, Adam
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.plugins.config;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.swing.BoxLayout;
+import static javax.swing.BoxLayout.Y_AXIS;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import net.runelite.client.RuneLite;
+import net.runelite.client.config.ConfigDescriptor;
+import net.runelite.client.config.ConfigItemDescriptor;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.plugins.PluginManager;
+import net.runelite.client.ui.PluginPanel;
+import static net.runelite.client.ui.PluginPanel.PANEL_HEIGHT;
+import static net.runelite.client.ui.PluginPanel.PANEL_WIDTH;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ConfigPanel extends PluginPanel
+{
+ private static final Logger logger = LoggerFactory.getLogger(ConfigPanel.class);
+
+ private final RuneLite runelite = RuneLite.getRunelite();
+
+ public ConfigPanel()
+ {
+ setMinimumSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
+ setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
+ setSize(PANEL_WIDTH, PANEL_HEIGHT);
+ setLayout(new BorderLayout());
+ setVisible(true);
+ }
+
+ @Override
+ public boolean equals(Object other)
+ {
+ return other.getClass() == this.getClass();
+ }
+
+ public void init()
+ {
+ add(createConfigPanel());
+ }
+
+ private Collection getConfig()
+ {
+ List list = new ArrayList<>();
+ PluginManager pm = runelite.getPluginManager();
+ for (Plugin plugin : pm.getPlugins())
+ {
+ Object config = plugin.getConfig();
+ if (config != null)
+ {
+ ConfigManager configManager = runelite.getConfigManager();
+ ConfigDescriptor configDescriptor = configManager.getConfigDescriptor(config);
+ list.add(configDescriptor);
+ }
+ }
+ return list;
+ }
+
+ private JPanel createConfigPanel()
+ {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, Y_AXIS));
+
+ ConfigManager configManager = runelite.getConfigManager();
+ Collection config = getConfig();
+ for (ConfigDescriptor cd : config)
+ {
+ JPanel groupPanel = new JPanel();
+ groupPanel.setLayout(new BorderLayout());
+
+ groupPanel.add(new JLabel(cd.getGroup().name()), BorderLayout.NORTH);
+
+ JPanel itemPanel = new JPanel();
+
+ for (ConfigItemDescriptor cid : cd.getItems())
+ {
+ itemPanel.add(new JLabel(cid.getItem().name()));
+
+ if (cid.getType() == boolean.class)
+ {
+ JCheckBox checkbox = new JCheckBox();
+ checkbox.setSelected(Boolean.parseBoolean(configManager.getConfiguration(cd.getGroup().keyName(), cid.getItem().keyName())));
+ checkbox.addActionListener(ae -> changeConfiguration(ae, checkbox, cd, cid));
+
+ itemPanel.add(checkbox);
+ }
+ }
+
+ groupPanel.add(itemPanel, BorderLayout.CENTER);
+ panel.add(groupPanel);
+ }
+ return panel;
+ }
+
+ private void changeConfiguration(ActionEvent ae, JCheckBox checkbox, ConfigDescriptor cd, ConfigItemDescriptor cid)
+ {
+ ConfigManager configManager = runelite.getConfigManager();
+ configManager.setConfiguration(cd.getGroup().keyName(), cid.getItem().keyName(), "" + checkbox.isSelected());
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPlugin.java
new file mode 100644
index 0000000000..193724eaa1
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPlugin.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2017, Adam
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.plugins.config;
+
+import java.awt.event.ActionEvent;
+import javax.imageio.ImageIO;
+import javax.swing.ImageIcon;
+import net.runelite.client.RuneLite;
+import net.runelite.client.config.ConfigManager;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.client.ui.ClientUI;
+import net.runelite.client.ui.NavigationButton;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ConfigPlugin extends Plugin
+{
+ private static final Logger logger = LoggerFactory.getLogger(ConfigPlugin.class);
+
+ private final NavigationButton navButton = new NavigationButton("Configuration");
+ private final RuneLite runelite = RuneLite.getRunelite();
+ private final ClientUI ui = runelite.getGui();
+
+ @Override
+ protected void startUp() throws Exception
+ {
+ navButton.getButton().addActionListener(this::setPluginPanel);
+
+ ImageIcon icon = new ImageIcon(ImageIO.read(getClass().getResourceAsStream("config_icon.png")));
+ navButton.getButton().setIcon(icon);
+
+ ui.getNavigationPanel().addNavigation(navButton);
+ }
+
+ @Override
+ protected void shutDown() throws Exception
+ {
+ }
+
+ private void setPluginPanel(ActionEvent ae)
+ {
+ ConfigPanel panel = new ConfigPanel();
+ panel.init();
+
+ ui.expand(panel);
+ }
+
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentConfig.java
new file mode 100644
index 0000000000..1d091cea85
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentConfig.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2017, Adam
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.plugins.opponentinfo;
+
+import net.runelite.client.config.ConfigGroup;
+import net.runelite.client.config.ConfigItem;
+
+@ConfigGroup(
+ keyName = "oppinfo",
+ name = "Opponent Info",
+ description = "Configuration for the opponent info plugin"
+)
+public interface OpponentConfig
+{
+ @ConfigItem(
+ keyName = "enabled",
+ name = "Enabled",
+ description = "Configures whether or not opponent info is displayed"
+ )
+ default boolean enabled()
+ {
+ return true;
+ }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfo.java b/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfo.java
index 8a25fcb71b..1d072d5660 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfo.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/opponentinfo/OpponentInfo.java
@@ -30,12 +30,14 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.util.Map;
+import net.runelite.client.RuneLite;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.ui.overlay.Overlay;
public class OpponentInfo extends Plugin
{
- private final Overlay overlay = new OpponentInfoOverlay();
+ private final OpponentConfig config = RuneLite.getRunelite().getConfigManager().getConfig(OpponentConfig.class);
+ private final Overlay overlay = new OpponentInfoOverlay(this);
@Override
public Overlay getOverlay()
@@ -53,10 +55,18 @@ public class OpponentInfo extends Plugin
{
}
+ @Override
+ public OpponentConfig getConfig()
+ {
+ return config;
+ }
+
public static Map loadNpcHealth()
{
Gson gson = new Gson();
- Type type = new TypeToken