client: Externals rework

This commit is contained in:
Owain van Brakel
2020-02-04 05:42:26 +01:00
parent cbb5c50939
commit aecffd8f71
29 changed files with 2058 additions and 430 deletions

View File

@@ -69,6 +69,7 @@ import net.runelite.client.game.XpDropManager;
import net.runelite.client.game.chatbox.ChatboxPanelManager;
import net.runelite.client.graphics.ModelOutlineRenderer;
import net.runelite.client.menus.MenuManager;
import net.runelite.client.plugins.ExternalPluginManager;
import net.runelite.client.plugins.PluginManager;
import net.runelite.client.rs.ClientLoader;
import net.runelite.client.rs.ClientUpdateCheckMode;
@@ -93,10 +94,13 @@ import org.slf4j.LoggerFactory;
@Slf4j
public class RuneLite
{
public static final String SYSTEM_VERSION = "0.0.1";
public static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite");
public static final File CACHE_DIR = new File(RUNELITE_DIR, "cache");
public static final File PROFILES_DIR = new File(RUNELITE_DIR, "profiles");
public static final File PLUGIN_DIR = new File(RUNELITE_DIR, "plugins");
public static final File EXTERNALPLUGIN_DIR = new File(RUNELITE_DIR, "externalmanager");
public static final File SCREENSHOT_DIR = new File(RUNELITE_DIR, "screenshots");
public static final File LOGS_DIR = new File(RUNELITE_DIR, "logs");
public static final File PLUGINS_DIR = new File(RUNELITE_DIR, "plugins");
@@ -105,16 +109,24 @@ public class RuneLite
@Getter
private static Injector injector;
@Inject
public DiscordService discordService;
@Inject
private WorldService worldService;
@Inject
private PluginManager pluginManager;
@Inject
private ExternalPluginManager externalPluginManager;
@Inject
private ConfigManager configManager;
@Inject
private SessionManager sessionManager;
@Inject
private ClientSessionManager clientSessionManager;
@@ -351,25 +363,34 @@ public class RuneLite
// Tell the plugin manager if client is outdated or not
pluginManager.setOutdated(isOutdated);
// Load external plugins
pluginManager.loadExternalPlugins();
// Initialize UI
RuneLiteSplashScreen.stage(.60, "Initialize UI");
clientUI.init(this);
// Load the plugins, but does not start them yet.
// This will initialize configuration
pluginManager.loadCorePlugins();
RuneLiteSplashScreen.stage(.70, "Finalizing configuration");
// Load external plugins
externalPluginManager.startExternalPluginManager();
RuneLiteSplashScreen.stage(.75, "Finalizing configuration");
// Plugins have provided their config, so set default config
// to main settings
pluginManager.loadDefaultPluginConfiguration();
// Start client session
RuneLiteSplashScreen.stage(.75, "Starting core interface");
clientSessionManager.start();
externalPluginManager.startExternalUpdateManager();
// Initialize UI
RuneLiteSplashScreen.stage(.80, "Initialize UI");
clientUI.init(this);
RuneLiteSplashScreen.stage(.77, "Updating external plugins");
externalPluginManager.update();
// Load external plugins
pluginManager.loadExternalPlugins();
// Start client session
RuneLiteSplashScreen.stage(.80, "Starting core interface");
clientSessionManager.start();
//Set the world if specified via CLI args - will not work until clientUI.init is called
Optional<Integer> worldArg = Optional.ofNullable(System.getProperty("cli.world")).map(Integer::parseInt);
@@ -409,6 +430,7 @@ public class RuneLite
// Start plugins
pluginManager.startCorePlugins();
externalPluginManager.loadPlugins();
// Register additional schedulers
if (this.client != null)

View File

@@ -42,6 +42,7 @@ public class RuneLiteProperties
private static final String WIKI_LINK = "runelite.wiki.link";
private static final String PATREON_LINK = "runelite.patreon.link";
private static final String LAUNCHER_VERSION_PROPERTY = "launcher.version";
private static final String PLUGIN_PATH = "plugin.path";
private static final String TROUBLESHOOTING_LINK = "runelite.wiki.troubleshooting.link";
private static final String BUILDING_LINK = "runelite.wiki.building.link";
private static final String DNS_CHANGE_LINK = "runelite.dnschange.link";
@@ -137,4 +138,11 @@ public class RuneLiteProperties
String launcherVersion = properties.getProperty(LAUNCHER_VERSION_PROPERTY);
return launcherVersion.equals("-1") ? null : launcherVersion;
}
@Nullable
public static String getPluginPath()
{
String pluginPath = properties.getProperty(PLUGIN_PATH);
return pluginPath.equals("") ? null : pluginPath;
}
}

View File

