diff --git a/runelite-client/pmd-ruleset.xml b/runelite-client/pmd-ruleset.xml index 3cdece031e..ebab6e2dcf 100644 --- a/runelite-client/pmd-ruleset.xml +++ b/runelite-client/pmd-ruleset.xml @@ -67,7 +67,12 @@ - + + + + + diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index f62282759d..46a0e5f2db 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -286,6 +286,7 @@ public class RuneLite try { final ClientLoader clientLoader = new ClientLoader(okHttpClient, options.valueOf(updateMode), (String) options.valueOf("jav_config")); + final RuntimeConfigLoader runtimeConfigLoader = new RuntimeConfigLoader(okHttpClient); new Thread(() -> { @@ -306,6 +307,7 @@ public class RuneLite injector = Guice.createInjector(new RuneLiteModule( okHttpClient, clientLoader, + runtimeConfigLoader, developerMode, options.has("safe-mode"), options.valueOf(sessionfile), diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java b/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java index 6a5a79dee5..9edd72dbad 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java @@ -26,13 +26,16 @@ package net.runelite.client; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.base.Strings; +import com.google.common.math.DoubleMath; import com.google.gson.Gson; import com.google.inject.AbstractModule; import com.google.inject.Provides; +import com.google.inject.binder.ConstantBindingBuilder; import com.google.inject.name.Names; import com.openosrs.client.config.OpenOSRSConfig; import java.applet.Applet; import java.io.File; +import java.util.Map; import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -69,6 +72,7 @@ public class RuneLiteModule extends AbstractModule { private final OkHttpClient okHttpClient; private final Supplier clientLoader; + private final Supplier configSupplier; private final boolean developerMode; private final boolean safeMode; private final File sessionfile; @@ -77,12 +81,40 @@ public class RuneLiteModule extends AbstractModule @Override protected void configure() { + // bind properties Properties properties = RuneLiteProperties.getProperties(); for (String key : properties.stringPropertyNames()) { String value = properties.getProperty(key); bindConstant().annotatedWith(Names.named(key)).to(value); } + + // bind runtime config + RuntimeConfig runtimeConfig = configSupplier.get(); + if (runtimeConfig != null && runtimeConfig.getProps() != null) + { + for (Map.Entry entry : runtimeConfig.getProps().entrySet()) + { + if (entry.getValue() instanceof String) + { + ConstantBindingBuilder binder = bindConstant().annotatedWith(Names.named(entry.getKey())); + binder.to((String) entry.getValue()); + } + else if (entry.getValue() instanceof Double) + { + ConstantBindingBuilder binder = bindConstant().annotatedWith(Names.named(entry.getKey())); + if (DoubleMath.isMathematicalInteger((double) entry.getValue())) + { + binder.to((int) (double) entry.getValue()); + } + else + { + binder.to((double) entry.getValue()); + } + } + } + } + bindConstant().annotatedWith(Names.named("developerMode")).to(developerMode); bindConstant().annotatedWith(Names.named("safeMode")).to(safeMode); bind(File.class).annotatedWith(Names.named("sessionfile")).toInstance(sessionfile); @@ -122,6 +154,13 @@ public class RuneLiteModule extends AbstractModule return applet instanceof Client ? (Client) applet : null; } + @Provides + @Singleton + RuntimeConfig provideRuntimeConfig() + { + return configSupplier.get(); + } + @Provides @Singleton RuneLiteConfig provideConfig(ConfigManager configManager) diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLiteProperties.java b/runelite-client/src/main/java/net/runelite/client/RuneLiteProperties.java index d20808a68c..2d6a01e1d8 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLiteProperties.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLiteProperties.java @@ -49,6 +49,7 @@ public class RuneLiteProperties private static final String PLUGINHUB_BASE = "runelite.pluginhub.url"; private static final String PLUGINHUB_VERSION = "runelite.pluginhub.version"; private static final String API_BASE = "runelite.api.base"; + private static final String RUNELITE_CONFIG = "runelite.config"; @Getter(AccessLevel.PACKAGE) private static final Properties properties = new Properties(); @@ -142,4 +143,9 @@ public class RuneLiteProperties { return properties.getProperty(API_BASE); } + + public static String getRuneLiteConfig() + { + return properties.getProperty(RUNELITE_CONFIG); + } } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/RuntimeConfig.java b/runelite-client/src/main/java/net/runelite/client/RuntimeConfig.java new file mode 100644 index 0000000000..5d3fb80d94 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/RuntimeConfig.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client; + +import java.util.Collections; +import java.util.Map; +import lombok.Data; + +@Data +public class RuntimeConfig +{ + private Map props = Collections.emptyMap(); +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/RuntimeConfigLoader.java b/runelite-client/src/main/java/net/runelite/client/RuntimeConfigLoader.java new file mode 100644 index 0000000000..aa82fe44dc --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/RuntimeConfigLoader.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2022, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client; + +import com.google.common.base.Strings; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; +import lombok.extern.slf4j.Slf4j; +import net.runelite.http.api.RuneLiteAPI; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +@Slf4j +class RuntimeConfigLoader implements Supplier +{ + private final OkHttpClient okHttpClient; + private final CompletableFuture configFuture; + + public RuntimeConfigLoader(OkHttpClient okHttpClient) + { + this.okHttpClient = okHttpClient; + configFuture = fetch(); + } + + @Override + public RuntimeConfig get() + { + try + { + return configFuture.get(); + } + catch (InterruptedException | ExecutionException e) + { + log.error("error fetching runtime config", e); + return null; + } + } + + private CompletableFuture fetch() + { + CompletableFuture future = new CompletableFuture<>(); + + String prop = System.getProperty("runelite.rtconf"); + if (!Strings.isNullOrEmpty(prop)) + { + try + { + log.info("Using local runtime config"); + + String strConf = new String(Files.readAllBytes(Paths.get(prop)), StandardCharsets.UTF_8); + RuntimeConfig conf = RuneLiteAPI.GSON.fromJson(strConf, RuntimeConfig.class); + future.complete(conf); + return future; + } + catch (IOException e) + { + throw new RuntimeException("failed to load override runtime config", e); + } + } + + Request request = new Request.Builder() + .url(RuneLiteProperties.getRuneLiteConfig()) + .build(); + + okHttpClient.newCall(request).enqueue(new Callback() + { + @Override + public void onFailure(Call call, IOException e) + { + future.completeExceptionally(e); + } + + @Override + public void onResponse(Call call, Response response) + { + try // NOPMD: UseTryWithResources + { + RuntimeConfig config = RuneLiteAPI.GSON.fromJson(response.body().charStream(), RuntimeConfig.class); + future.complete(config); + } + catch (Throwable ex) + { + future.completeExceptionally(ex); + } + finally + { + response.close(); + } + } + }); + return future; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigInvocationHandler.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigInvocationHandler.java index 466bed401d..d032c0b29a 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/ConfigInvocationHandler.java +++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigInvocationHandler.java @@ -63,6 +63,21 @@ class ConfigInvocationHandler implements InvocationHandler Class iface = proxy.getClass().getInterfaces()[0]; + if ("toString".equals(method.getName()) && args == null) + { + return iface.getSimpleName(); + } + + if ("hashCode".equals(method.getName()) && args == null) + { + return System.identityHashCode(proxy); + } + + if ("equals".equals(method.getName()) && args != null && args.length == 1) + { + return proxy == args[0]; + } + ConfigGroup group = iface.getAnnotation(ConfigGroup.class); ConfigItem item = method.getAnnotation(ConfigItem.class); diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java index 1eaf2845c9..fe1b253d8c 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java +++ b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java @@ -28,6 +28,7 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableMap; +import com.google.inject.Inject; import com.openosrs.client.game.ItemReclaimCost; import java.awt.Color; import java.awt.image.BufferedImage; @@ -42,7 +43,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import javax.inject.Inject; +import javax.inject.Named; import javax.inject.Singleton; import lombok.Value; import lombok.extern.slf4j.Slf4j; @@ -85,6 +86,14 @@ public class ItemManager private final ItemClient itemClient; private final RuneLiteConfig runeLiteConfig; + @Inject(optional = true) + @Named("activePriceThreshold") + private double activePriceThreshold = 5; + + @Inject(optional = true) + @Named("lowPriceThreshold") + private int lowPriceThreshold = 1000; + private Map itemPrices = Collections.emptyMap(); private Map itemStats = Collections.emptyMap(); private final LoadingCache itemImages; @@ -167,7 +176,7 @@ public class ItemManager @Inject public ItemManager(Client client, ScheduledExecutorService scheduledExecutorService, ClientThread clientThread, - ItemClient itemClient, RuneLiteConfig runeLiteConfig) + ItemClient itemClient, RuneLiteConfig runeLiteConfig) { this.client = client; this.clientThread = clientThread; @@ -257,7 +266,7 @@ public class ItemManager /** * Look up an item's price * - * @param itemID item id + * @param itemID item id * @param useWikiPrice use the actively traded/wiki price * @return item price */ @@ -289,7 +298,7 @@ public class ItemManager if (ip != null) { - price = useWikiPrice && ip.getWikiPrice() > 0 ? ip.getWikiPrice() : ip.getPrice(); + price = useWikiPrice ? getWikiPrice(ip) : ip.getPrice(); } } else @@ -338,8 +347,31 @@ public class ItemManager return 0; } + /** + * Get the wiki price for an item, with checks to try and avoid excessive price manipulation + * + * @param itemPrice + * @return + */ + public int getWikiPrice(ItemPrice itemPrice) + { + final int wikiPrice = itemPrice.getWikiPrice(); + final int jagPrice = itemPrice.getPrice(); + if (wikiPrice <= 0) + { + return jagPrice; + } + if (wikiPrice <= lowPriceThreshold) + { + return wikiPrice; + } + int d = jagPrice - (int) (jagPrice * activePriceThreshold); + return wikiPrice >= jagPrice - d && wikiPrice <= jagPrice + d ? wikiPrice : jagPrice; + } + /** * Look up an item's stats + * * @param itemId item id * @return item stats */ @@ -479,7 +511,7 @@ public class ItemManager /** * Create item sprite and applies an outline. * - * @param itemId item id + * @param itemId item id * @param itemQuantity item quantity * @param outlineColor outline color * @return image @@ -493,7 +525,7 @@ public class ItemManager /** * Get item outline with a specific color. * - * @param itemId item id + * @param itemId item id * @param itemQuantity item quantity * @param outlineColor outline color * @return image diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java index ac68df9b48..b824454133 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java @@ -35,6 +35,18 @@ public abstract class Plugin implements Module, ExtensionPoint @Getter protected Injector injector; + @Override + public final int hashCode() + { + return super.hashCode(); + } + + @Override + public final boolean equals(Object obj) + { + return super.equals(obj); + } + @Override public void configure(Binder binder) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java index 7e2003efb4..e1be2f6144 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java @@ -26,7 +26,6 @@ package net.runelite.client.plugins; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; -import com.google.common.collect.Lists; import com.google.common.graph.Graph; import com.google.common.graph.GraphBuilder; import com.google.common.graph.Graphs; @@ -354,8 +353,7 @@ public class PluginManager continue; } - Class pluginClass = (Class) clazz; - graph.addNode(pluginClass); + graph.addNode((Class) clazz); } // Build plugin graph @@ -367,7 +365,7 @@ public class PluginManager { if (graph.nodes().contains(pluginDependency.value())) { - graph.putEdge(pluginClazz, pluginDependency.value()); + graph.putEdge(pluginDependency.value(), pluginClazz); } } } @@ -378,7 +376,6 @@ public class PluginManager } List> sortedPlugins = topologicalSort(graph); - sortedPlugins = Lists.reverse(sortedPlugins); int loaded = 0; List newPlugins = new ArrayList<>(); @@ -718,11 +715,14 @@ public class PluginManager /** * Topologically sort a graph. Uses Kahn's algorithm. * - * @param graph - * @param - * @return + * @param graph - A directed graph + * @param - The type of the item contained in the nodes of the graph + * @return - A topologically sorted list corresponding to graph. + *

+ * Multiple invocations with the same arguments may return lists that are not equal. */ - private List topologicalSort(Graph graph) + @VisibleForTesting + static List topologicalSort(Graph graph) { MutableGraph graphCopy = Graphs.copyOf(graph); List l = new ArrayList<>(); @@ -737,7 +737,7 @@ public class PluginManager l.add(n); - for (T m : graphCopy.successors(n)) + for (T m : new HashSet<>(graphCopy.successors(n))) { graphCopy.removeEdge(n, m); if (graphCopy.inDegree(m) == 0) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java index f1e72c9303..3d86d52ad9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java @@ -1287,7 +1287,7 @@ public class ChatCommandsPlugin extends Plugin ItemPrice item = retrieveFromList(results, search); int itemId = item.getId(); - int itemPrice = runeLiteConfig.useWikiItemPrices() && item.getWikiPrice() > 0 ? item.getWikiPrice() : item.getPrice(); + int itemPrice = runeLiteConfig.useWikiItemPrices() ? itemManager.getWikiPrice(item) : item.getPrice(); final ChatMessageBuilder builder = new ChatMessageBuilder() .append(ChatColorType.NORMAL) @@ -1407,21 +1407,11 @@ public class ChatCommandsPlugin extends Plugin return; } - ChatMessageType type = chatMessage.getType(); - - String player; - if (type == ChatMessageType.PRIVATECHATOUT) - { - player = client.getLocalPlayer().getName(); - } - else - { - player = Text.sanitize(chatMessage.getName()); - } + final HiscoreLookup lookup = getCorrectLookupFor(chatMessage); try { - HiscoreResult playerStats = hiscoreClient.lookup(player); + HiscoreResult playerStats = hiscoreClient.lookup(lookup.getName(), lookup.getEndpoint()); if (playerStats == null) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java index 210ee8b935..4941ad5f3b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java @@ -29,7 +29,8 @@ import javax.inject.Inject; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.GameState; -import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.WorldChanged; +import net.runelite.client.callback.ClientThread; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.SessionOpen; @@ -51,27 +52,33 @@ public class DefaultWorldPlugin extends Plugin @Inject private Client client; + @Inject + private ClientThread clientThread; + @Inject private DefaultWorldConfig config; @Inject private WorldService worldService; - private int worldCache; - private boolean worldChangeRequired; - @Override - protected void startUp() throws Exception + protected void startUp() { - worldChangeRequired = true; - applyWorld(); + clientThread.invokeLater(() -> + { + if (client.getGameState().getState() < GameState.LOGIN_SCREEN.getState()) + { + return false; + } + + applyWorld(); + return true; + }); } @Override - protected void shutDown() throws Exception + protected void shutDown() { - worldChangeRequired = true; - changeWorld(worldCache); } @Provides @@ -83,29 +90,30 @@ public class DefaultWorldPlugin extends Plugin @Subscribe public void onSessionOpen(SessionOpen event) { - worldChangeRequired = true; - applyWorld(); + clientThread.invokeLater(this::applyWorld); } @Subscribe - public void onGameStateChanged(GameStateChanged event) + public void onWorldChanged(WorldChanged worldChanged) { - if (event.getGameState() == GameState.LOGGED_IN) - { - config.lastWorld(client.getWorld()); - } - - applyWorld(); + int world = client.getWorld(); + config.lastWorld(world); + log.debug("Saving last world {}", world); } - private void changeWorld(int newWorld) + private void applyWorld() { - if (!worldChangeRequired || client.getGameState() != GameState.LOGIN_SCREEN) + if (client.getGameState() != GameState.LOGIN_SCREEN) { return; } - worldChangeRequired = false; + if (System.getProperty("cli.world") != null) + { + return; + } + + final int newWorld = config.useLastWorld() ? config.lastWorld() : config.getWorld(); int correctedWorld = newWorld < 300 ? newWorld + 300 : newWorld; // Old School RuneScape worlds start on 301 so don't even bother trying to find lower id ones @@ -116,7 +124,6 @@ public class DefaultWorldPlugin extends Plugin } final WorldResult worldResult = worldService.getWorlds(); - if (worldResult == null) { log.warn("Failed to lookup worlds."); @@ -124,40 +131,21 @@ public class DefaultWorldPlugin extends Plugin } final World world = worldResult.findWorld(correctedWorld); - - if (world != null) - { - final net.runelite.api.World rsWorld = client.createWorld(); - rsWorld.setActivity(world.getActivity()); - rsWorld.setAddress(world.getAddress()); - rsWorld.setId(world.getId()); - rsWorld.setPlayerCount(world.getPlayers()); - rsWorld.setLocation(world.getLocation()); - rsWorld.setTypes(WorldUtil.toWorldTypes(world.getTypes())); - - client.changeWorld(rsWorld); - log.debug("Applied new world {}", correctedWorld); - } - else + if (world == null) { log.warn("World {} not found.", correctedWorld); - } - } - - private void applyWorld() - { - if (worldCache == 0) - { - worldCache = client.getWorld(); - log.debug("Stored old world {}", worldCache); - } - - if (System.getProperty("cli.world") != null) - { return; } - final int newWorld = !config.useLastWorld() ? config.getWorld() : config.lastWorld(); - changeWorld(newWorld); + final net.runelite.api.World rsWorld = client.createWorld(); + rsWorld.setActivity(world.getActivity()); + rsWorld.setAddress(world.getAddress()); + rsWorld.setId(world.getId()); + rsWorld.setPlayerCount(world.getPlayers()); + rsWorld.setLocation(world.getLocation()); + rsWorld.setTypes(WorldUtil.toWorldTypes(world.getTypes())); + + client.changeWorld(rsWorld); + log.debug("Applied new world {}", correctedWorld); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java index ae22917b37..9cbfaf667b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java @@ -215,7 +215,7 @@ class GrandExchangeSearchPanel extends JPanel ItemComposition itemComp = itemManager.getItemComposition(itemId); ItemStats itemStats = itemManager.getItemStats(itemId, false); - int itemPrice = useActivelyTradedPrice && item.getWikiPrice() > 0 ? item.getWikiPrice() : item.getPrice(); + int itemPrice = useActivelyTradedPrice ? itemManager.getWikiPrice(item) : item.getPrice(); int itemLimit = itemStats != null ? itemStats.getGeLimit() : 0; final int haPrice = itemComp.getHaPrice(); AsyncBufferedImage itemImage = itemManager.getImage(itemId); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemHotkeyListener.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemHotkeyListener.java new file mode 100644 index 0000000000..a83210b0a7 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemHotkeyListener.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2018, Seth + * 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.grounditems; + +import java.time.Duration; +import java.time.Instant; +import javax.inject.Inject; +import net.runelite.client.util.HotkeyListener; + +class GroundItemHotkeyListener extends HotkeyListener +{ + private final GroundItemsPlugin plugin; + private final GroundItemsConfig config; + + private Instant lastPress; + + @Inject + private GroundItemHotkeyListener(GroundItemsPlugin plugin, GroundItemsConfig config) + { + super(config::hotkey); + + this.plugin = plugin; + this.config = config; + } + + @Override + public void hotkeyPressed() + { + if (plugin.isHideAll()) + { + plugin.setHideAll(false); + plugin.setHotKeyPressed(true); + lastPress = null; + } + else if (lastPress != null && !plugin.isHotKeyPressed() && config.doubleTapDelay() > 0 && Duration.between(lastPress, Instant.now()).compareTo(Duration.ofMillis(config.doubleTapDelay())) < 0) + { + plugin.setHideAll(true); + lastPress = null; + } + else + { + plugin.setHotKeyPressed(true); + lastPress = Instant.now(); + } + } + + @Override + public void hotkeyReleased() + { + plugin.setHotKeyPressed(false); + plugin.setTextBoxBounds(null); + plugin.setHiddenBoxBounds(null); + plugin.setHighlightBoxBounds(null); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemInputListener.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemMouseAdapter.java similarity index 71% rename from runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemInputListener.java rename to runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemMouseAdapter.java index da6c3a80c5..a61be015db 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemInputListener.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemMouseAdapter.java @@ -25,69 +25,16 @@ package net.runelite.client.plugins.grounditems; import java.awt.Point; -import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; -import java.time.Duration; -import java.time.Instant; import javax.inject.Inject; import javax.swing.SwingUtilities; -import net.runelite.client.input.KeyListener; import net.runelite.client.input.MouseAdapter; -public class GroundItemInputListener extends MouseAdapter implements KeyListener +class GroundItemMouseAdapter extends MouseAdapter { - private static final int HOTKEY = KeyEvent.VK_ALT; - - private Instant lastPress; - @Inject private GroundItemsPlugin plugin; - @Inject - private GroundItemsConfig config; - - @Override - public void keyTyped(KeyEvent e) - { - - } - - @Override - public void keyPressed(KeyEvent e) - { - if (e.getKeyCode() == HOTKEY) - { - if (plugin.isHideAll()) - { - plugin.setHideAll(false); - plugin.setHotKeyPressed(true); - lastPress = null; - } - else if (lastPress != null && !plugin.isHotKeyPressed() && config.doubleTapDelay() > 0 && Duration.between(lastPress, Instant.now()).compareTo(Duration.ofMillis(config.doubleTapDelay())) < 0) - { - plugin.setHideAll(true); - lastPress = null; - } - else - { - plugin.setHotKeyPressed(true); - lastPress = Instant.now(); - } - } - } - - @Override - public void keyReleased(KeyEvent e) - { - if (e.getKeyCode() == HOTKEY) - { - plugin.setHotKeyPressed(false); - plugin.setTextBoxBounds(null); - plugin.setHiddenBoxBounds(null); - plugin.setHighlightBoxBounds(null); - } - } - @Override public MouseEvent mousePressed(MouseEvent e) { @@ -134,4 +81,3 @@ public class GroundItemInputListener extends MouseAdapter implements KeyListener return e; } } - diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java index 5869c07208..1031765645 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java @@ -30,8 +30,9 @@ import net.runelite.client.config.Alpha; import net.runelite.client.config.Config; import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigItem; -import net.runelite.client.config.Units; import net.runelite.client.config.ConfigSection; +import net.runelite.client.config.Keybind; +import net.runelite.client.config.Units; import net.runelite.client.plugins.grounditems.config.DespawnTimerMode; import net.runelite.client.plugins.grounditems.config.HighlightTier; import net.runelite.client.plugins.grounditems.config.ItemHighlightMode; @@ -447,4 +448,15 @@ public interface GroundItemsConfig extends Config { return Lootbeam.Style.MODERN; } + + @ConfigItem( + keyName = "hotkey", + name = "Hotkey", + description = "Configures the hotkey used by the Ground Items plugin", + position = 33 + ) + default Keybind hotkey() + { + return Keybind.ALT; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java index af5282db14..c4524bf353 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java @@ -140,7 +140,10 @@ public class GroundItemsPlugin extends Plugin private List highlightedItemsList = new CopyOnWriteArrayList<>(); @Inject - private GroundItemInputListener inputListener; + private GroundItemHotkeyListener hotkeyListener; + + @Inject + private GroundItemMouseAdapter mouseAdapter; @Inject private MouseManager mouseManager; @@ -191,8 +194,8 @@ public class GroundItemsPlugin extends Plugin protected void startUp() { overlayManager.add(overlay); - mouseManager.registerMouseListener(inputListener); - keyManager.registerKeyListener(inputListener); + mouseManager.registerMouseListener(mouseAdapter); + keyManager.registerKeyListener(hotkeyListener); executor.execute(this::reset); lastUsedItem = -1; } @@ -201,8 +204,8 @@ public class GroundItemsPlugin extends Plugin protected void shutDown() { overlayManager.remove(overlay); - mouseManager.unregisterMouseListener(inputListener); - keyManager.unregisterKeyListener(inputListener); + mouseManager.unregisterMouseListener(mouseAdapter); + keyManager.unregisterKeyListener(hotkeyListener); highlightedItems.invalidateAll(); highlightedItems = null; hiddenItems.invalidateAll(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightOverlay.java index 0a14053e36..e401f39d9b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightOverlay.java @@ -33,6 +33,7 @@ import net.runelite.api.Client; import net.runelite.api.MenuAction; import net.runelite.api.MenuEntry; import net.runelite.api.NPC; +import net.runelite.api.Point; import net.runelite.api.TileObject; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; @@ -78,8 +79,8 @@ class InteractHighlightOverlay extends Overlay return; } - MenuEntry top = menuEntries[menuEntries.length - 1]; - MenuAction menuAction = top.getType(); + MenuEntry entry = client.isMenuOpen() ? hoveredMenuEntry(menuEntries) : menuEntries[menuEntries.length - 1]; + MenuAction menuAction = entry.getType(); switch (menuAction) { @@ -90,10 +91,11 @@ class InteractHighlightOverlay extends Overlay case GAME_OBJECT_THIRD_OPTION: case GAME_OBJECT_FOURTH_OPTION: case GAME_OBJECT_FIFTH_OPTION: + case EXAMINE_OBJECT: { - int x = top.getParam0(); - int y = top.getParam1(); - int id = top.getIdentifier(); + int x = entry.getParam0(); + int y = entry.getParam1(); + int id = entry.getIdentifier(); TileObject tileObject = plugin.findTileObject(x, y, id); if (tileObject != null && config.objectShowHover() && (tileObject != plugin.getInteractedObject() || !config.objectShowInteract())) { @@ -108,8 +110,9 @@ class InteractHighlightOverlay extends Overlay case NPC_THIRD_OPTION: case NPC_FOURTH_OPTION: case NPC_FIFTH_OPTION: + case EXAMINE_NPC: { - int id = top.getIdentifier(); + int id = entry.getIdentifier(); NPC npc = plugin.findNpc(id); if (npc != null && config.npcShowHover() && (npc != plugin.getInteractedTarget() || !config.npcShowInteract())) { @@ -155,4 +158,29 @@ class InteractHighlightOverlay extends Overlay } return end; } + + private MenuEntry hoveredMenuEntry(final MenuEntry[] menuEntries) + { + final int menuX = client.getMenuX(); + final int menuY = client.getMenuY(); + final int menuWidth = client.getMenuWidth(); + final Point mousePosition = client.getMouseCanvasPosition(); + + int dy = mousePosition.getY() - menuY; + dy -= 19; // Height of Choose Option + if (dy < 0) + { + return menuEntries[menuEntries.length - 1]; + } + + int idx = dy / 15; // Height of each menu option + idx = menuEntries.length - 1 - idx; + + if (mousePosition.getX() > menuX && mousePosition.getX() < menuX + menuWidth + && idx >= 0 && idx < menuEntries.length) + { + return menuEntries[idx]; + } + return menuEntries[menuEntries.length - 1]; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/Tree.java b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/Tree.java index f87bcaeef3..28f90cfc6f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/Tree.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/Tree.java @@ -34,18 +34,23 @@ import static net.runelite.api.ObjectID.MAGIC_TREE_10834; import static net.runelite.api.NullObjectID.NULL_10835; import static net.runelite.api.ObjectID.MAHOGANY; import static net.runelite.api.ObjectID.MAHOGANY_36688; +import static net.runelite.api.ObjectID.MAHOGANY_40760; import static net.runelite.api.ObjectID.MAPLE_TREE_10832; import static net.runelite.api.ObjectID.MAPLE_TREE_36681; +import static net.runelite.api.ObjectID.MAPLE_TREE_40754; import static net.runelite.api.ObjectID.OAK_10820; import static net.runelite.api.ObjectID.OAK_TREE_4540; import static net.runelite.api.ObjectID.REDWOOD_29670; import static net.runelite.api.ObjectID.TEAK; import static net.runelite.api.ObjectID.TEAK_36686; +import static net.runelite.api.ObjectID.TEAK_40758; import static net.runelite.api.ObjectID.TREE; import static net.runelite.api.ObjectID.TREE_1277; import static net.runelite.api.ObjectID.TREE_1278; import static net.runelite.api.ObjectID.TREE_1279; import static net.runelite.api.ObjectID.TREE_1280; +import static net.runelite.api.ObjectID.TREE_40750; +import static net.runelite.api.ObjectID.TREE_40752; import static net.runelite.api.ObjectID.WILLOW; import static net.runelite.api.ObjectID.WILLOW_10829; import static net.runelite.api.ObjectID.WILLOW_10831; @@ -53,15 +58,16 @@ import static net.runelite.api.ObjectID.WILLOW_10833; import static net.runelite.api.ObjectID.YEW; import static net.runelite.api.NullObjectID.NULL_10823; import static net.runelite.api.ObjectID.YEW_36683; +import static net.runelite.api.ObjectID.YEW_40756; import static net.runelite.client.util.RSTimeUnit.GAME_TICKS; @Getter enum Tree { - REGULAR_TREE(null, TREE, TREE_1277, TREE_1278, TREE_1279, TREE_1280), + REGULAR_TREE(null, TREE, TREE_1277, TREE_1278, TREE_1279, TREE_1280, TREE_40750, TREE_40752), OAK_TREE(Duration.of(14, GAME_TICKS), OAK_TREE_4540, OAK_10820), WILLOW_TREE(Duration.of(14, GAME_TICKS), WILLOW, WILLOW_10829, WILLOW_10831, WILLOW_10833), - MAPLE_TREE(Duration.of(59, GAME_TICKS), MAPLE_TREE_10832, MAPLE_TREE_36681) + MAPLE_TREE(Duration.of(59, GAME_TICKS), MAPLE_TREE_10832, MAPLE_TREE_36681, MAPLE_TREE_40754) { @Override Duration getRespawnTime(int region) @@ -69,9 +75,9 @@ enum Tree return region == MISCELLANIA_REGION ? Duration.of(14, GAME_TICKS) : super.respawnTime; } }, - TEAK_TREE(Duration.of(15, GAME_TICKS), TEAK, TEAK_36686), - MAHOGANY_TREE(Duration.of(14, GAME_TICKS), MAHOGANY, MAHOGANY_36688), - YEW_TREE(Duration.of(99, GAME_TICKS), YEW, NULL_10823, YEW_36683), + TEAK_TREE(Duration.of(15, GAME_TICKS), TEAK, TEAK_36686, TEAK_40758), + MAHOGANY_TREE(Duration.of(14, GAME_TICKS), MAHOGANY, MAHOGANY_36688, MAHOGANY_40760), + YEW_TREE(Duration.of(99, GAME_TICKS), YEW, NULL_10823, YEW_36683, YEW_40756), MAGIC_TREE(Duration.of(199, GAME_TICKS), MAGIC_TREE_10834, NULL_10835), REDWOOD(Duration.of(199, GAME_TICKS), ObjectID.REDWOOD, REDWOOD_29670); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpInfoBox.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpInfoBox.java index dbcfd6ddbd..8f6f9d6dbc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpInfoBox.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpInfoBox.java @@ -124,7 +124,7 @@ class XpInfoBox extends JPanel // Create open xp tracker menu final JMenuItem openXpTracker = new JMenuItem("Open Wise Old Man"); openXpTracker.addActionListener(e -> LinkBrowser.browse(XpPanel.buildXpTrackerUrl( - client.getLocalPlayer(), skill))); + client.getWorldType(), client.getLocalPlayer(), skill))); // Create reset menu final JMenuItem reset = new JMenuItem("Reset"); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpPanel.java index ed72128ef4..0ecce8290d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpPanel.java @@ -29,6 +29,7 @@ import java.awt.BorderLayout; import java.awt.GridLayout; import java.util.HashMap; import java.util.Map; +import java.util.Set; import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JComponent; @@ -41,6 +42,7 @@ import javax.swing.border.EmptyBorder; import net.runelite.api.Actor; import net.runelite.api.Client; import net.runelite.api.Skill; +import net.runelite.api.WorldType; import net.runelite.client.game.SkillIconManager; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.FontManager; @@ -83,7 +85,7 @@ class XpPanel extends PluginPanel // Create open xp tracker menu final JMenuItem openXpTracker = new JMenuItem("Open Wise Old Man"); openXpTracker.addActionListener(e -> LinkBrowser.browse(XpPanel.buildXpTrackerUrl( - client.getLocalPlayer(), Skill.OVERALL))); + client.getWorldType(), client.getLocalPlayer(), Skill.OVERALL))); // Create reset all menu final JMenuItem reset = new JMenuItem("Reset All"); @@ -146,7 +148,7 @@ class XpPanel extends PluginPanel add(errorPanel); } - static String buildXpTrackerUrl(final Actor player, final Skill skill) + static String buildXpTrackerUrl(final Set worldTypes, final Actor player, final Skill skill) { if (player == null) { @@ -155,7 +157,7 @@ class XpPanel extends PluginPanel return new HttpUrl.Builder() .scheme("https") - .host("wiseoldman.net") + .host(worldTypes.contains(WorldType.SEASONAL) ? "seasonal.wiseoldman.net" : "wiseoldman.net") .addPathSegment("players") .addPathSegment(player.getName()) .addPathSegment("gained") diff --git a/runelite-client/src/main/java/net/runelite/client/util/HotkeyListener.java b/runelite-client/src/main/java/net/runelite/client/util/HotkeyListener.java index 4db58ccf98..090c0d21e9 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/HotkeyListener.java +++ b/runelite-client/src/main/java/net/runelite/client/util/HotkeyListener.java @@ -89,6 +89,7 @@ public abstract class HotkeyListener implements KeyListener } isPressed = false; isConsumingTyped = false; + hotkeyReleased(); } } @@ -99,4 +100,8 @@ public abstract class HotkeyListener implements KeyListener protected void hotkeyReleased() { } + + public void hotkeyReleased() + { + } } diff --git a/runelite-client/src/main/resources/net/runelite/client/runelite.properties b/runelite-client/src/main/resources/net/runelite/client/runelite.properties index 262089b836..b719012af5 100644 --- a/runelite-client/src/main/resources/net/runelite/client/runelite.properties +++ b/runelite-client/src/main/resources/net/runelite/client/runelite.properties @@ -17,4 +17,5 @@ runelite.imgur.client.id=30d71e5f6860809 runelite.api.base=https://api.runelite.net/runelite-@project.version@ runelite.session=https://session.openosrs.dev runelite.static.base=https://static.runelite.net -runelite.ws=https://api.runelite.net/ws \ No newline at end of file +runelite.ws=https://api.runelite.net/ws +runelite.config=https://static.runelite.net/config.json \ No newline at end of file diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/PluginManagerTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/PluginManagerTest.java index 50692a915a..3166552b6a 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/PluginManagerTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/PluginManagerTest.java @@ -24,6 +24,8 @@ */ package net.runelite.client.plugins; +import com.google.common.graph.GraphBuilder; +import com.google.common.graph.MutableGraph; import com.google.common.reflect.ClassPath; import com.google.common.reflect.ClassPath.ClassInfo; import com.google.inject.Guice; @@ -40,6 +42,7 @@ import java.io.PrintWriter; import java.lang.reflect.Method; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Objects; import java.util.Set; import net.runelite.api.Client; @@ -51,6 +54,7 @@ import net.runelite.client.eventbus.EventBus; import okhttp3.OkHttpClient; import okhttp3.Request; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -89,7 +93,7 @@ public class PluginManagerTest .thenThrow(new RuntimeException("in plugin manager test")); Injector injector = Guice.createInjector(Modules - .override(new RuneLiteModule(okHttpClient, () -> null, true, false, + .override(new RuneLiteModule(okHttpClient, () -> null, () -> null, true, false, RuneLite.DEFAULT_SESSION_FILE, RuneLite.DEFAULT_CONFIG_FILE)) .with(BoundFieldModule.of(this))); @@ -208,4 +212,23 @@ public class PluginManagerTest } } + @Test + public void testTopologicalSort() + { + MutableGraph graph = GraphBuilder + .directed() + .build(); + + graph.addNode(1); + graph.addNode(2); + graph.addNode(3); + + graph.putEdge(1, 2); + graph.putEdge(1, 3); + + List sorted = PluginManager.topologicalSort(graph); + + assertTrue(sorted.indexOf(1) < sorted.indexOf(2)); + assertTrue(sorted.indexOf(1) < sorted.indexOf(3)); + } }