runelite-client: add config plugin

This commit is contained in:
Adam
2017-05-19 12:19:40 -04:00
parent fcbb405449
commit 0e6d408aee
20 changed files with 1119 additions and 7 deletions

View File

@@ -92,6 +92,12 @@
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@@ -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;

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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<ConfigItemDescriptor> items;
public ConfigDescriptor(ConfigGroup group, Collection<ConfigItemDescriptor> items)
{
this.group = group;
this.items = items;
}
public ConfigGroup getGroup()
{
return group;
}
public Collection<ConfigItemDescriptor> getItems()
{
return items;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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();
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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<MethodHandles.Lookup> 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);
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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();
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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;
}
}

View File

@@ -0,0 +1,272 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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> T getConfig(Class<T> 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<ConfigItemDescriptor> 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);
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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;
}
}

View File

@@ -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

View File

@@ -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"))
{

View File

@@ -0,0 +1,135 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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<ConfigDescriptor> getConfig()
{
List<ConfigDescriptor> 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<ConfigDescriptor> 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());
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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);
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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;
}
}

View File

@@ -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<String, Integer> loadNpcHealth()
{
Gson gson = new Gson();
Type type = new TypeToken<Map<String, Integer>>(){}.getType();
Type type = new TypeToken<Map<String, Integer>>()
{
}.getType();
InputStream healthFile = OpponentInfo.class.getResourceAsStream("/npc_health.json");
return gson.fromJson(new InputStreamReader(healthFile), type);

View File

@@ -22,7 +22,6 @@
* (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 java.awt.Color;
@@ -58,6 +57,7 @@ class OpponentInfoOverlay extends Overlay
private static final Duration WAIT = Duration.ofSeconds(3);
private final OpponentConfig config;
private Integer lastMaxHealth;
private DecimalFormat df = new DecimalFormat("0.0");
private float lastRatio = 0;
@@ -65,9 +65,10 @@ class OpponentInfoOverlay extends Overlay
private String opponentName;
private Map<String, Integer> oppInfoHealth = OpponentInfo.loadNpcHealth();
OpponentInfoOverlay()
OpponentInfoOverlay(OpponentInfo plugin)
{
super(OverlayPosition.TOP_LEFT, OverlayPriority.HIGH);
this.config = plugin.getConfig();
}
private Actor getOpponent()
@@ -76,7 +77,9 @@ class OpponentInfoOverlay extends Overlay
Player player = client.getLocalPlayer();
if (player == null)
{
return null;
}
return player.getInteracting();
}
@@ -84,8 +87,10 @@ class OpponentInfoOverlay extends Overlay
@Override
public Dimension render(Graphics2D graphics)
{
if (RuneLite.getClient().getGameState() != GameState.LOGGED_IN)
if (RuneLite.getClient().getGameState() != GameState.LOGGED_IN || config.enabled() == false)
{
return null;
}
Actor opponent = getOpponent();
@@ -98,8 +103,9 @@ class OpponentInfoOverlay extends Overlay
}
if (Duration.between(Instant.now(), lastTime).abs().compareTo(WAIT) > 0)
{
return null; //don't draw anything.
}
FontMetrics fm = graphics.getFontMetrics();
int height = TOP_BORDER + fm.getHeight(); // opponent name

View File

@@ -28,6 +28,7 @@ import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Objects;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
@@ -99,7 +100,7 @@ public final class ClientUI extends JFrame
public void expand(PluginPanel panel)
{
if (pluginPanel == panel)
if (Objects.equals(pluginPanel, panel))
{
navContainer.remove(1);
container.validate();

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

View File

@@ -0,0 +1,97 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.IOException;
import java.time.Instant;
import java.util.UUID;
import net.runelite.client.account.AccountSession;
import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.mock;
public class ConfigManagerTest
{
@Test
public void testGetConfig() throws IOException
{
AccountSession accountSession = new AccountSession();
accountSession.setUuid(UUID.randomUUID());
accountSession.setUsername("test");
accountSession.setCreated(Instant.now());
ConfigManager manager = new ConfigManager(mock(EventBus.class));
manager.setConfiguration("test", "key", "moo");
TestConfig conf = manager.getConfig(TestConfig.class);
Assert.assertEquals("moo", conf.key());
}
@Test
public void testGetConfigDefault() throws IOException
{
AccountSession accountSession = new AccountSession();
accountSession.setUuid(UUID.randomUUID());
accountSession.setUsername("test");
accountSession.setCreated(Instant.now());
ConfigManager manager = new ConfigManager(mock(EventBus.class));
TestConfig conf = manager.getConfig(TestConfig.class);
Assert.assertEquals("default", conf.key());
}
@Test
public void testSetConfig() throws IOException
{
AccountSession accountSession = new AccountSession();
accountSession.setUuid(UUID.randomUUID());
accountSession.setUsername("test");
accountSession.setCreated(Instant.now());
ConfigManager manager = new ConfigManager(mock(EventBus.class));
TestConfig conf = manager.getConfig(TestConfig.class);
conf.key("new value");
Assert.assertEquals("new value", conf.key());
}
@Test
public void testGetConfigDescriptor() throws IOException
{
AccountSession accountSession = new AccountSession();
accountSession.setUuid(UUID.randomUUID());
accountSession.setUsername("test");
accountSession.setCreated(Instant.now());
ConfigManager manager = new ConfigManager(mock(EventBus.class));
TestConfig conf = manager.getConfig(TestConfig.class);
ConfigDescriptor descriptor = manager.getConfigDescriptor(conf);
Assert.assertEquals(1, descriptor.getItems().size());
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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;
@ConfigGroup(
keyName = "test",
name = "test",
description = "test"
)
public interface TestConfig
{
@ConfigItem(
keyName = "key",
name = "Key Name",
description = "value"
)
default String key()
{
return "default";
}
@ConfigItem(
keyName = "key",
name = "Key Name",
description = "value"
)
void key(String key);
}