@@ -281,8 +281,8 @@ public interface OpenOSRSConfig extends Config
@ConfigItem(
keyName = "enablePlugins",
name = "Enable loading of external plugins",
description = "Enable loading of external plugins",
name = "Enable loading of legacy external plugins",
description = "Enable loading of legacy external plugins",
position = 16,
titleSection = "externalPluginsTitle"
)
@@ -364,4 +364,23 @@ public interface OpenOSRSConfig extends Config
{
return Keybind.NOT_SET;
}
@ConfigItem(
keyName = "externalRepositories",
name = "",
description = "",
hidden = true
)
default String getExternalRepositories()
{
return "OpenOSRS:https://raw.githubusercontent.com/open-osrs/plugin-hosting/master/";
}
@ConfigItem(
keyName = "externalRepositories",
name = "",
description = "",
hidden = true
)
void setExternalRepositories(String val);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Alexander V.
* Copyright (c) 2019 Owain van Brakel <https://github.com/Owain94>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -22,23 +22,16 @@
* (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.chatboxperformance;
package net.runelite.client.events;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import lombok.Data;
import net.runelite.api.events.Event;
import net.runelite.client.plugins.Plugin;
@ConfigGroup("chatboxperformance")
public interface ChatboxPerformanceConfig extends Config
@Data
public class ExternalPluginChanged implements Event
{
@ConfigItem(
position = 1,
keyName = "Chatbox",
name = "Toggle gradient",
description = "Toggles the gradient inside the chatbox."
)
default boolean transparentChatBox()
{
return true; //default enabled, just like in game.
}
}
private final String pluginId;
private final Plugin plugin;
private final boolean added;
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2019 Owain van Brakel <https://github.com/Owain94>
* 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;
import lombok.Data;
import net.runelite.api.events.Event;
@Data
public class ExternalPluginsLoaded implements Event
{}

View File

@@ -0,0 +1,49 @@
package net.runelite.client.menus;
import lombok.EqualsAndHashCode;
import net.runelite.api.MenuEntry;
import net.runelite.api.util.Text;
import net.runelite.api.widgets.WidgetID;
import net.runelite.api.widgets.WidgetInfo;
import org.apache.commons.lang3.StringUtils;
@EqualsAndHashCode(callSuper = true)
public class BankComparableEntry extends AbstractComparableEntry
{
public BankComparableEntry(String option, String itemName, boolean strictTarget)
{
this.setOption(option);
this.setTarget(Text.standardize(itemName));
this.setStrictTarget(strictTarget);
}
public boolean matches(MenuEntry entry)
{
if (isNotBankWidget(entry.getParam1()))
{
return false;
}
if (isStrictTarget() && !Text.standardize(entry.getTarget()).equals(this.getTarget()))
{
return false;
}
return StringUtils.containsIgnoreCase(entry.getOption(), this.getOption()) && Text.standardize(entry.getTarget()).contains(this.getTarget());
}
@Override
public int getPriority()
{
return 100;
}
static boolean isNotBankWidget(int widgetID)
{
final int groupId = WidgetInfo.TO_GROUP(widgetID);
return groupId != WidgetID.BANK_GROUP_ID
&& groupId != WidgetID.BANK_INVENTORY_GROUP_ID
&& groupId != WidgetID.GRAND_EXCHANGE_GROUP_ID;
}
}

View File

@@ -1,7 +1,6 @@
package net.runelite.client.menus;
import net.runelite.api.Client;
import net.runelite.client.plugins.menuentryswapper.comparables.BankComparableEntry;
public interface ComparableEntries
{

View File

@@ -0,0 +1,31 @@
package net.runelite.client.menus;
import lombok.EqualsAndHashCode;
import net.runelite.api.MenuEntry;
import net.runelite.api.util.Text;
import net.runelite.api.widgets.WidgetID;
import net.runelite.api.widgets.WidgetInfo;
import org.apache.commons.lang3.StringUtils;
@EqualsAndHashCode(callSuper = true)
public class EquipmentComparableEntry extends AbstractComparableEntry
{
public EquipmentComparableEntry(String option, String itemName)
{
this.setOption(option);
this.setTarget(Text.standardize(itemName));
}
public boolean matches(MenuEntry entry)
{
final int groupId = WidgetInfo.TO_GROUP(entry.getParam1());
if (groupId != WidgetID.EQUIPMENT_GROUP_ID)
{
return false;
}
return StringUtils.equalsIgnoreCase(entry.getOption(), this.getOption())
&& Text.standardize(entry.getTarget()).contains(this.getTarget());
}
}

View File

@@ -0,0 +1,36 @@
package net.runelite.client.menus;
import lombok.EqualsAndHashCode;
import net.runelite.api.MenuEntry;
import net.runelite.api.util.Text;
import net.runelite.api.widgets.WidgetID;
import net.runelite.api.widgets.WidgetInfo;
import org.apache.commons.lang3.StringUtils;
@EqualsAndHashCode(callSuper = true)
public class InventoryComparableEntry extends AbstractComparableEntry
{
public InventoryComparableEntry(String option, String itemName, boolean strictTarget)
{
this.setOption(option);
this.setTarget(Text.standardize(itemName));
this.setStrictTarget(strictTarget);
}
public boolean matches(MenuEntry entry)
{
final int groupId = WidgetInfo.TO_GROUP(entry.getParam1());
if (groupId != WidgetID.INVENTORY_GROUP_ID)
{
return false;
}
if (isStrictTarget() && Text.standardize(entry.getTarget()).equals(this.getTarget()))
{
return false;
}
return StringUtils.containsIgnoreCase(entry.getOption(), this.getOption()) && Text.standardize(entry.getTarget()).contains(this.getTarget());
}
}

View File

@@ -0,0 +1,45 @@
package net.runelite.client.menus;
import java.util.List;
import net.runelite.api.MenuEntry;
import net.runelite.api.util.Text;
public class ShopComparableEntry extends AbstractComparableEntry
{
private ShopComparableEntry(final boolean buy, final int amount, final String item)
{
assert amount == 1 || amount == 5 || amount == 10 || amount == 50 : "Only 1, 5, 10, or 50 are valid amounts";
this.setOption((buy ? "buy " : "sell ") + amount);
this.setTarget(Text.standardize(item));
}
@Override
public boolean matches(final MenuEntry entry)
{
return Text.standardize(entry.getOption()).equals(this.getOption()) && Text.standardize(entry.getTarget()).equals(this.getTarget());
}
@Override
public int getPriority()
{
return 100;
}
@Override
public boolean equals(Object other)
{
return other instanceof ShopComparableEntry && super.equals(other);
}
/**
* Fills the array with ShopComparableEntries, getting the items from the fed list
*/
public static void populateArray(final AbstractComparableEntry[] array, final List<String> items, final boolean buy, final int amount)
{
for (int i = 0; i < array.length; i++)
{
array[i] = new ShopComparableEntry(buy, amount, items.get(i));
}
}
}

View File

@@ -0,0 +1,82 @@
package net.runelite.client.menus;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import net.runelite.api.MenuEntry;
import net.runelite.api.util.Text;
@EqualsAndHashCode(callSuper = true)
public class WithdrawComparableEntry extends AbstractComparableEntry
{
private static String x;
private final Amount amount;
private WithdrawComparableEntry(Amount amount, String item)
{
this.amount = amount;
this.setTarget(Text.standardize(item));
}
@Override
public boolean matches(MenuEntry entry)
{
if (BankComparableEntry.isNotBankWidget(entry.getParam1()))
{
return false;
}
final String option = entry.getOption();
if (!option.startsWith("Withdraw") && !option.startsWith("Deposit"))
{
return false;
}
if (amount == Amount.X)
{
if (!option.endsWith(x))
{
return false;
}
}
else if (!option.endsWith(amount.suffix))
{
return false;
}
return Text.standardize(entry.getTarget()).contains(this.getTarget());
}
@Override
public int getPriority()
{
return 10;
}
public static void setX(int amount)
{
x = String.valueOf(amount);
}
public static void populateArray(AbstractComparableEntry[] array, List<String> items, Amount amount)
{
for (int i = 0; i < array.length; i++)
{
array[i] = new WithdrawComparableEntry(amount, items.get(i));
}
}
@AllArgsConstructor
public enum Amount
{
ONE("1"),
FIVE("5"),
TEN("10"),
X(null),
ALL("All");
private String suffix;
}
}

View File

@@ -0,0 +1,521 @@
package net.runelite.client.plugins;
import com.google.common.collect.ImmutableList;
import com.google.inject.Binder;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.swing.JOptionPane;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.RuneLite;
import static net.runelite.client.RuneLite.EXTERNALPLUGIN_DIR;
import static net.runelite.client.RuneLite.SYSTEM_VERSION;
import net.runelite.client.RuneLiteProperties;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.config.OpenOSRSConfig;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.events.ExternalPluginChanged;
import net.runelite.client.events.ExternalPluginsLoaded;
import net.runelite.client.ui.RuneLiteSplashScreen;
import net.runelite.client.util.SwingUtil;
import org.pf4j.DefaultPluginManager;
import org.pf4j.DependencyResolver;
import org.pf4j.JarPluginLoader;
import org.pf4j.JarPluginRepository;
import org.pf4j.ManifestPluginDescriptorFinder;
import org.pf4j.PluginDependency;
import org.pf4j.PluginDescriptorFinder;
import org.pf4j.PluginLoader;
import org.pf4j.PluginRepository;
import org.pf4j.PluginWrapper;
import org.pf4j.RuntimeMode;
import org.pf4j.update.DefaultUpdateRepository;
import org.pf4j.update.PluginInfo;
import org.pf4j.update.UpdateManager;
import org.pf4j.update.UpdateRepository;
import org.pf4j.update.VerifyException;
@Slf4j
@Singleton
public
class ExternalPluginManager
{
private final PluginManager runelitePluginManager;
private final org.pf4j.PluginManager externalPluginManager;
@Getter(AccessLevel.PUBLIC)
private final List<UpdateRepository> repositories = new ArrayList<>();
private final OpenOSRSConfig openOSRSConfig;
private final ConfigManager configManager;
private final EventBus eventBus;
@Getter(AccessLevel.PUBLIC)
private UpdateManager updateManager;
@Inject
public ExternalPluginManager(
PluginManager pluginManager,
OpenOSRSConfig openOSRSConfig,
ConfigManager configManager,
EventBus eventBus)
{
this.runelitePluginManager = pluginManager;
this.openOSRSConfig = openOSRSConfig;
this.configManager = configManager;
this.eventBus = eventBus;
//noinspection ResultOfMethodCallIgnored
EXTERNALPLUGIN_DIR.mkdirs();
boolean debug = RuneLiteProperties.getLauncherVersion() == null && RuneLiteProperties.getPluginPath() != null;
this.externalPluginManager = new DefaultPluginManager(debug ? Paths.get(RuneLiteProperties.getPluginPath() + File.separator + "release") : EXTERNALPLUGIN_DIR.toPath())
{
@Override
protected PluginDescriptorFinder createPluginDescriptorFinder()
{
return new ManifestPluginDescriptorFinder();
}
@Override
protected PluginRepository createPluginRepository()
{
return new JarPluginRepository(getPluginsRoot());
}
@Override
protected PluginLoader createPluginLoader()
{
return new JarPluginLoader(this);
}
@Override
public RuntimeMode getRuntimeMode()
{
return debug ? RuntimeMode.DEVELOPMENT : RuntimeMode.DEPLOYMENT;
}
};
this.externalPluginManager.setSystemVersion(SYSTEM_VERSION);
}
private static URL toRepositoryUrl(String owner, String name) throws MalformedURLException
{
return new URL("https://raw.githubusercontent.com/" + owner + "/" + name + "/master/");
}
public static boolean testRepository(String owner, String name)
{
final List<UpdateRepository> repositories = new ArrayList<>();
try
{
repositories.add(new DefaultUpdateRepository("github", new URL("https://raw.githubusercontent.com/" + owner + "/" + name + "/master/")));
}
catch (MalformedURLException e)
{
return true;
}
DefaultPluginManager testPluginManager = new DefaultPluginManager(EXTERNALPLUGIN_DIR.toPath());
UpdateManager updateManager = new UpdateManager(testPluginManager, repositories);
return updateManager.getPlugins().size() <= 0;
}
public static <T> Predicate<T> not(Predicate<T> t)
{
return t.negate();
}
public void startExternalPluginManager()
{
this.externalPluginManager.loadPlugins();
}
public void startExternalUpdateManager()
{
try
{
for (String keyval : openOSRSConfig.getExternalRepositories().split(";"))
{
String[] repository = keyval.split(":", 2);
repositories.add(new DefaultUpdateRepository(repository[0], new URL(repository[1])));
}
}
catch (MalformedURLException e)
{
e.printStackTrace();
}
this.updateManager = new UpdateManager(this.externalPluginManager, repositories);
}
public void addRepository(String owner, String name)
{
try
{
DefaultUpdateRepository respository = new DefaultUpdateRepository(owner, toRepositoryUrl(owner, name));
updateManager.addRepository(respository);
saveConfig();
}
catch (MalformedURLException e)
{
log.error("Repostitory could not be added");
}
}
public void removeRepository(String owner)
{
updateManager.removeRepository(owner);
saveConfig();
}
private void saveConfig()
{
StringBuilder config = new StringBuilder();
for (UpdateRepository repository : updateManager.getRepositories())
{
config.append(repository.getId());
config.append(":");
config.append(repository.getUrl().toString());
config.append(";");
}
config.deleteCharAt(config.lastIndexOf(";"));
openOSRSConfig.setExternalRepositories(config.toString());
}
private void instantiatePlugin(String pluginId, Plugin plugin) throws PluginInstantiationException
{
List<Plugin> scannedPlugins = new ArrayList<>(runelitePluginManager.getPlugins());
Class<? extends Plugin> clazz = plugin.getClass();
PluginDescriptor[] pluginDescriptors = clazz.getAnnotationsByType(PluginDescriptor.class);
for (PluginDescriptor pluginDescriptor : pluginDescriptors)
{
if (pluginDescriptor.type() == PluginType.EXTERNAL)
{
log.error("Class {} is using the the new external plugin loader, it should not use PluginType.EXTERNAL", clazz);
return;
}
}
net.runelite.client.plugins.PluginDependency[] pluginDependencies = clazz.getAnnotationsByType(net.runelite.client.plugins.PluginDependency.class);
List<Plugin> deps = new ArrayList<>();
for (net.runelite.client.plugins.PluginDependency pluginDependency : pluginDependencies)
{
Optional<Plugin> dependency = scannedPlugins.stream().filter(p -> p.getClass() == pluginDependency.value()).findFirst();
if (dependency.isEmpty())
{
throw new PluginInstantiationException("Unmet dependency for " + clazz.getSimpleName() + ": " + pluginDependency.value().getSimpleName());
}
deps.add(dependency.get());
}
Module pluginModule = (Binder binder) ->
{
//noinspection unchecked
binder.bind((Class<Plugin>) plugin.getClass()).toInstance(plugin);
binder.install(plugin);
for (Plugin p : deps)
{
Module p2 = (Binder binder2) ->
{
//noinspection unchecked
binder2.bind((Class<Plugin>) p.getClass()).toInstance(p);
binder2.install(p);
};
binder.install(p2);
}
};
Injector pluginInjector = RuneLite.getInjector().createChildInjector(pluginModule);
pluginInjector.injectMembers(plugin);
plugin.injector = pluginInjector;
// Initialize default configuration
Injector injector = plugin.getInjector();
for (Key<?> key : injector.getAllBindings().keySet())
{
Class<?> type = key.getTypeLiteral().getRawType();
if (Config.class.isAssignableFrom(type))
{
Config config = (Config) injector.getInstance(key);
configManager.setDefaultConfiguration(config, false);
}
}
try
{
runelitePluginManager.startPlugin(plugin);
}
catch (PluginInstantiationException ex)
{
log.warn("unable to start plugin", ex);
return;
}
runelitePluginManager.add(plugin);
eventBus.post(ExternalPluginChanged.class, new ExternalPluginChanged(pluginId, plugin, true));
}
public void loadPlugins()
{
this.externalPluginManager.startPlugins();
List<PluginWrapper> startedPlugins = getStartedPlugins();
int index = 1;
for (PluginWrapper plugin : startedPlugins)
{
RuneLiteSplashScreen.stage(.90, 1, "Starting external plugins", index++, startedPlugins.size());
loadPlugin(plugin.getPluginId());
}
eventBus.post(ExternalPluginsLoaded.class, new ExternalPluginsLoaded());
}
private void loadPlugin(String pluginId)
{
try
{
List<Plugin> extensions = externalPluginManager.getExtensions(Plugin.class, pluginId);
for (Plugin plugin : extensions)
{
try
{
instantiatePlugin(pluginId, plugin);
}
catch (PluginInstantiationException e)
{
log.warn("Error instantiating plugin!", e);
return;
}
}
}
catch (NoClassDefFoundError ex)
{
try
{
SwingUtil.syncExec(() ->
JOptionPane.showMessageDialog(null,
pluginId + " could not be loaded due to the following error: " + ex.getMessage(),
"External plugin error",
JOptionPane.ERROR_MESSAGE));
}
catch (InvocationTargetException | InterruptedException ignored)
{
}
}
}
private void stopPlugins()
{
List<PluginWrapper> startedPlugins = ImmutableList.copyOf(getStartedPlugins());
for (PluginWrapper pluginWrapper : startedPlugins)
{
String pluginId = pluginWrapper.getDescriptor().getPluginId();
List<Plugin> extensions = externalPluginManager.getExtensions(Plugin.class, pluginId);
for (Plugin plugin : runelitePluginManager.getPlugins())
{
if (!extensions.get(0).getClass().getName().equals(plugin.getClass().getName()))
{
continue;
}
try
{
runelitePluginManager.stopPlugin(plugin);
runelitePluginManager.remove(plugin);
eventBus.post(ExternalPluginChanged.class, new ExternalPluginChanged(pluginId, plugin, false));
}
catch (PluginInstantiationException ex)
{
log.warn("unable to stop plugin", ex);
return;
}
}
}
}
private Path stopPlugin(String pluginId)
{
List<PluginWrapper> startedPlugins = ImmutableList.copyOf(getStartedPlugins());
for (PluginWrapper pluginWrapper : startedPlugins)
{
if (!pluginId.equals(pluginWrapper.getDescriptor().getPluginId()))
{
continue;
}
List<Plugin> extensions = externalPluginManager.getExtensions(Plugin.class, pluginId);
for (Plugin plugin : runelitePluginManager.getPlugins())
{
if (!extensions.get(0).getClass().getName().equals(plugin.getClass().getName()))
{
continue;
}
try
{
runelitePluginManager.stopPlugin(plugin);
runelitePluginManager.remove(plugin);
eventBus.post(ExternalPluginChanged.class, new ExternalPluginChanged(pluginId, plugin, false));
return pluginWrapper.getPluginPath();
}
catch (PluginInstantiationException ex)
{
log.warn("unable to stop plugin", ex);
return null;
}
}
}
return null;
}
public void install(String pluginId) throws VerifyException
{
if (getDisabledPlugins().contains(pluginId))
{
this.externalPluginManager.enablePlugin(pluginId);
this.externalPluginManager.startPlugin(pluginId);
loadPlugin(pluginId);
return;
}
if (getStartedPlugins().stream().anyMatch(ev -> ev.getPluginId().equals(pluginId)))
{
return;
}
// Null version returns the last release version of this plugin for given system version
try
{
PluginInfo.PluginRelease latest = updateManager.getLastPluginRelease(pluginId);
if (latest == null)
{
try
{
SwingUtil.syncExec(() ->
JOptionPane.showMessageDialog(null,
pluginId + " is outdated and cannot be installed",
"Installation error",
JOptionPane.ERROR_MESSAGE));
}
catch (InvocationTargetException | InterruptedException ignored)
{
}
return;
}
updateManager.installPlugin(pluginId, null);
loadPlugin(pluginId);
}
catch (DependencyResolver.DependenciesNotFoundException ex)
{
uninstall(pluginId);
for (String dep : ex.getDependencies())
{
install(dep);
}
install(pluginId);
}
}
public void uninstall(String pluginId)
{
Path pluginPath = stopPlugin(pluginId);
if (pluginPath == null)
{
return;
}
externalPluginManager.stopPlugin(pluginId);
externalPluginManager.disablePlugin(pluginId);
}
public void update()
{
if (updateManager.hasUpdates())
{
List<PluginInfo> updates = updateManager.getUpdates();
for (PluginInfo plugin : updates)
{
PluginInfo.PluginRelease lastRelease = updateManager.getLastPluginRelease(plugin.id);
String lastVersion = lastRelease.version;
boolean updated = updateManager.updatePlugin(plugin.id, lastVersion);
if (!updated)
{
log.warn("Cannot update plugin '{}'", plugin.id);
}
}
}
}
public Set<String> getDependencies()
{
Set<String> deps = new HashSet<>();
List<PluginWrapper> startedPlugins = getStartedPlugins();
for (PluginWrapper pluginWrapper : startedPlugins)
{
for (PluginDependency pluginDependency : pluginWrapper.getDescriptor().getDependencies())
{
deps.add(pluginDependency.getPluginId());
}
}
return deps;
}
public List<String> getDisabledPlugins()
{
return this.externalPluginManager.getResolvedPlugins()
.stream()
.filter(not(this.externalPluginManager.getStartedPlugins()::contains))
.map(PluginWrapper::getPluginId)
.collect(Collectors.toList());
}
public List<PluginWrapper> getStartedPlugins()
{
return this.externalPluginManager.getStartedPlugins();
}
}

View File

@@ -24,23 +24,26 @@
*/
package net.runelite.client.plugins;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Binder;
import com.google.inject.Injector;
import com.google.inject.Module;
import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import java.lang.invoke.MethodHandles;
import java.util.Collection;
import io.reactivex.functions.Consumer;
import java.lang.reflect.Method;
import java.util.Set;
import lombok.AccessLevel;
import lombok.Getter;
import net.runelite.client.eventbus.AccessorGenerator;
import lombok.Value;
import net.runelite.api.events.Event;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.eventbus.Subscription;
import net.runelite.client.eventbus.EventScheduler;
import net.runelite.client.eventbus.Subscribe;
import org.pf4j.ExtensionPoint;
public abstract class Plugin implements Module
public abstract class Plugin implements Module, ExtensionPoint
{
private Set<Subscription> annotatedSubscriptions = null;
private final Set<Subscription> annotatedSubscriptions = findSubscriptions();
private final Object annotatedSubsLock = new Object();
@Getter(AccessLevel.PROTECTED)
protected Injector injector;
@@ -58,33 +61,53 @@ public abstract class Plugin implements Module
{
}
@SuppressWarnings("unchecked")
final void addAnnotatedSubscriptions(EventBus eventBus)
{
if (annotatedSubscriptions == null)
{
Observable.fromCallable(this::findSubscriptions)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.single())
.subscribe(subs -> addSubs(eventBus, (annotatedSubscriptions = subs)));
}
else
{
addSubs(eventBus, annotatedSubscriptions);
}
annotatedSubscriptions.forEach(sub -> eventBus.subscribe(sub.type, annotatedSubsLock, sub.method, sub.takeUntil, sub.subscribe, sub.observe));
}
final void removeAnnotatedSubscriptions(EventBus eventBus)
{
eventBus.unregister(this);
eventBus.unregister(annotatedSubsLock);
}
private Set<Subscription> findSubscriptions()
{
return AccessorGenerator.scanSubscribes(MethodHandles.lookup(), this);
ImmutableSet.Builder<Subscription> builder = ImmutableSet.builder();
for (Method method : this.getClass().getDeclaredMethods())
{
Subscribe annotation = method.getAnnotation(Subscribe.class);
if (annotation == null)
{
continue;
}
assert method.getParameterCount() == 1 : "Methods annotated with @Subscribe should have only one parameter";
Class<?> type = method.getParameterTypes()[0];
assert Event.class.isAssignableFrom(type) : "Parameters of methods annotated with @Subscribe should implement net.runelite.api.events.Event";
assert method.getReturnType() == void.class : "Methods annotated with @Subscribe should have a void return type";
method.setAccessible(true);
Subscription sub = new Subscription(type.asSubclass(Event.class), event -> method.invoke(this, event), annotation.takeUntil(), annotation.subscribe(), annotation.observe());
builder.add(sub);
}
return builder.build();
}
private void addSubs(EventBus eventBus, Collection<Subscription> subs)
@Value
private static class Subscription
{
subs.forEach(s -> s.subscribe(eventBus, this));
private final Class type;
private final Consumer method;
private final int takeUntil;
private final EventScheduler subscribe;
private final EventScheduler observe;
}
}

View File

@@ -13,7 +13,7 @@ public enum PluginType
SKILLING("Skilling"),
UTILITY("Utilities"),
MISCELLANEOUS("Miscellaneous"),
EXTERNAL("External"),
EXTERNAL("Legacy External"),
IMPORTANT("System"),
MINIGAME("Minigame"),
GAMEMODE("Gamemode"),
@@ -26,4 +26,4 @@ public enum PluginType
{
return getName();
}
}
}

View File

@@ -24,9 +24,7 @@
*/
package net.runelite.client.plugins.chatboxperformance;
import com.google.inject.Provides;
import javax.inject.Inject;
import javax.inject.Singleton;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.ScriptID;
@@ -37,18 +35,14 @@ import net.runelite.api.widgets.WidgetPositionMode;
import net.runelite.api.widgets.WidgetSizeMode;
import net.runelite.api.widgets.WidgetType;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.plugins.PluginType;
@PluginDescriptor(
name = "Chatbox performance",
type = PluginType.MISCELLANEOUS
hidden = true
)
@Singleton
public class ChatboxPerformancePlugin extends Plugin
{
@Inject
@@ -57,24 +51,6 @@ public class ChatboxPerformancePlugin extends Plugin
@Inject
private ClientThread clientThread;
@Inject
private ChatboxPerformanceConfig config;
@Subscribe
public void onConfigChanged(ConfigChanged event)
{
if (event.getGroup().equals("chatboxperformance"))
{
fixDarkBackground();
}
}
@Provides
ChatboxPerformanceConfig getConfig(ConfigManager configManager)
{
return configManager.getConfig(ChatboxPerformanceConfig.class);
}
@Override
public void startUp()
{
@@ -108,7 +84,7 @@ public class ChatboxPerformancePlugin extends Plugin
private void fixDarkBackground()
{
int currOpacity = 255;
int currOpacity = 256;
int prevY = 0;
Widget[] children = client.getWidget(WidgetInfo.CHATBOX_TRANSPARENT_BACKGROUND).getDynamicChildren();
Widget prev = null;
@@ -132,10 +108,7 @@ public class ChatboxPerformancePlugin extends Plugin
}
prevY = w.getRelativeY();
if (config.transparentChatBox())
{
currOpacity -= 3;
}
currOpacity -= 3; // Rough number, can't get exactly the same as Jagex because of rounding
prev = w;
}
if (prev != null)
@@ -146,7 +119,7 @@ public class ChatboxPerformancePlugin extends Plugin
private void fixWhiteLines(boolean upperLine)
{
int currOpacity = 255;
int currOpacity = 256;
int prevWidth = 0;
Widget[] children = client.getWidget(WidgetInfo.CHATBOX_TRANSPARENT_LINES).getDynamicChildren();
Widget prev = null;
@@ -184,4 +157,4 @@ public class ChatboxPerformancePlugin extends Plugin
prev.setOpacity(currOpacity);
}
}
}
}

View File

@@ -1122,9 +1122,7 @@ class ConfigPanel extends PluginPanel
if (event.getPlugin() == this.pluginConfig.getPlugin())
{
SwingUtilities.invokeLater(() ->
{
pluginToggle.setSelected(event.isLoaded());
});
pluginToggle.setSelected(event.isLoaded()));
}
}
}

View File

@@ -72,6 +72,8 @@ import net.runelite.client.config.RuneLiteConfig;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.events.ExternalPluginChanged;
import net.runelite.client.events.ExternalPluginsLoaded;
import net.runelite.client.events.PluginChanged;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
@@ -183,6 +185,14 @@ public class PluginListPanel extends PluginPanel
}
});
eventBus.subscribe(ExternalPluginsLoaded.class, this, ignored -> {
eventBus.subscribe(ExternalPluginChanged.class, this, ev -> {
SwingUtilities.invokeLater(this::rebuildPluginList);
});
SwingUtilities.invokeLater(this::rebuildPluginList);
});
muxer = new MultiplexingPluginPanel(this);
searchBar = new IconTextField();

View File

@@ -1,87 +0,0 @@
package net.runelite.client.plugins.info;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
@ConfigGroup("info")
public interface InfoConfig extends Config
{
@ConfigItem(
keyName = "showGithub",
name = "Show the OpenOSRS Github",
description = "Configures if you want to show the OpenOSRS Github or not.",
position = 0
)
default boolean showGithub()
{
return true;
}
@ConfigItem(
keyName = "showLauncher",
name = "Show the Launcher download",
description = "Configures if you want to show the OpenOSRS Launcher download or not.",
position = 1
)
default boolean showLauncher()
{
return true;
}
@ConfigItem(
keyName = "showLogDir",
name = "Show Log Directory",
description = "Configures if you want to show the Log Directory or not.",
position = 2
)
default boolean showLogDir()
{
return true;
}
@ConfigItem(
keyName = "showRuneliteDir",
name = "Show Runelite Directory",
description = "Configures if you want to show the Runelite directory or not.",
position = 3
)
default boolean showRuneliteDir()
{
return true;
}
@ConfigItem(
keyName = "showPluginsDir",
name = "Show Plugins Directory",
description = "Configures if you want to show the Plugins Directory or not.",
position = 4
)
default boolean showPluginsDir()
{
return true;
}
@ConfigItem(
keyName = "showScreenshotsDir",
name = "Show Screenshots Directory",
description = "Configures if you want to show the Screenshots Directory or not.",
position = 5
)
default boolean showScreenshotsDir()
{
return true;
}
@ConfigItem(
keyName = "showPhysicalDir",
name = "Show Physical Locations",
description = "Configures if you want to show the Physical Directory Locations or not.",
position = 6
)
default boolean showPhysicalDir()
{
return true;
}
}

View File

@@ -143,57 +143,36 @@ class InfoPanel extends PluginPanel
actionsContainer.add(buildLinkPanel(GITHUB_ICON, "License info", "for distribution", "https://github.com/open-osrs/runelite/blob/master/LICENSE"));
actionsContainer.add(buildLinkPanel(PATREON_ICON, "Patreon to support", "the OpenOSRS Devs", RuneLiteProperties.getPatreonLink()));
actionsContainer.add(buildLinkPanel(DISCORD_ICON, "Talk to us on our", "Discord Server", "https://discord.gg/OpenOSRS"));
if (plugin.isShowGithub())
{
actionsContainer.add(buildLinkPanel(GITHUB_ICON, "OpenOSRS Github", "", "https://github.com/open-osrs"));
}
if (plugin.isShowLauncher())
{
actionsContainer.add(buildLinkPanel(IMPORT_ICON, "Launcher Download", "for the latest launcher", "https://github.com/open-osrs/launcher/releases"));
}
if (plugin.isShowRuneliteDir())
{
actionsContainer.add(buildLinkPanel(FOLDER_ICON, "Open Runelite Directory", "for your .properties file", RUNELITE_DIR));
}
if (plugin.isShowLogDir())
{
actionsContainer.add(buildLinkPanel(FOLDER_ICON, "Open Logs Directory", "for bug reports", LOGS_DIR));
}
if (plugin.isShowPluginsDir())
{
actionsContainer.add(buildLinkPanel(FOLDER_ICON, "Open Plugins Directory", "for external plugins", PLUGINS_DIR));
}
if (plugin.isShowScreenshotsDir())
{
actionsContainer.add(buildLinkPanel(FOLDER_ICON, "Open Screenshots Directory", "for your screenshots", SCREENSHOT_DIR));
}
actionsContainer.add(buildLinkPanel(GITHUB_ICON, "OpenOSRS Github", "", "https://github.com/open-osrs"));
actionsContainer.add(buildLinkPanel(IMPORT_ICON, "Launcher Download", "for the latest launcher", "https://github.com/open-osrs/launcher/releases"));
actionsContainer.add(buildLinkPanel(FOLDER_ICON, "Open Runelite Directory", "for your .properties file", RUNELITE_DIR));
actionsContainer.add(buildLinkPanel(FOLDER_ICON, "Open Logs Directory", "for bug reports", LOGS_DIR));
actionsContainer.add(buildLinkPanel(FOLDER_ICON, "Open Plugins Directory", "for external plugins", PLUGINS_DIR));
actionsContainer.add(buildLinkPanel(FOLDER_ICON, "Open Screenshots Directory", "for your screenshots", SCREENSHOT_DIR));
if (plugin.isShowPhysicalDir())
{
JPanel pathPanel = new JPanel();
pathPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
pathPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
pathPanel.setLayout(new GridLayout(0, 1));
JPanel pathPanel = new JPanel();
pathPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
pathPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
pathPanel.setLayout(new GridLayout(0, 1));
JLabel rldirectory = new JLabel(htmlLabel("Runelite Directory: ", RUNELITE_DIRECTORY));
rldirectory.setFont(smallFont);
JLabel rldirectory = new JLabel(htmlLabel("Runelite Directory: ", RUNELITE_DIRECTORY));
rldirectory.setFont(smallFont);
JLabel logdirectory = new JLabel(htmlLabel("Log Directory: ", LOG_DIRECTORY));
logdirectory.setFont(smallFont);
JLabel logdirectory = new JLabel(htmlLabel("Log Directory: ", LOG_DIRECTORY));
logdirectory.setFont(smallFont);
JLabel pluginsdirectory = new JLabel(htmlLabel("Plugins Directory: ", PLUGINS_DIRECTORY));
pluginsdirectory.setFont(smallFont);
JLabel pluginsdirectory = new JLabel(htmlLabel("Plugins Directory: ", PLUGINS_DIRECTORY));
pluginsdirectory.setFont(smallFont);
JLabel screenshotsdirectory = new JLabel(htmlLabel("Screenshot Directory: ", SCREENSHOT_DIRECTORY));
screenshotsdirectory.setFont(smallFont);
JLabel screenshotsdirectory = new JLabel(htmlLabel("Screenshot Directory: ", SCREENSHOT_DIRECTORY));
screenshotsdirectory.setFont(smallFont);
pathPanel.add(rldirectory);
pathPanel.add(logdirectory);
pathPanel.add(pluginsdirectory);
pathPanel.add(screenshotsdirectory);
pathPanel.add(rldirectory);
pathPanel.add(logdirectory);
pathPanel.add(pluginsdirectory);
pathPanel.add(screenshotsdirectory);
add(pathPanel, BorderLayout.SOUTH);
}
add(pathPanel, BorderLayout.SOUTH);
add(versionPanel, BorderLayout.NORTH);
add(actionsContainer, BorderLayout.CENTER);

View File

@@ -24,13 +24,9 @@
*/
package net.runelite.client.plugins.info;
import com.google.inject.Provides;
import java.awt.image.BufferedImage;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.AccessLevel;
import lombok.Getter;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.plugins.Plugin;
@@ -50,35 +46,11 @@ import net.runelite.client.util.ImageUtil;
@Singleton
public class InfoPlugin extends Plugin
{
@Getter(AccessLevel.PACKAGE)
private boolean showLogDir;
@Getter(AccessLevel.PACKAGE)
private boolean showRuneliteDir;
@Getter(AccessLevel.PACKAGE)
private boolean showPluginsDir;
@Getter(AccessLevel.PACKAGE)
private boolean showScreenshotsDir;
@Getter(AccessLevel.PACKAGE)
private boolean showGithub;
@Getter(AccessLevel.PACKAGE)
private boolean showLauncher;
@Getter(AccessLevel.PACKAGE)
private boolean showPhysicalDir;
@Inject
private ClientToolbar clientToolbar;
@Inject
private InfoConfig config;
private NavigationButton navButton;
@Provides
InfoConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(InfoConfig.class);
}
@Subscribe
private void onConfigChanged(ConfigChanged event)
{
@@ -86,15 +58,11 @@ public class InfoPlugin extends Plugin
{
return;
}
updateConfig();
}
@Override
protected void startUp()
{
updateConfig();
InfoPanel panel = injector.getInstance(InfoPanel.class);
final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "info_icon.png");
@@ -114,15 +82,4 @@ public class InfoPlugin extends Plugin
{
clientToolbar.removeNavigation(navButton);
}
private void updateConfig()
{
this.showGithub = config.showGithub();
this.showLauncher = config.showLauncher();
this.showLogDir = config.showLogDir();
this.showRuneliteDir = config.showRuneliteDir();
this.showPluginsDir = config.showPluginsDir();
this.showScreenshotsDir = config.showScreenshotsDir();
this.showPhysicalDir = config.showPhysicalDir();
}
}

View File

@@ -27,42 +27,29 @@
package net.runelite.client.plugins.openosrs;
import java.awt.event.KeyEvent;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.awt.image.BufferedImage;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.AnimationID;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.GameObject;
import static net.runelite.api.ObjectID.CANNON_BASE;
import net.runelite.api.Player;
import net.runelite.api.Projectile;
import static net.runelite.api.ProjectileID.CANNONBALL;
import static net.runelite.api.ProjectileID.GRANITE_CANNONBALL;
import static net.runelite.api.ScriptID.BANK_PIN_OP;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.CannonChanged;
import net.runelite.api.events.CannonPlaced;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameObjectSpawned;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.ProjectileSpawned;
import net.runelite.api.events.ScriptCallbackEvent;
import net.runelite.api.widgets.WidgetID;
import static net.runelite.api.widgets.WidgetInfo.*;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.Keybind;
import net.runelite.client.config.OpenOSRSConfig;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.input.KeyListener;
import net.runelite.client.input.KeyManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.plugins.openosrs.externals.PluginManagerPanel;
import net.runelite.client.ui.ClientToolbar;
import net.runelite.client.ui.NavigationButton;
import net.runelite.client.util.HotkeyListener;
import net.runelite.client.util.ImageUtil;
@PluginDescriptor(
loadWhenOutdated = true, // prevent users from disabling
@@ -88,21 +75,10 @@ public class OpenOSRSPlugin extends Plugin
private ClientThread clientThread;
@Inject
private EventBus eventBus;
private ClientToolbar clientToolbar;
private static final Pattern NUMBER_PATTERN = Pattern.compile("([0-9]+)");
private static final int MAX_CBALLS = 30;
private int cballsLeft;
private WorldPoint cannonPosition;
private GameObject cannon;
private boolean cannonPlaced;
private boolean skipProjectileCheckThisTick;
private NavigationButton navButton;
private int entered = -1;
private int enterIdx;
private boolean expectInput;
private boolean detach;
private Keybind keybind;
private final HotkeyListener hotkeyListener = new HotkeyListener(() -> this.keybind)
{
@Override
@@ -113,10 +89,27 @@ public class OpenOSRSPlugin extends Plugin
client.setOculusOrbNormalSpeed(detach ? 36 : 12);
}
};
private int entered = -1;
private int enterIdx;
private boolean expectInput;
private boolean detach;
private Keybind keybind;
@Override
protected void startUp()
{
PluginManagerPanel panel = injector.getInstance(PluginManagerPanel.class);
final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "externalmanager_icon.png");
navButton = NavigationButton.builder()
.tooltip("External Plugin Manager")
.icon(icon)
.priority(1)
.panel(panel)
.build();
clientToolbar.addNavigation(navButton);
entered = -1;
enterIdx = 0;
expectInput = false;
@@ -127,6 +120,8 @@ public class OpenOSRSPlugin extends Plugin
@Override
protected void shutDown()
{
clientToolbar.removeNavigation(navButton);
entered = 0;
enterIdx = 0;
expectInput = false;
@@ -189,136 +184,6 @@ public class OpenOSRSPlugin extends Plugin
}
}
@Subscribe
private void onChatMessage(ChatMessage event)
{
if (event.getType() != ChatMessageType.SPAM && event.getType() != ChatMessageType.GAMEMESSAGE)
{
return;
}
if (event.getMessage().equals("You add the furnace."))
{
cballsLeft = 0;
eventBus.post(CannonPlaced.class, new CannonPlaced(true, cannonPosition, cannon));
eventBus.post(CannonChanged.class, new CannonChanged(null, cballsLeft));
cannonPlaced = true;
}
if (event.getMessage().contains("You pick up the cannon")
|| event.getMessage().contains("Your cannon has decayed. Speak to Nulodion to get a new one!"))
{
cballsLeft = 0;
eventBus.post(CannonPlaced.class, new CannonPlaced(false, null, null));
eventBus.post(CannonChanged.class, new CannonChanged(null, cballsLeft));
cannonPlaced = false;
}
if (event.getMessage().startsWith("You load the cannon with"))
{
Matcher m = NUMBER_PATTERN.matcher(event.getMessage());
if (m.find())
{
// The cannon will usually refill to MAX_CBALLS, but if the
// player didn't have enough cannonballs in their inventory,
// it could fill up less than that. Filling the cannon to
// cballsLeft + amt is not always accurate though because our
// counter doesn't decrease if the player has been too far away
// from the cannon due to the projectiels not being in memory,
// so our counter can be higher than it is supposed to be.
int amt = Integer.parseInt(m.group());
if (cballsLeft + amt >= MAX_CBALLS)
{
skipProjectileCheckThisTick = true;
cballsLeft = MAX_CBALLS;
}
else
{
cballsLeft += amt;
}
}
else if (event.getMessage().equals("You load the cannon with one cannonball."))
{
if (cballsLeft + 1 >= MAX_CBALLS)
{
skipProjectileCheckThisTick = true;
cballsLeft = MAX_CBALLS;
}
else
{
cballsLeft++;
}
}
eventBus.post(CannonChanged.class, new CannonChanged(null, cballsLeft));
}
if (event.getMessage().contains("Your cannon is out of ammo!"))
{
skipProjectileCheckThisTick = true;
// If the player was out of range of the cannon, some cannonballs
// may have been used without the client knowing, so having this
// extra check is a good idea.
cballsLeft = 0;
eventBus.post(CannonChanged.class, new CannonChanged(null, cballsLeft));
}
if (event.getMessage().startsWith("You unload your cannon and receive Cannonball")
|| event.getMessage().startsWith("You unload your cannon and receive Granite cannonball"))
{
skipProjectileCheckThisTick = true;
cballsLeft = 0;
eventBus.post(CannonChanged.class, new CannonChanged(null, cballsLeft));
}
}
@Subscribe
private void onGameTick(GameTick event)
{
skipProjectileCheckThisTick = false;
}
@Subscribe
private void onGameObjectSpawned(GameObjectSpawned event)
{
final GameObject gameObject = event.getGameObject();
final Player localPlayer = client.getLocalPlayer();
if (gameObject.getId() == CANNON_BASE && !cannonPlaced &&
localPlayer != null && localPlayer.getWorldLocation().distanceTo(gameObject.getWorldLocation()) <= 2 &&
localPlayer.getAnimation() == AnimationID.BURYING_BONES)
{
cannonPosition = gameObject.getWorldLocation();
cannon = gameObject;
}
}
@Subscribe
private void onProjectileSpawned(ProjectileSpawned event)
{
if (!cannonPlaced)
{
return;
}
final Projectile projectile = event.getProjectile();
if ((projectile.getId() == CANNONBALL || projectile.getId() == GRANITE_CANNONBALL) && cannonPosition != null)
{
final WorldPoint projectileLoc = WorldPoint.fromLocal(client, projectile.getX1(), projectile.getY1(), client.getPlane());
if (projectileLoc.equals(cannonPosition) && !skipProjectileCheckThisTick)
{
cballsLeft--;
eventBus.post(CannonChanged.class, new CannonChanged(projectile.getId(), cballsLeft));
}
}
}
private void handleKey(char c)
{
if (client.getWidget(WidgetID.BANK_PIN_GROUP_ID, BANK_PIN_INSTRUCTION_TEXT.getChildId()) == null
@@ -397,4 +262,4 @@ public class OpenOSRSPlugin extends Plugin
{
}
}
}
}

View File

@@ -0,0 +1,97 @@
package net.runelite.client.plugins.openosrs.externals;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.net.URL;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.text.DefaultCaret;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.FontManager;
import org.pf4j.update.PluginInfo;
public class ExternalBox extends JPanel
{
private static final Font normalFont = FontManager.getRunescapeFont();
private static final Font smallFont = FontManager.getRunescapeSmallFont();
PluginInfo pluginInfo;
JLabel install = new JLabel();
JMultilineLabel description = new JMultilineLabel();
ExternalBox(String name, URL url)
{
this(name, url.toString().replace("https://raw.githubusercontent.com/", "").replace("/master/", ""));
}
ExternalBox(PluginInfo pluginInfo)
{
this(pluginInfo.name, pluginInfo.description);
}
ExternalBox(String name, String desc)
{
setLayout(new BorderLayout());
setBackground(ColorScheme.DARKER_GRAY_COLOR);
JPanel titleWrapper = new JPanel(new BorderLayout());
titleWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR);
titleWrapper.setBorder(new CompoundBorder(
BorderFactory.createMatteBorder(0, 0, 1, 0, ColorScheme.DARK_GRAY_COLOR),
BorderFactory.createLineBorder(ColorScheme.DARKER_GRAY_COLOR)
));
JLabel title = new JLabel();
title.setText(name);
title.setFont(normalFont);
title.setBorder(null);
title.setBackground(ColorScheme.DARKER_GRAY_COLOR);
title.setPreferredSize(new Dimension(0, 24));
title.setForeground(Color.WHITE);
title.setBorder(new EmptyBorder(0, 8, 0, 0));
JPanel titleActions = new JPanel(new BorderLayout(3, 0));
titleActions.setBorder(new EmptyBorder(0, 0, 0, 8));
titleActions.setBackground(ColorScheme.DARKER_GRAY_COLOR);
titleActions.add(install, BorderLayout.EAST);
titleWrapper.add(title, BorderLayout.CENTER);
titleWrapper.add(titleActions, BorderLayout.EAST);
description.setText(desc);
description.setFont(smallFont);
description.setDisabledTextColor(Color.WHITE);
description.setBackground(ColorScheme.DARKER_GRAY_COLOR);
add(titleWrapper, BorderLayout.NORTH);
add(description, BorderLayout.CENTER);
}
public static class JMultilineLabel extends JTextArea
{
private static final long serialVersionUID = 1L;
public JMultilineLabel()
{
super();
setEditable(false);
setCursor(null);
setOpaque(false);
setFocusable(false);
setWrapStyleWord(true);
setLineWrap(true);
setBorder(new EmptyBorder(0, 8, 0, 8));
setAlignmentY(JLabel.CENTER_ALIGNMENT);
DefaultCaret caret = (DefaultCaret) getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
}
}
}

View File

@@ -0,0 +1,831 @@
package net.runelite.client.plugins.openosrs.externals;
import com.google.gson.JsonSyntaxException;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import javax.inject.Inject;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.events.ExternalPluginChanged;
import net.runelite.client.events.ExternalPluginsLoaded;
import net.runelite.client.plugins.ExternalPluginManager;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.DynamicGridLayout;
import net.runelite.client.ui.FontManager;
import net.runelite.client.ui.PluginPanel;
import net.runelite.client.ui.components.IconTextField;
import net.runelite.client.ui.components.shadowlabel.JShadowedLabel;
import net.runelite.client.util.DeferredDocumentChangedListener;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.SwingUtil;
import org.apache.commons.text.similarity.JaroWinklerDistance;
import org.pf4j.update.PluginInfo;
import org.pf4j.update.UpdateManager;
import org.pf4j.update.UpdateRepository;
import org.pf4j.update.VerifyException;
@Slf4j
public class PluginManagerPanel extends PluginPanel
{
private static final JaroWinklerDistance DISTANCE = new JaroWinklerDistance();
private static final ImageIcon SECTION_EXPAND_ICON;
private static final ImageIcon SECTION_EXPAND_ICON_HOVER;
private static final ImageIcon SECTION_RETRACT_ICON;
private static final ImageIcon SECTION_RETRACT_ICON_HOVER;
private static final ImageIcon ADD_ICON;
private static final ImageIcon ADD_HOVER_ICON;
private static final ImageIcon DELETE_ICON;
private static final ImageIcon DELETE_HOVER_ICON;
private static final ImageIcon DELETE_ICON_GRAY;
private static final ImageIcon DELETE_HOVER_ICON_GRAY;
static
{
final BufferedImage backIcon = ImageUtil.getResourceStreamFromClass(PluginManagerPanel.class, "config_back_icon.png");
final BufferedImage orangeBackIcon = ImageUtil.fillImage(backIcon, ColorScheme.BRAND_BLUE);
final BufferedImage sectionRetractIcon = ImageUtil.rotateImage(orangeBackIcon, Math.PI * 1.5);
SECTION_RETRACT_ICON = new ImageIcon(sectionRetractIcon);
SECTION_RETRACT_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(sectionRetractIcon, -100));
final BufferedImage sectionExpandIcon = ImageUtil.rotateImage(orangeBackIcon, Math.PI);
SECTION_EXPAND_ICON = new ImageIcon(sectionExpandIcon);
SECTION_EXPAND_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(sectionExpandIcon, -100));
final BufferedImage addIcon =
ImageUtil.recolorImage(
ImageUtil.getResourceStreamFromClass(PluginManagerPanel.class, "add_icon.png"), ColorScheme.BRAND_BLUE
);
ADD_ICON = new ImageIcon(addIcon);
ADD_HOVER_ICON = new ImageIcon(ImageUtil.alphaOffset(addIcon, 0.53f));
final BufferedImage deleteImg =
ImageUtil.recolorImage(
ImageUtil.resizeCanvas(
ImageUtil.getResourceStreamFromClass(PluginManagerPanel.class, "delete_icon.png"), 14, 14
), ColorScheme.BRAND_BLUE
);
DELETE_ICON = new ImageIcon(deleteImg);
DELETE_HOVER_ICON = new ImageIcon(ImageUtil.alphaOffset(deleteImg, 0.53f));
DELETE_ICON_GRAY = new ImageIcon(ImageUtil.grayscaleImage(deleteImg));
DELETE_HOVER_ICON_GRAY = new ImageIcon(ImageUtil.alphaOffset(ImageUtil.grayscaleImage(deleteImg), 0.53f));
}
private final ExternalPluginManager externalPluginManager;
private final UpdateManager updateManager;
private final ScheduledExecutorService executor;
private final IconTextField searchBar = new IconTextField();
private final List<PluginInfo> installedPluginsList = new ArrayList<>();
private final List<PluginInfo> availablePluginsList = new ArrayList<>();
private final JPanel repositoriesPanel = new JPanel();
private final JPanel installedPluginsPanel = new JPanel(new GridBagLayout());
private final JPanel availablePluginsPanel = new JPanel(new GridBagLayout());
private String filterMode = "Available plugins (All)";
private int scrollBarPosition;
private JScrollBar scrollbar;
private Set<String> deps;
@Inject
private PluginManagerPanel(ExternalPluginManager externalPluginManager, EventBus eventBus, ScheduledExecutorService executor)
{
super(false);
this.externalPluginManager = externalPluginManager;
this.updateManager = externalPluginManager.getUpdateManager();
this.executor = executor;
eventBus.subscribe(ExternalPluginsLoaded.class, "loading-externals", (e) -> {
eventBus.unregister("loading-externals");
eventBus.subscribe(ExternalPluginChanged.class, this, this::onExternalPluginChanged);
reloadPlugins();
});
DeferredDocumentChangedListener listener = new DeferredDocumentChangedListener();
listener.addChangeListener(e ->
onSearchBarChanged());
searchBar.setIcon(IconTextField.Icon.SEARCH);
searchBar.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH - 20, 30));
searchBar.setBackground(ColorScheme.DARKER_GRAY_COLOR);
searchBar.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR);
searchBar.getDocument().addDocumentListener(listener);
buildPanel();
}
private static boolean mismatchesSearchTerms(String search, PluginInfo pluginInfo)
{
final String[] searchTerms = search.toLowerCase().split(" ");
final String[] pluginTerms = (pluginInfo.name + " " + pluginInfo.description).toLowerCase().split("[/\\s]");
for (String term : searchTerms)
{
if (Arrays.stream(pluginTerms).noneMatch((t) -> t.contains(term) ||
DISTANCE.apply(t, term) > 0.9))
{
return true;
}
}
return false;
}
private JPanel addSection(String name, JPanel sectionContent)
{
final JPanel section = new JPanel();
section.setLayout(new BoxLayout(section, BoxLayout.Y_AXIS));
JPanel item = new JPanel();
item.setLayout(new BorderLayout());
JLabel headerLabel = new JLabel(name);
headerLabel.setFont(FontManager.getRunescapeFont());
headerLabel.setForeground(ColorScheme.BRAND_BLUE);
final JToggleButton collapse = new JToggleButton(SECTION_EXPAND_ICON);
SwingUtil.removeButtonDecorations(collapse);
collapse.setRolloverIcon(SECTION_EXPAND_ICON_HOVER);
collapse.setSelectedIcon(SECTION_RETRACT_ICON);
collapse.setRolloverSelectedIcon(SECTION_RETRACT_ICON_HOVER);
collapse.setToolTipText("Retract");
collapse.setPreferredSize(new Dimension(20, 20));
collapse.setFont(collapse.getFont().deriveFont(16.0f));
collapse.setBorder(null);
collapse.setMargin(new Insets(0, 0, 0, 0));
headerLabel.setBorder(new EmptyBorder(0, 6, 0, 0));
item.add(collapse, BorderLayout.WEST);
item.add(headerLabel, BorderLayout.CENTER);
final JPanel sectionContents = new JPanel();
sectionContents.setLayout(new DynamicGridLayout(0, 1, 0, 5));
sectionContents.setBorder(new EmptyBorder(6, 5, 0, 0));
section.add(item, BorderLayout.NORTH);
section.add(sectionContents, BorderLayout.SOUTH);
sectionContents.add(sectionContent);
final MouseAdapter adapter = new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
toggleSection(collapse, sectionContents);
}
};
collapse.addActionListener(e -> toggleSection(collapse, sectionContents));
headerLabel.addMouseListener(adapter);
return section;
}
private void toggleSection(JToggleButton button, JPanel contents)
{
boolean newState = !contents.isVisible();
button.setSelected(newState);
contents.setVisible(newState);
button.setToolTipText(newState ? "Retract" : "Expand");
SwingUtilities.invokeLater(() ->
{
contents.revalidate();
contents.repaint();
});
}
private JPanel filterPanel()
{
JPanel filterPanel = new JPanel();
filterPanel.setLayout(new BorderLayout(0, 5));
filterPanel.setBorder(new EmptyBorder(0, 10, 10, 10));
JRadioButton repositories = new JRadioButton("Repositories");
repositories.setSelected(filterMode.equals("Repositories"));
JRadioButton plugins = new JRadioButton("Plugins");
plugins.setSelected(filterMode.contains("plugins"));
JRadioButton available = new JRadioButton("Available");
available.setSelected(filterMode.contains("Available"));
JRadioButton installed = new JRadioButton("Installed");
installed.setSelected(filterMode.contains("Installed"));
List<UpdateRepository> updateRepositories = externalPluginManager.getRepositories();
List<JRadioButton> authors = new ArrayList<>();
JRadioButton allPlugins = new JRadioButton("All");
allPlugins.setSelected(filterMode.contains("All"));
authors.add(allPlugins);
for (UpdateRepository repository : updateRepositories)
{
JRadioButton author = new JRadioButton(repository.getId());
author.setSelected(filterMode.contains(repository.getId()));
author.addActionListener(ev -> {
filterMode = filterMode.contains("Installed") ? "Installed plugins (" + repository.getId() + ")" : "Available plugins (" + repository.getId() + ")";
onSearchBarChanged();
buildPanel();
});
authors.add(author);
}
repositories.addActionListener(ev -> {
filterMode = "Repositories";
buildPanel();
});
plugins.addActionListener(ev -> {
filterMode = "Available plugins (All)";
onSearchBarChanged();
buildPanel();
});
available.addActionListener(ev -> {
filterMode = "Available plugins (All)";
onSearchBarChanged();
buildPanel();
});
installed.addActionListener(ev -> {
filterMode = "Installed plugins (All)";
onSearchBarChanged();
buildPanel();
});
allPlugins.addActionListener(ev -> {
filterMode = filterMode.contains("Installed") ? "Installed plugins (All)" : "Available plugins (All)";
onSearchBarChanged();
buildPanel();
});
RadioButtonPanel mainRadioPanel = new RadioButtonPanel("Show", repositories, plugins);
RadioButtonPanel pluginRadioPanel = new RadioButtonPanel("Plugins", available, installed);
RadioButtonPanel authorRadioPanel = new RadioButtonPanel("Author", authors.toArray(new JRadioButton[0]));
filterPanel.add(mainRadioPanel, BorderLayout.NORTH);
if (!filterMode.equals("Repositories"))
{
filterPanel.add(pluginRadioPanel, BorderLayout.CENTER);
}
if (!filterMode.equals("Repositories") && updateRepositories.size() > 1)
{
filterPanel.add(authorRadioPanel, BorderLayout.SOUTH);
}
return filterPanel;
}
private void buildPanel()
{
removeAll();
setLayout(new BorderLayout(0, 10));
setBackground(ColorScheme.DARK_GRAY_COLOR);
add(titleBar(), BorderLayout.NORTH);
add(wrapContainer(getContentPanels()), BorderLayout.CENTER);
revalidate();
repaint();
}
private JLabel titleLabel(String text)
{
JLabel title = new JShadowedLabel();
title.setFont(FontManager.getRunescapeSmallFont());
title.setForeground(Color.WHITE);
title.setHorizontalAlignment(SwingConstants.CENTER);
title.setText("<html><body style = 'text-align:center'>" + text + "</body></html>");
return title;
}
private JPanel titleBar()
{
JPanel titlePanel = new JPanel(new BorderLayout());
titlePanel.setBorder(new EmptyBorder(10, 10, 10, 10));
JLabel title = new JLabel();
JLabel addRepo = new JLabel(ADD_ICON);
title.setText("External Plugin Manager");
title.setForeground(Color.WHITE);
addRepo.setToolTipText("Add new repository");
addRepo.addMouseListener(new MouseAdapter()
{
@Override
public void mousePressed(MouseEvent mouseEvent)
{
JTextField owner = new JTextField();
JTextField name = new JTextField();
Object[] message = {
"Repository owner:", owner,
"Repository name:", name
};
int option = JOptionPane.showConfirmDialog(null, message, "Add repository", JOptionPane.OK_CANCEL_OPTION);
if (option != JOptionPane.OK_OPTION || owner.getText().equals("") || name.getText().equals(""))
{
return;
}
if (ExternalPluginManager.testRepository(owner.getText(), name.getText()))
{
JOptionPane.showMessageDialog(null, "This doesn't appear to be a valid repository.", "Error!", JOptionPane.ERROR_MESSAGE);
return;
}
externalPluginManager.addRepository(owner.getText(), name.getText());
repositories();
reloadPlugins();
buildPanel();
}
@Override
public void mouseEntered(MouseEvent mouseEvent)
{
addRepo.setIcon(ADD_HOVER_ICON);
}
@Override
public void mouseExited(MouseEvent mouseEvent)
{
addRepo.setIcon(ADD_ICON);
}
});
titlePanel.add(title, BorderLayout.WEST);
titlePanel.add(addRepo, BorderLayout.EAST);
return titlePanel;
}
// Wrap the panel inside a scroll pane
private JScrollPane wrapContainer(final JPanel container)
{
final JPanel wrapped = new JPanel(new BorderLayout());
wrapped.add(container, BorderLayout.NORTH);
final JScrollPane scroller = new JScrollPane(wrapped);
scroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scroller.getVerticalScrollBar().setPreferredSize(new Dimension(8, 0));
this.scrollbar = scroller.getVerticalScrollBar();
return scroller;
}
private void onExternalPluginChanged(ExternalPluginChanged externalPluginChanged)
{
String pluginId = externalPluginChanged.getPluginId();
Optional<Component> externalBox;
if (externalPluginChanged.isAdded())
{
externalBox = Arrays.stream(
availablePluginsPanel.getComponents()
).filter(extBox ->
extBox instanceof ExternalBox && ((ExternalBox) extBox).pluginInfo.id.equals(pluginId)
).findFirst();
}
else
{
externalBox = Arrays.stream(
installedPluginsPanel.getComponents()
).filter(extBox ->
extBox instanceof ExternalBox && ((ExternalBox) extBox).pluginInfo.id.equals(pluginId)
).findFirst();
}
if (externalBox.isEmpty())
{
log.info("EXTERNALBOX IS EMPTY: {}", pluginId);
return;
}
ExternalBox extBox = (ExternalBox) externalBox.get();
deps = externalPluginManager.getDependencies();
try
{
SwingUtil.syncExec(() ->
{
if (externalPluginChanged.isAdded())
{
availablePluginsPanel.remove(externalBox.get());
availablePluginsList.remove(extBox.pluginInfo);
installedPluginsList.add(extBox.pluginInfo);
installedPluginsList.sort(Comparator.naturalOrder());
installedPlugins();
pluginInstallButton(extBox.install, extBox.pluginInfo, true, deps.contains(extBox.pluginInfo.id));
}
else
{
installedPluginsPanel.remove(externalBox.get());
installedPluginsList.remove(extBox.pluginInfo);
availablePluginsList.add(extBox.pluginInfo);
availablePluginsList.sort(Comparator.naturalOrder());
availablePlugins();
pluginInstallButton(extBox.install, extBox.pluginInfo, false, false);
}
});
}
catch (InvocationTargetException | InterruptedException e)
{
e.printStackTrace();
}
}
private void reloadPlugins()
{
fetchPlugins();
try
{
SwingUtil.syncExec(() -> {
this.installedPlugins();
this.availablePlugins();
resetScrollValue();
});
}
catch (InvocationTargetException | InterruptedException e)
{
e.printStackTrace();
}
resetScrollValue();
}
private void onSearchBarChanged()
{
if (filterMode.contains("Installed plugins"))
{
installedPlugins();
}
else if (filterMode.contains("Available plugins"))
{
availablePlugins();
}
}
private JPanel getContentPanels()
{
JPanel contentPanel = new JPanel();
contentPanel.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1;
c.gridx = 0;
c.gridy = 0;
c.insets = new Insets(5, 0, 5, 0);
contentPanel.add(addSection("Filter", filterPanel()), c);
if (filterMode.equals("Repositories"))
{
c.gridy++;
contentPanel.add(repositoriesPanel(), c);
}
else if (filterMode.contains("Installed plugins"))
{
c.gridy++;
contentPanel.add(installedPluginsPanel(), c);
}
else if (filterMode.contains("Available plugins"))
{
c.gridy++;
contentPanel.add(availablePluginsPanel(), c);
}
return contentPanel;
}
private JPanel repositoriesPanel()
{
JPanel installedRepositoriesPanel = new JPanel();
installedRepositoriesPanel.setLayout(new BorderLayout(0, 5));
installedRepositoriesPanel.setBorder(new EmptyBorder(0, 10, 10, 10));
installedRepositoriesPanel.add(titleLabel("Repositories"), BorderLayout.NORTH);
installedRepositoriesPanel.add(repositoriesPanel, BorderLayout.CENTER);
repositories();
return installedRepositoriesPanel;
}
private JPanel installedPluginsPanel()
{
JPanel installedPluginsContainer = new JPanel();
installedPluginsContainer.setLayout(new BorderLayout(0, 5));
installedPluginsContainer.setBorder(new EmptyBorder(0, 10, 10, 10));
installedPluginsContainer.add(titleLabel(filterMode.replace(" (All)", "")), BorderLayout.NORTH);
installedPluginsContainer.add(searchBar, BorderLayout.CENTER);
installedPluginsContainer.add(installedPluginsPanel, BorderLayout.SOUTH);
return installedPluginsContainer;
}
private JPanel availablePluginsPanel()
{
JPanel availablePluginsContainer = new JPanel();
availablePluginsContainer.setLayout(new BorderLayout(0, 5));
availablePluginsContainer.setBorder(new EmptyBorder(0, 10, 10, 10));
availablePluginsContainer.add(titleLabel(filterMode.replace(" (All)", "")), BorderLayout.NORTH);
availablePluginsContainer.add(searchBar, BorderLayout.CENTER);
availablePluginsContainer.add(availablePluginsPanel, BorderLayout.SOUTH);
return availablePluginsContainer;
}
private void repositories()
{
repositoriesPanel.removeAll();
repositoriesPanel.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
for (UpdateRepository repository : externalPluginManager.getRepositories())
{
String name = repository.getId();
ExternalBox repositoryBox = new ExternalBox(name, repository.getUrl());
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1.0;
c.gridy += 1;
c.insets = new Insets(5, 0, 0, 0);
repositoriesPanel.add(repositoryBox, c);
if (name.equals("OpenOSRS"))
{
repositoryBox.install.setVisible(false);
continue;
}
repositoryBox.install.setIcon(DELETE_ICON);
repositoryBox.install.setToolTipText("Remove");
repositoryBox.install.addMouseListener(new MouseAdapter()
{
@Override
public void mousePressed(MouseEvent e)
{
externalPluginManager.removeRepository(name);
repositories();
reloadPlugins();
}
@Override
public void mouseEntered(MouseEvent e)
{
repositoryBox.install.setIcon(DELETE_HOVER_ICON);
}
@Override
public void mouseExited(MouseEvent e)
{
repositoryBox.install.setIcon(DELETE_ICON);
}
});
}
}
private void fetchPlugins()
{
List<PluginInfo> availablePlugins = null;
List<PluginInfo> plugins = null;
List<String> disabledPlugins = externalPluginManager.getDisabledPlugins();
try
{
availablePlugins = updateManager.getAvailablePlugins();
plugins = updateManager.getPlugins();
}
catch (JsonSyntaxException ex)
{
log.error(String.valueOf(ex));
}
if (availablePlugins == null || plugins == null)
{
JOptionPane.showMessageDialog(null, "The external plugin list could not be loaded.", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
availablePluginsList.clear();
installedPluginsList.clear();
deps = externalPluginManager.getDependencies();
for (PluginInfo pluginInfo : plugins)
{
if (availablePlugins.contains(pluginInfo) || disabledPlugins.contains(pluginInfo.id))
{
availablePluginsList.add(pluginInfo);
}
else
{
installedPluginsList.add(pluginInfo);
}
}
}
private void installedPlugins()
{
JPanel panel = new JPanel();
panel.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
installedPluginsPanel.removeAll();
String search = searchBar.getText();
for (PluginInfo pluginInfo : installedPluginsList)
{
if ((!search.equals("") && mismatchesSearchTerms(search, pluginInfo)) ||
(!filterMode.contains("All") && !filterMode.contains(pluginInfo.getRepositoryId())))
{
continue;
}
ExternalBox pluginBox = new ExternalBox(pluginInfo);
pluginBox.pluginInfo = pluginInfo;
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1.0;
c.gridy += 1;
c.insets = new Insets(5, 0, 0, 0);
pluginInstallButton(pluginBox.install, pluginInfo, true, deps.contains(pluginInfo.id));
installedPluginsPanel.add(pluginBox, c);
}
if (installedPluginsPanel.getComponents().length < 1)
{
installedPluginsPanel.add(titleLabel("No plugins found"));
}
}
private void availablePlugins()
{
JPanel panel = new JPanel();
panel.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
availablePluginsPanel.removeAll();
String search = searchBar.getText();
for (PluginInfo pluginInfo : availablePluginsList)
{
if ((!search.equals("") && mismatchesSearchTerms(search, pluginInfo)) ||
(!filterMode.contains("All") && !filterMode.contains(pluginInfo.getRepositoryId())))
{
continue;
}
ExternalBox pluginBox = new ExternalBox(pluginInfo);
pluginBox.pluginInfo = pluginInfo;
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1.0;
c.gridy += 1;
c.insets = new Insets(5, 0, 0, 0);
pluginInstallButton(pluginBox.install, pluginInfo, false, false);
availablePluginsPanel.add(pluginBox, c);
}
if (availablePluginsPanel.getComponents().length < 1)
{
availablePluginsPanel.add(titleLabel("No plugins found"));
}
}
private void pluginInstallButton(JLabel install, PluginInfo pluginInfo, boolean installed, boolean hideAction)
{
install.setIcon(installed ? hideAction ? DELETE_ICON_GRAY : DELETE_ICON : ADD_ICON);
if (!hideAction)
{
install.setToolTipText(installed ? "Uninstall" : "Install");
}
install.addMouseListener(new MouseAdapter()
{
@Override
public void mousePressed(MouseEvent e)
{
saveScrollValue();
if (installed)
{
if (hideAction)
{
JOptionPane.showMessageDialog(null, "This plugin can't be uninstalled because one or more other plugins have a dependency on it.", "Error!", JOptionPane.ERROR_MESSAGE);
}
else
{
install.setIcon(null);
install.setText("Uninstalling");
executor.submit(() -> externalPluginManager.uninstall(pluginInfo.id));
}
}
else
{
install.setIcon(null);
install.setText("Installing");
executor.submit(() -> installPlugin(pluginInfo));
}
}
@Override
public void mouseEntered(MouseEvent e)
{
if (install.getText().toLowerCase().contains("installing"))
{
return;
}
install.setIcon(installed ? hideAction ? DELETE_HOVER_ICON_GRAY : DELETE_HOVER_ICON : ADD_HOVER_ICON);
}
@Override
public void mouseExited(MouseEvent e)
{
if (install.getText().toLowerCase().contains("installing"))
{
return;
}
install.setIcon(installed ? hideAction ? DELETE_ICON_GRAY : DELETE_ICON : ADD_ICON);
}
});
}
private void installPlugin(PluginInfo pluginInfo)
{
try
{
externalPluginManager.install(pluginInfo.id);
}
catch (VerifyException ex)
{
try
{
SwingUtil.syncExec(() ->
JOptionPane.showMessageDialog(null, pluginInfo.name + " could not be installed, the hash could not be verified.", "Error!", JOptionPane.ERROR_MESSAGE));
}
catch (InvocationTargetException | InterruptedException ignored)
{
}
}
}
private void saveScrollValue()
{
scrollBarPosition = scrollbar.getValue();
}
private void resetScrollValue()
{
scrollbar.setValue(scrollBarPosition);
}
}

View File

@@ -0,0 +1,56 @@
package net.runelite.client.plugins.openosrs.externals;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
public class RadioButtonPanel extends JPanel
{
public static final Orientation VERTICAL = Orientation.VERTICAL;
public static final Orientation HORIZONTAL = Orientation.HORIZONTAL;
private ButtonGroup buttonGroup = new ButtonGroup();
public RadioButtonPanel(String title, JRadioButton... buttons)
{
this(VERTICAL, title, buttons);
}
public RadioButtonPanel(Orientation orientation, String title, JRadioButton... buttons)
{
if (orientation == VERTICAL)
{
this.setLayout(new GridLayout(buttons.length, 1));
}
else
{
this.setLayout(new FlowLayout(FlowLayout.LEADING));
}
for (JRadioButton button : buttons)
{
buttonGroup.add(button);
this.add(button);
}
if (title != null)
{
this.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createEtchedBorder(), title));
}
}
public void clearSelection()
{
buttonGroup.clearSelection();
}
private enum Orientation
{
VERTICAL,
HORIZONTAL
}
}

View File

@@ -0,0 +1,58 @@
package net.runelite.client.util;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Timer;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
public class DeferredDocumentChangedListener implements DocumentListener
{
private final Timer timer;
private final List<ChangeListener> listeners;
public DeferredDocumentChangedListener()
{
listeners = new ArrayList<>(25);
timer = new Timer(200, e -> fireStateChanged());
timer.setRepeats(false);
}
public void addChangeListener(ChangeListener listener)
{
listeners.add(listener);
}
private void fireStateChanged()
{
if (!listeners.isEmpty())
{
ChangeEvent evt = new ChangeEvent(this);
for (ChangeListener listener : listeners)
{
listener.stateChanged(evt);
}
}
}
@Override
public void insertUpdate(DocumentEvent e)
{
timer.restart();
}
@Override
public void removeUpdate(DocumentEvent e)
{
timer.restart();
}
@Override
public void changedUpdate(DocumentEvent e)
{
timer.restart();
}
}

View File

@@ -29,6 +29,7 @@ import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Image;
@@ -40,6 +41,7 @@ import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.concurrent.Callable;
import java.util.function.BiConsumer;
@@ -472,4 +474,16 @@ public class SwingUtil
{
button.addItemListener(l -> button.setToolTipText(button.isSelected() ? on : off));
}
public static void syncExec(final Runnable r) throws InvocationTargetException, InterruptedException
{
if (EventQueue.isDispatchThread())
{
r.run();
}
else
{
EventQueue.invokeAndWait(r);
}
}
}