From 3e4f61b40642836a9478518d0dbd5fc103572664 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Thu, 8 Aug 2019 02:45:15 -0600 Subject: [PATCH 1/9] ClanManager: handle startup with an empty cache --- .../net/runelite/client/game/ClanManager.java | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/game/ClanManager.java b/runelite-client/src/main/java/net/runelite/client/game/ClanManager.java index 0b80bbc7e9..2a88aab050 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/ClanManager.java +++ b/runelite-client/src/main/java/net/runelite/client/game/ClanManager.java @@ -34,6 +34,7 @@ import java.util.Arrays; import java.util.Objects; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; import net.runelite.api.ClanMember; @@ -94,7 +95,7 @@ public class ClanManager } }); - private int modIconsLength; + private int offset; @Inject private ClanManager(Client client, SpriteManager spriteManager) @@ -108,6 +109,7 @@ public class ClanManager return clanRanksCache.getUnchecked(playerName); } + @Nullable public BufferedImage getClanImage(final ClanMemberRank clanMemberRank) { if (clanMemberRank == ClanMemberRank.UNRANKED) @@ -120,14 +122,13 @@ public class ClanManager public int getIconNumber(final ClanMemberRank clanMemberRank) { - return modIconsLength - CLANCHAT_IMAGES.length + clanMemberRank.ordinal() - 1; + return offset + clanMemberRank.ordinal() - 1; } @Subscribe public void onGameStateChanged(GameStateChanged gameStateChanged) { - if (gameStateChanged.getGameState() == GameState.LOGGED_IN - && modIconsLength == 0) + if (gameStateChanged.getGameState() == GameState.LOGIN_SCREEN && offset == 0) { loadClanChatIcons(); } @@ -141,19 +142,31 @@ public class ClanManager private void loadClanChatIcons() { - final IndexedSprite[] modIcons = client.getModIcons(); - final IndexedSprite[] newModIcons = Arrays.copyOf(modIcons, modIcons.length + CLANCHAT_IMAGES.length); - int curPosition = newModIcons.length - CLANCHAT_IMAGES.length; - - for (int i = 0; i < CLANCHAT_IMAGES.length; i++, curPosition++) { - final int resource = CLANCHAT_IMAGES[i]; - clanChatImages[i] = clanChatImageFromSprite(spriteManager.getSprite(resource, 0)); - newModIcons[curPosition] = ImageUtil.getImageIndexedSprite(clanChatImages[i], client); + IndexedSprite[] modIcons = client.getModIcons(); + offset = modIcons.length; + + IndexedSprite blank = ImageUtil.getImageIndexedSprite( + new BufferedImage(modIcons[0].getWidth(), modIcons[0].getHeight(), BufferedImage.TYPE_INT_ARGB), + client); + + modIcons = Arrays.copyOf(modIcons, offset + CLANCHAT_IMAGES.length); + Arrays.fill(modIcons, offset, modIcons.length, blank); + + client.setModIcons(modIcons); } - client.setModIcons(newModIcons); - modIconsLength = newModIcons.length; + for (int i = 0; i < CLANCHAT_IMAGES.length; i++) + { + final int fi = i; + + spriteManager.getSpriteAsync(CLANCHAT_IMAGES[i], 0, sprite -> + { + IndexedSprite[] modIcons = client.getModIcons(); + clanChatImages[fi] = clanChatImageFromSprite(sprite); + modIcons[offset + fi] = ImageUtil.getImageIndexedSprite(clanChatImages[fi], client); + }); + } } private static String sanitize(String lookup) From a019d39361232aba563f5e74b387626470c90003 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Fri, 9 Aug 2019 05:29:15 -0600 Subject: [PATCH 2/9] runelite-api: Annotate script ids with their argument counts The cache-code updater can update these annotations so we know when a script we call require changes --- .../net/runelite/api/ScriptArguments.java | 47 +++++++++++++++++++ .../main/java/net/runelite/api/ScriptID.java | 23 +++++++-- 2 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 runelite-api/src/main/java/net/runelite/api/ScriptArguments.java diff --git a/runelite-api/src/main/java/net/runelite/api/ScriptArguments.java b/runelite-api/src/main/java/net/runelite/api/ScriptArguments.java new file mode 100644 index 0000000000..c3e89f56a4 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/ScriptArguments.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Abex + * 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.api; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Documented +@Target(ElementType.FIELD) +@interface ScriptArguments +{ + /** + * The number of int arguments the script takes + */ + int integer() default 0; + + /** + * The number of string arguments the script takes + */ + int string() default 0; +} diff --git a/runelite-api/src/main/java/net/runelite/api/ScriptID.java b/runelite-api/src/main/java/net/runelite/api/ScriptID.java index 355aa5cffa..671c0c4e23 100644 --- a/runelite-api/src/main/java/net/runelite/api/ScriptID.java +++ b/runelite-api/src/main/java/net/runelite/api/ScriptID.java @@ -34,6 +34,7 @@ public final class ScriptID *
  • int how far down to scroll
  • * */ + @ScriptArguments(integer = 3) public static final int UPDATE_SCROLLBAR = 72; /** @@ -43,11 +44,13 @@ public final class ScriptID *
  • String Message to send
  • * */ + @ScriptArguments(integer = 1, string = 1) public static final int CHATBOX_INPUT = 96; /** * Rebuilds the chatbox */ + @ScriptArguments() public static final int BUILD_CHATBOX = 216; /** @@ -58,6 +61,7 @@ public final class ScriptID *
  • String Player to send private message to
  • * */ + @ScriptArguments(string = 1) public static final int OPEN_PRIVATE_MESSAGE_INTERFACE = 107; /** @@ -66,6 +70,7 @@ public final class ScriptID *
  • String Message Prefix. Only used inside the GE search interfaces * */ + @ScriptArguments(string = 1) public static final int CHAT_TEXT_INPUT_REBUILD = 222; /** @@ -73,6 +78,7 @@ public final class ScriptID * * Takes 13 widget ids of various parts of the bank interface */ + @ScriptArguments(integer = 17) public static final int BANK_LAYOUT = 277; /** @@ -82,17 +88,20 @@ public final class ScriptID *
  • int (boolean) Restore to chat view
  • * */ + @ScriptArguments(integer = 2) public static final int RESET_CHATBOX_INPUT = 299; /** * Readies the chatbox panel for things like the chatbox input * Inverse of RESET_CHATBOX_INPUT */ + @ScriptArguments(integer = 1) public static final int CLEAR_CHATBOX_PANEL = 677; /** * Builds the chatbox input widget */ + @ScriptArguments() public static final int CHAT_PROMPT_INIT = 223; /** @@ -103,19 +112,21 @@ public final class ScriptID *
  • String Item Name
  • * */ + @ScriptArguments(integer = 2, string = 1) public static final int DEATH_KEEP_ITEM_EXAMINE = 1603; /** * Checks the state of the given stash unit. * * * Returns a pair of booleans indicating if the stash unit is built and if it is filled */ + @ScriptArguments(integer = 4) public static final int WATSON_STASH_UNIT_CHECK = 1479; /** @@ -128,6 +139,7 @@ public final class ScriptID *
  • int (QuestState) the normalized state of the quest * */ + @ScriptArguments(integer = 1) public static final int QUESTLIST_PROGRESS = 2267; /** @@ -137,6 +149,7 @@ public final class ScriptID *
  • int Number of lines
  • * */ + @ScriptArguments(integer = 2) public static final int DIARY_QUEST_UPDATE_LINECOUNT = 2523; /** @@ -148,6 +161,7 @@ public final class ScriptID *
  • int Reset zoom position
  • * */ + @ScriptArguments(integer = 2) public static final int CAMERA_DO_ZOOM = 42; /** @@ -156,11 +170,13 @@ public final class ScriptID * This is used to eat events when you want a menu action attached to it * because you need an op listener attached to it for it to work */ + @ScriptArguments() public static final int NULL = 10003; /** * Send a private message. */ + @ScriptArguments(string = 2) public static final int PRIVMSG = 10004; /** @@ -171,5 +187,6 @@ public final class ScriptID *
  • int Amount of exp to drop
  • * */ + @ScriptArguments(integer = 2) public static final int XPDROP_DISABLED = 2091; } From 4cec794a29096b97ddf6da42164a1a8d5ec29114 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Fri, 9 Aug 2019 08:05:49 -0600 Subject: [PATCH 3/9] runelite-api: allow runScript to take a plain Object... --- runelite-api/src/main/java/net/runelite/api/Client.java | 5 ++--- .../src/main/java/net/runelite/api/widgets/Widget.java | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/runelite-api/src/main/java/net/runelite/api/Client.java b/runelite-api/src/main/java/net/runelite/api/Client.java index 95cf40931a..7e315eb39b 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -1253,11 +1253,10 @@ public interface Client extends GameEngine * * This method must be ran on the client thread and is not reentrant * - * @param id the script ID - * @param args additional arguments to execute the script with + * @param args the script id, then any additional arguments to execute the script with * @see ScriptID */ - void runScript(int id, Object... args); + void runScript(Object... args); /** * Checks whether or not there is any active hint arrow. diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java b/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java index ed4277a2a3..e4ad3f3baa 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java @@ -622,6 +622,8 @@ public interface Widget */ Object[] getOnLoadListener(); + Object[] getOnInvTransmitListener(); + /** * Returns the archive id of the font used * From 56f7d09c923751d1b41dae225946bb53c229d2ef Mon Sep 17 00:00:00 2001 From: Max Weber Date: Fri, 9 Aug 2019 08:06:59 -0600 Subject: [PATCH 4/9] runelite-client: Call scripts with the correct number of arguments --- .../main/java/net/runelite/api/ScriptID.java | 17 +++----- .../java/net/runelite/api/VarClientInt.java | 4 ++ .../game/chatbox/ChatboxPanelManager.java | 4 +- .../plugins/banktags/tabs/BankSearch.java | 43 ++++--------------- .../plugins/banktags/tabs/TabInterface.java | 2 +- .../ChatboxPerformancePlugin.java | 4 +- 6 files changed, 23 insertions(+), 51 deletions(-) diff --git a/runelite-api/src/main/java/net/runelite/api/ScriptID.java b/runelite-api/src/main/java/net/runelite/api/ScriptID.java index 671c0c4e23..657ec2dcb8 100644 --- a/runelite-api/src/main/java/net/runelite/api/ScriptID.java +++ b/runelite-api/src/main/java/net/runelite/api/ScriptID.java @@ -73,14 +73,6 @@ public final class ScriptID @ScriptArguments(string = 1) public static final int CHAT_TEXT_INPUT_REBUILD = 222; - /** - * Layouts the bank widgets - * - * Takes 13 widget ids of various parts of the bank interface - */ - @ScriptArguments(integer = 17) - public static final int BANK_LAYOUT = 277; - /** * Closes the chatbox input *
      @@ -89,14 +81,17 @@ public final class ScriptID *
    */ @ScriptArguments(integer = 2) - public static final int RESET_CHATBOX_INPUT = 299; + public static final int MESSAGE_LAYER_CLOSE = 299; /** * Readies the chatbox panel for things like the chatbox input - * Inverse of RESET_CHATBOX_INPUT + * Inverse of MESSAGE_LAYER_CLOSE + *
      + *
    • int (InputType) message layer type we are changing to
    • + *
    */ @ScriptArguments(integer = 1) - public static final int CLEAR_CHATBOX_PANEL = 677; + public static final int MESSAGE_LAYER_OPEN = 677; /** * Builds the chatbox input widget diff --git a/runelite-api/src/main/java/net/runelite/api/VarClientInt.java b/runelite-api/src/main/java/net/runelite/api/VarClientInt.java index 0652f3b0e0..09945a1137 100644 --- a/runelite-api/src/main/java/net/runelite/api/VarClientInt.java +++ b/runelite-api/src/main/java/net/runelite/api/VarClientInt.java @@ -42,6 +42,10 @@ public enum VarClientInt */ TOOLTIP_VISIBLE(2), + /** + * Current message layer mode + * @see net.runelite.api.vars.InputType + */ INPUT_TYPE(5), MEMBERSHIP_STATUS(103), diff --git a/runelite-client/src/main/java/net/runelite/client/game/chatbox/ChatboxPanelManager.java b/runelite-client/src/main/java/net/runelite/client/game/chatbox/ChatboxPanelManager.java index ff23bb2cda..e9d7c5641c 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/chatbox/ChatboxPanelManager.java +++ b/runelite-client/src/main/java/net/runelite/client/game/chatbox/ChatboxPanelManager.java @@ -89,7 +89,7 @@ public class ChatboxPanelManager private void unsafeCloseInput() { client.runScript( - ScriptID.RESET_CHATBOX_INPUT, + ScriptID.MESSAGE_LAYER_CLOSE, 0, 1 ); @@ -101,7 +101,7 @@ public class ChatboxPanelManager private void unsafeOpenInput(ChatboxInput input) { - client.runScript(ScriptID.CLEAR_CHATBOX_PANEL); + client.runScript(ScriptID.MESSAGE_LAYER_OPEN, 0); eventBus.register(input); if (input instanceof KeyListener) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/BankSearch.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/BankSearch.java index 73bfbc99a4..10a1ed2004 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/BankSearch.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/BankSearch.java @@ -37,20 +37,6 @@ import net.runelite.client.callback.ClientThread; public class BankSearch { - // Widget indexes for searching - private static final int INNER_CONTAINER_IDX = 2; - private static final int SETTINGS_IDX = 4; - private static final int ITEM_CONTAINER_IDX = 7; - private static final int SCROLLBAR_IDX = 8; - private static final int BOTTOM_BAR_IDX = 9; - private static final int SEARCH_BUTTON_BACKGROUND_IDX = 15; - private static final int TITLE_BAR_IDX = 16; - private static final int ITEM_COUNT_IDX = 17; - private static final int TAB_BAR_IDX = 18; - private static final int INCINERATOR_IDX = 19; - private static final int INCINERATOR_CONFIRM_IDX = 20; - private static final int HIDDEN_WIDGET_IDX = 21; - private final Client client; private final ClientThread clientThread; @@ -64,51 +50,38 @@ public class BankSearch this.clientThread = clientThread; } - public void search(InputType inputType, String search, Boolean closeInput) + public void search(InputType inputType, String search, boolean closeInput) { clientThread.invoke(() -> { - Widget bankContainer = client.getWidget(WidgetInfo.BANK_CONTAINER); + Widget bankContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); if (bankContainer == null || bankContainer.isHidden()) { return; } - Object[] widgetIds = bankContainer.getOnLoadListener(); + Object[] scriptArgs = bankContainer.getOnInvTransmitListener(); - // In case the widget ids array is incorrect, do not proceed - if (widgetIds == null || widgetIds.length < 21) + if (scriptArgs == null) { return; } + // This ensures that any chatbox input (e.g from search) will not remain visible when // selecting/changing tab if (closeInput) { - client.runScript(ScriptID.RESET_CHATBOX_INPUT, 0, 0); + client.runScript(ScriptID.MESSAGE_LAYER_CLOSE, 0, 0); } client.setVar(VarClientInt.INPUT_TYPE, inputType.getType()); client.setVar(VarClientStr.INPUT_TEXT, search); - client.runScript(ScriptID.BANK_LAYOUT, - WidgetInfo.BANK_CONTAINER.getId(), - widgetIds[INNER_CONTAINER_IDX], - widgetIds[SETTINGS_IDX], - widgetIds[ITEM_CONTAINER_IDX], - widgetIds[SCROLLBAR_IDX], - widgetIds[BOTTOM_BAR_IDX], - widgetIds[TITLE_BAR_IDX], - widgetIds[ITEM_COUNT_IDX], - widgetIds[SEARCH_BUTTON_BACKGROUND_IDX], - widgetIds[TAB_BAR_IDX], - widgetIds[INCINERATOR_IDX], - widgetIds[INCINERATOR_CONFIRM_IDX], - widgetIds[HIDDEN_WIDGET_IDX]); + client.runScript(scriptArgs); }); } - public void reset(Boolean closeChat) + public void reset(boolean closeChat) { search(InputType.NONE, "", closeChat); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java index 14b6ee9178..46b4eef567 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java @@ -336,7 +336,7 @@ public class TabInterface { bankSearch.reset(true); - clientThread.invokeLater(() -> client.runScript(ScriptID.RESET_CHATBOX_INPUT, 0, 0)); + clientThread.invokeLater(() -> client.runScript(ScriptID.MESSAGE_LAYER_CLOSE, 0, 0)); } else { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatboxperformance/ChatboxPerformancePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatboxperformance/ChatboxPerformancePlugin.java index a548bb44ff..da642e1d43 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatboxperformance/ChatboxPerformancePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatboxperformance/ChatboxPerformancePlugin.java @@ -56,7 +56,7 @@ public class ChatboxPerformancePlugin extends Plugin { if (client.getGameState() == GameState.LOGGED_IN) { - clientThread.invokeLater(() -> client.runScript(ScriptID.RESET_CHATBOX_INPUT)); + clientThread.invokeLater(() -> client.runScript(ScriptID.MESSAGE_LAYER_CLOSE, 0, 0)); } } @@ -65,7 +65,7 @@ public class ChatboxPerformancePlugin extends Plugin { if (client.getGameState() == GameState.LOGGED_IN) { - clientThread.invokeLater(() -> client.runScript(ScriptID.RESET_CHATBOX_INPUT)); + clientThread.invokeLater(() -> client.runScript(ScriptID.MESSAGE_LAYER_CLOSE, 0, 0)); } } From 3757c3bae68e951eecb702eee2845e4ce6b5ed6f Mon Sep 17 00:00:00 2001 From: Max Weber Date: Wed, 7 Aug 2019 02:40:39 -0600 Subject: [PATCH 5/9] runelite-client: Add loading splash screen --- .../java/net/runelite/client/RuneLite.java | 79 +++--- .../client/plugins/PluginManager.java | 12 +- .../net/runelite/client/rs/ClientLoader.java | 30 ++- .../java/net/runelite/client/ui/ClientUI.java | 15 +- .../net/runelite/client/ui/SplashScreen.java | 232 ++++++++++++++++++ .../client/ui/runelite_transparent.png | Bin 0 -> 31065 bytes 6 files changed, 331 insertions(+), 37 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/ui/SplashScreen.java create mode 100644 runelite-client/src/main/resources/net/runelite/client/ui/runelite_transparent.png 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 e309734264..e8b4d60a43 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -60,6 +60,7 @@ import net.runelite.client.rs.ClientLoader; import net.runelite.client.rs.ClientUpdateCheckMode; import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.DrawManager; +import net.runelite.client.ui.SplashScreen; import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.ui.overlay.OverlayRenderer; import net.runelite.client.ui.overlay.WidgetOverlay; @@ -197,40 +198,50 @@ public class RuneLite } }); - final ClientLoader clientLoader = new ClientLoader(options.valueOf(updateMode)); + SplashScreen.init(); + SplashScreen.stage(0, "Retrieving client", ""); - new Thread(() -> + try { - clientLoader.get(); - ClassPreloader.preload(); - }, "Preloader").start(); + final ClientLoader clientLoader = new ClientLoader(options.valueOf(updateMode)); - final boolean developerMode = options.has("developer-mode") && RuneLiteProperties.getLauncherVersion() == null; - - if (developerMode) - { - boolean assertions = false; - assert assertions = true; - if (!assertions) + new Thread(() -> { - throw new RuntimeException("Developers should enable assertions; Add `-ea` to your JVM arguments`"); + clientLoader.get(); + ClassPreloader.preload(); + }, "Preloader").start(); + + final boolean developerMode = options.has("developer-mode") && RuneLiteProperties.getLauncherVersion() == null; + + if (developerMode) + { + boolean assertions = false; + assert assertions = true; + if (!assertions) + { + throw new RuntimeException("Developers should enable assertions; Add `-ea` to your JVM arguments`"); + } } + + PROFILES_DIR.mkdirs(); + + final long start = System.currentTimeMillis(); + + injector = Guice.createInjector(new RuneLiteModule( + clientLoader, + developerMode)); + + injector.getInstance(RuneLite.class).start(); + + final long end = System.currentTimeMillis(); + final RuntimeMXBean rb = ManagementFactory.getRuntimeMXBean(); + final long uptime = rb.getUptime(); + log.info("Client initialization took {}ms. Uptime: {}ms", end - start, uptime); + } + finally + { + SplashScreen.stop(); } - - PROFILES_DIR.mkdirs(); - - final long start = System.currentTimeMillis(); - - injector = Guice.createInjector(new RuneLiteModule( - clientLoader, - developerMode)); - - injector.getInstance(RuneLite.class).start(); - - final long end = System.currentTimeMillis(); - final RuntimeMXBean rb = ManagementFactory.getRuntimeMXBean(); - final long uptime = rb.getUptime(); - log.info("Client initialization took {}ms. Uptime: {}ms", end - start, uptime); } public void start() throws Exception @@ -244,6 +255,8 @@ public class RuneLite injector.injectMembers(client); } + SplashScreen.stage(.57, null, "Loading configuration"); + // Load user configuration configManager.load(); @@ -257,6 +270,8 @@ public class RuneLite // This will initialize configuration pluginManager.loadCorePlugins(); + SplashScreen.stage(.70, null, "Finalizing configuration"); + // Plugins have provided their config, so set default config // to main settings pluginManager.loadDefaultPluginConfiguration(); @@ -264,8 +279,10 @@ public class RuneLite // Start client session clientSessionManager.start(); + SplashScreen.stage(.75, null, "Starting core interface"); + // Initialize UI - clientUI.open(this); + clientUI.init(this); // Initialize Discord service discordService.init(); @@ -301,6 +318,10 @@ public class RuneLite // Start plugins pluginManager.startCorePlugins(); + + SplashScreen.stop(); + + clientUI.show(); } public void shutdown() 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 73d6bf32e4..b820f2d6b3 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 @@ -70,6 +70,7 @@ import net.runelite.client.events.PluginChanged; import net.runelite.client.task.Schedule; import net.runelite.client.task.ScheduledMethod; import net.runelite.client.task.Scheduler; +import net.runelite.client.ui.SplashScreen; import net.runelite.client.util.GameEventManager; @Singleton @@ -200,6 +201,7 @@ public class PluginManager public void startCorePlugins() { List scannedPlugins = new ArrayList<>(plugins); + int loaded = 0; for (Plugin plugin : scannedPlugins) { try @@ -211,11 +213,15 @@ public class PluginManager log.warn("Unable to start plugin {}. {}", plugin.getClass().getSimpleName(), ex); plugins.remove(plugin); } + + loaded++; + SplashScreen.stage(.80, 1, null, "Starting plugins", loaded, scannedPlugins.size(), false); } } List scanAndInstantiate(ClassLoader classLoader, String packageName) throws IOException { + SplashScreen.stage(.59, null, "Loading Plugins"); MutableGraph> graph = GraphBuilder .directed() .build(); @@ -280,20 +286,22 @@ public class PluginManager List> sortedPlugins = topologicalSort(graph); sortedPlugins = Lists.reverse(sortedPlugins); + int loaded = 0; for (Class pluginClazz : sortedPlugins) { Plugin plugin; try { plugin = instantiate(scannedPlugins, (Class) pluginClazz); + scannedPlugins.add(plugin); } catch (PluginInstantiationException ex) { log.warn("Error instantiating plugin!", ex); - continue; } - scannedPlugins.add(plugin); + loaded++; + SplashScreen.stage(.60, .70, null, "Loading Plugins", loaded, sortedPlugins.size(), false); } return scannedPlugins; diff --git a/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java b/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java index e5adedb2f9..f666703a66 100644 --- a/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java +++ b/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java @@ -34,6 +34,7 @@ import io.sigpipe.jbsdiff.InvalidHeaderException; import io.sigpipe.jbsdiff.Patch; import java.applet.Applet; import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -53,6 +54,7 @@ import net.runelite.api.Client; import static net.runelite.client.rs.ClientUpdateCheckMode.AUTO; import static net.runelite.client.rs.ClientUpdateCheckMode.NONE; import static net.runelite.client.rs.ClientUpdateCheckMode.VANILLA; +import net.runelite.client.ui.SplashScreen; import net.runelite.http.api.RuneLiteAPI; import okhttp3.Request; import okhttp3.Response; @@ -88,6 +90,7 @@ public class ClientLoader implements Supplier try { + SplashScreen.stage(0, null, "Fetching applet viewer config"); RSConfig config = ClientConfigLoader.fetch(); Map zipFile = new HashMap<>(); @@ -102,7 +105,26 @@ public class ClientLoader implements Supplier try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) { - JarInputStream jis = new JarInputStream(response.body().byteStream()); + int length = (int) response.body().contentLength(); + if (length < 0) + { + length = 3 * 1024 * 1024; + } + final int flength = length; + InputStream istream = new FilterInputStream(response.body().byteStream()) + { + private int read = 0; + + @Override + public int read(byte[] b, int off, int len) throws IOException + { + int thisRead = super.read(b, off, len); + this.read += thisRead; + SplashScreen.stage(.05, .35, null, "Downloading Old School RuneScape", this.read, flength, true); + return thisRead; + } + }; + JarInputStream jis = new JarInputStream(istream); byte[] tmp = new byte[4096]; ByteArrayOutputStream buffer = new ByteArrayOutputStream(756 * 1024); @@ -146,6 +168,7 @@ public class ClientLoader implements Supplier if (updateCheckMode == AUTO) { + SplashScreen.stage(.35, null, "Patching"); Map hashes; try (InputStream is = ClientLoader.class.getResourceAsStream("/patch/hashes.json")) { @@ -197,11 +220,14 @@ public class ClientLoader implements Supplier file.setValue(patchOs.toByteArray()); ++patchCount; + SplashScreen.stage(.38, .45, null, "Patching", patchCount, zipFile.size(), false); } log.debug("Patched {} classes", patchCount); } + SplashScreen.stage(.465, "Starting", "Starting Old School RuneScape"); + String initialClass = config.getInitialClass(); ClassLoader rsClassLoader = new ClassLoader(ClientLoader.class.getClassLoader()) @@ -230,6 +256,8 @@ public class ClientLoader implements Supplier log.info("client-patch {}", ((Client) rs).getBuildID()); } + SplashScreen.stage(.5, null, "Starting core classes"); + return rs; } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException diff --git a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java index 5a175716d4..a61b1c656f 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java @@ -261,7 +261,7 @@ public class ClientUI return; } - final Client client = (Client)this.client; + final Client client = (Client) this.client; final ClientThread clientThread = clientThreadProvider.get(); // Keep scheduling event until we get our name @@ -293,11 +293,10 @@ public class ClientUI /** * Initialize UI. - * * @param runelite runelite instance that will be shut down on exit * @throws Exception exception that can occur during creation of the UI */ - public void open(final RuneLite runelite) throws Exception + public void init(final RuneLite runelite) throws Exception { SwingUtilities.invokeAndWait(() -> { @@ -453,7 +452,13 @@ public class ClientUI titleToolbar.addComponent(sidebarNavigationButton, sidebarNavigationJButton); toggleSidebar(); + }); + } + public void show() + { + SwingUtilities.invokeLater(() -> + { // Layout frame frame.pack(); frame.revalidateMinimumSize(); @@ -603,10 +608,10 @@ public class ClientUI } /** - * Changes cursor for client window. Requires ${@link ClientUI#open(RuneLite)} to be called first. + * Changes cursor for client window. Requires ${@link ClientUI#init(RuneLite)} to be called first. * FIXME: This is working properly only on Windows, Linux and Mac are displaying cursor incorrectly * @param image cursor image - * @param name cursor name + * @param name cursor name */ public void setCursor(final BufferedImage image, final String name) { diff --git a/runelite-client/src/main/java/net/runelite/client/ui/SplashScreen.java b/runelite-client/src/main/java/net/runelite/client/ui/SplashScreen.java new file mode 100644 index 0000000000..6e945cb2a4 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/SplashScreen.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2019 Abex + * 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.ui; + +import java.awt.Color; +import java.awt.Container; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import javax.annotation.Nullable; +import javax.imageio.ImageIO; +import javax.swing.ImageIcon; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JProgressBar; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.UIManager; +import javax.swing.border.EmptyBorder; +import javax.swing.plaf.basic.BasicProgressBarUI; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SplashScreen extends JFrame implements ActionListener +{ + private static final int WIDTH = 200; + private static final int PAD = 10; + + private static SplashScreen INSTANCE; + + private final JLabel action = new JLabel("Loading"); + private final JProgressBar progress = new JProgressBar(); + private final JLabel subAction = new JLabel(); + private final Timer timer; + + private volatile double overallProgress = 0; + private volatile String actionText = "Loading"; + private volatile String subActionText = ""; + private volatile String progressText = null; + + private SplashScreen() throws IOException + { + BufferedImage logo = ImageIO.read(SplashScreen.class.getResourceAsStream("runelite_transparent.png")); + + setTitle("RuneLite Launcher"); + + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setUndecorated(true); + setIconImage(logo); + setLayout(null); + Container pane = getContentPane(); + pane.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + Font font = new Font(Font.DIALOG, Font.PLAIN, 12); + + JLabel logoLabel = new JLabel(new ImageIcon(logo)); + pane.add(logoLabel); + logoLabel.setBounds(0, 0, WIDTH, WIDTH); + + int y = WIDTH; + + pane.add(action); + action.setForeground(Color.WHITE); + action.setBounds(0, y, WIDTH, 16); + action.setHorizontalAlignment(SwingConstants.CENTER); + action.setFont(font); + y += action.getHeight() + PAD; + + pane.add(progress); + progress.setForeground(ColorScheme.BRAND_ORANGE); + progress.setBackground(ColorScheme.BRAND_ORANGE.darker().darker()); + progress.setBorder(new EmptyBorder(0, 0, 0, 0)); + progress.setBounds(0, y, WIDTH, 14); + progress.setFont(font); + progress.setUI(new BasicProgressBarUI() + { + @Override + protected Color getSelectionBackground() + { + return Color.BLACK; + } + + @Override + protected Color getSelectionForeground() + { + return Color.BLACK; + } + }); + y += 12 + PAD; + + pane.add(subAction); + subAction.setForeground(Color.LIGHT_GRAY); + subAction.setBounds(0, y, WIDTH, 16); + subAction.setHorizontalAlignment(SwingConstants.CENTER); + subAction.setFont(font); + y += subAction.getHeight() + PAD; + + setSize(WIDTH, y); + setLocationRelativeTo(null); + + timer = new Timer(100, this); + timer.setRepeats(true); + timer.start(); + + setVisible(true); + } + + @Override + public void actionPerformed(ActionEvent e) + { + action.setText(actionText); + subAction.setText(subActionText); + progress.setMaximum(1000); + progress.setValue((int) (overallProgress * 1000)); + + String progressText = this.progressText; + if (progressText == null) + { + progress.setStringPainted(false); + } + else + { + progress.setStringPainted(true); + progress.setString(progressText); + } + } + + public static void init() + { + try + { + SwingUtilities.invokeAndWait(() -> + { + if (INSTANCE != null) + { + return; + } + + try + { + UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); + INSTANCE = new SplashScreen(); + } + catch (Exception e) + { + log.warn("Unable to start splash screen", e); + } + }); + } + catch (InterruptedException | InvocationTargetException bs) + { + throw new RuntimeException(bs); + } + } + + public static void stop() + { + SwingUtilities.invokeLater(() -> + { + if (INSTANCE == null) + { + return; + } + + INSTANCE.timer.stop(); + INSTANCE.dispose(); + INSTANCE = null; + }); + } + + public static void stage(double overallProgress, @Nullable String actionText, String subActionText) + { + stage(overallProgress, actionText, subActionText, null); + } + + public static void stage(double startProgress, double endProgress, + @Nullable String actionText, String subActionText, + int done, int total, boolean mib) + { + String progress; + if (mib) + { + final double MiB = 1024 * 1042; + progress = String.format("%.1f / %.1f MiB", done / MiB, total / MiB); + } + else + { + progress = done + " / " + total; + } + stage(startProgress + ((endProgress - startProgress) * done / total), actionText, subActionText, progress); + } + + public static void stage(double overallProgress, @Nullable String actionText, String subActionText, @Nullable String progressText) + { + if (INSTANCE != null) + { + INSTANCE.overallProgress = overallProgress; + if (actionText != null) + { + INSTANCE.actionText = actionText; + } + INSTANCE.subActionText = subActionText; + INSTANCE.progressText = progressText; + } + } +} diff --git a/runelite-client/src/main/resources/net/runelite/client/ui/runelite_transparent.png b/runelite-client/src/main/resources/net/runelite/client/ui/runelite_transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..1d96a54c6db8272037994c775c330d7b083d1a0c GIT binary patch literal 31065 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 Date: Wed, 7 Aug 2019 02:58:57 -0600 Subject: [PATCH 6/9] runelite-client: Make RuneLiteProperties fully static --- .../java/net/runelite/client/Notifier.java | 5 ++-- .../net/runelite/client/RuneLiteModule.java | 1 - .../runelite/client/RuneLiteProperties.java | 30 ++++++++----------- .../client/discord/DiscordService.java | 5 +--- .../ChatNotificationsPlugin.java | 5 +--- .../client/plugins/discord/DiscordPlugin.java | 5 +--- .../client/plugins/discord/DiscordState.java | 8 ++--- .../client/plugins/info/InfoPanel.java | 13 ++++---- .../java/net/runelite/client/ui/ClientUI.java | 13 ++++---- 9 files changed, 30 insertions(+), 55 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/Notifier.java b/runelite-client/src/main/java/net/runelite/client/Notifier.java index b8f0b6f592..20bcf83c97 100644 --- a/runelite-client/src/main/java/net/runelite/client/Notifier.java +++ b/runelite-client/src/main/java/net/runelite/client/Notifier.java @@ -73,8 +73,9 @@ public class Notifier private static final int MINIMUM_FLASH_DURATION_MILLIS = 2000; private static final int MINIMUM_FLASH_DURATION_TICKS = MINIMUM_FLASH_DURATION_MILLIS / Constants.CLIENT_TICK_LENGTH; + private static final String appName = RuneLiteProperties.getTitle(); + private final Client client; - private final String appName; private final RuneLiteConfig runeLiteConfig; private final ClientUI clientUI; private final ScheduledExecutorService executorService; @@ -89,12 +90,10 @@ public class Notifier final ClientUI clientUI, final Client client, final RuneLiteConfig runeliteConfig, - final RuneLiteProperties runeLiteProperties, final ScheduledExecutorService executorService, final ChatMessageManager chatMessageManager) { this.client = client; - this.appName = runeLiteProperties.getTitle(); this.clientUI = clientUI; this.runeLiteConfig = runeliteConfig; this.executorService = executorService; 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 23b80ea009..525a0f23b6 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java @@ -75,7 +75,6 @@ public class RuneLiteModule extends AbstractModule bind(ItemManager.class); bind(Scheduler.class); bind(PluginManager.class); - bind(RuneLiteProperties.class); bind(SessionManager.class); bind(Callbacks.class).to(Hooks.class); 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 c58c588436..4ff24fe690 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLiteProperties.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLiteProperties.java @@ -28,12 +28,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.Properties; import javax.annotation.Nullable; -import javax.inject.Inject; -import javax.inject.Singleton; -import lombok.extern.slf4j.Slf4j; -@Singleton -@Slf4j public class RuneLiteProperties { private static final String RUNELITE_TITLE = "runelite.title"; @@ -46,57 +41,56 @@ public class RuneLiteProperties private static final String PATREON_LINK = "runelite.patreon.link"; private static final String LAUNCHER_VERSION_PROPERTY = "runelite.launcher.version"; - private final Properties properties = new Properties(); + private static final Properties properties = new Properties(); - @Inject - public RuneLiteProperties() + static { - try (InputStream in = getClass().getResourceAsStream("runelite.properties")) + try (InputStream in = RuneLiteProperties.class.getResourceAsStream("runelite.properties")) { properties.load(in); } catch (IOException ex) { - log.warn("unable to load propertries", ex); + throw new RuntimeException(ex); } } - public String getTitle() + public static String getTitle() { return properties.getProperty(RUNELITE_TITLE); } - public String getVersion() + public static String getVersion() { return properties.getProperty(RUNELITE_VERSION); } - public String getRunescapeVersion() + public static String getRunescapeVersion() { return properties.getProperty(RUNESCAPE_VERSION); } - public String getDiscordAppId() + public static String getDiscordAppId() { return properties.getProperty(DISCORD_APP_ID); } - public String getDiscordInvite() + public static String getDiscordInvite() { return properties.getProperty(DISCORD_INVITE); } - public String getGithubLink() + public static String getGithubLink() { return properties.getProperty(GITHUB_LINK); } - public String getWikiLink() + public static String getWikiLink() { return properties.getProperty(WIKI_LINK); } - public String getPatreonLink() + public static String getPatreonLink() { return properties.getProperty(PATREON_LINK); } diff --git a/runelite-client/src/main/java/net/runelite/client/discord/DiscordService.java b/runelite-client/src/main/java/net/runelite/client/discord/DiscordService.java index 091c479ac6..0aaa4735e0 100644 --- a/runelite-client/src/main/java/net/runelite/client/discord/DiscordService.java +++ b/runelite-client/src/main/java/net/runelite/client/discord/DiscordService.java @@ -49,7 +49,6 @@ import net.runelite.discord.DiscordUser; public class DiscordService implements AutoCloseable { private final EventBus eventBus; - private final RuneLiteProperties runeLiteProperties; private final ScheduledExecutorService executorService; private final DiscordRPC discordRPC; @@ -62,12 +61,10 @@ public class DiscordService implements AutoCloseable @Inject private DiscordService( final EventBus eventBus, - final RuneLiteProperties runeLiteProperties, final ScheduledExecutorService executorService) { this.eventBus = eventBus; - this.runeLiteProperties = runeLiteProperties; this.executorService = executorService; DiscordRPC discordRPC = null; @@ -106,7 +103,7 @@ public class DiscordService implements AutoCloseable discordEventHandlers.joinGame = this::joinGame; discordEventHandlers.spectateGame = this::spectateGame; discordEventHandlers.joinRequest = this::joinRequest; - discordRPC.Discord_Initialize(runeLiteProperties.getDiscordAppId(), discordEventHandlers, true, null); + discordRPC.Discord_Initialize(RuneLiteProperties.getDiscordAppId(), discordEventHandlers, true, null); executorService.scheduleAtFixedRate(discordRPC::Discord_RunCallbacks, 0, 2, TimeUnit.SECONDS); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatnotifications/ChatNotificationsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatnotifications/ChatNotificationsPlugin.java index 16127de3b6..874a446a34 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatnotifications/ChatNotificationsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatnotifications/ChatNotificationsPlugin.java @@ -68,9 +68,6 @@ public class ChatNotificationsPlugin extends Plugin @Inject private Notifier notifier; - @Inject - private RuneLiteProperties runeLiteProperties; - //Custom Highlights private Pattern usernameMatcher = null; private String usernameReplacer = ""; @@ -148,7 +145,7 @@ public class ChatNotificationsPlugin extends Plugin break; case CONSOLE: // Don't notify for notification messages - if (chatMessage.getName().equals(runeLiteProperties.getTitle())) + if (chatMessage.getName().equals(RuneLiteProperties.getTitle())) { return; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java index 443dd8ac12..fb62c2cc8f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java @@ -91,9 +91,6 @@ public class DiscordPlugin extends Plugin @Inject private ClientToolbar clientToolbar; - @Inject - private RuneLiteProperties properties; - @Inject private DiscordState discordState; @@ -125,7 +122,7 @@ public class DiscordPlugin extends Plugin .tab(false) .tooltip("Join Discord") .icon(icon) - .onClick(() -> LinkBrowser.browse(properties.getDiscordInvite())) + .onClick(() -> LinkBrowser.browse(RuneLiteProperties.getDiscordInvite())) .build(); clientToolbar.addNavigation(discordButton); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordState.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordState.java index 0b57424f85..3361b26c52 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordState.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordState.java @@ -58,16 +58,14 @@ class DiscordState private final DiscordService discordService; private final DiscordConfig config; private PartyService party; - private final RuneLiteProperties properties; private DiscordPresence lastPresence; @Inject - private DiscordState(final DiscordService discordService, final DiscordConfig config, final PartyService party, final RuneLiteProperties properties) + private DiscordState(final DiscordService discordService, final DiscordConfig config, final PartyService party) { this.discordService = discordService; this.config = config; this.party = party; - this.properties = properties; } /** @@ -173,12 +171,12 @@ class DiscordState } // Replace snapshot with + to make tooltip shorter (so it will span only 1 line) - final String versionShortHand = properties.getVersion().replace("-SNAPSHOT", "+"); + final String versionShortHand = RuneLiteProperties.getVersion().replace("-SNAPSHOT", "+"); final DiscordPresence.DiscordPresenceBuilder presenceBuilder = DiscordPresence.builder() .state(MoreObjects.firstNonNull(state, "")) .details(MoreObjects.firstNonNull(details, "")) - .largeImageText(properties.getTitle() + " v" + versionShortHand) + .largeImageText(RuneLiteProperties.getTitle() + " v" + versionShortHand) .startTimestamp(event.getStart()) .smallImageKey(imageKey) .partyMax(PARTY_MAX) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java index f9dc12f4e7..86a31a983c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java @@ -79,9 +79,6 @@ public class InfoPanel extends PluginPanel @Nullable private Client client; - @Inject - private RuneLiteProperties runeLiteProperties; - @Inject private EventBus eventBus; @@ -117,7 +114,7 @@ public class InfoPanel extends PluginPanel final Font smallFont = FontManager.getRunescapeSmallFont(); - JLabel version = new JLabel(htmlLabel("RuneLite version: ", runeLiteProperties.getVersion())); + JLabel version = new JLabel(htmlLabel("RuneLite version: ", RuneLiteProperties.getVersion())); version.setFont(smallFont); JLabel revision = new JLabel(); @@ -176,10 +173,10 @@ public class InfoPanel extends PluginPanel } }); - actionsContainer.add(buildLinkPanel(GITHUB_ICON, "Report an issue or", "make a suggestion", runeLiteProperties.getGithubLink())); - actionsContainer.add(buildLinkPanel(DISCORD_ICON, "Talk to us on our", "discord server", runeLiteProperties.getDiscordInvite())); - actionsContainer.add(buildLinkPanel(PATREON_ICON, "Become a patron to", "help support RuneLite", runeLiteProperties.getPatreonLink())); - actionsContainer.add(buildLinkPanel(WIKI_ICON, "Information about", "RuneLite and plugins", runeLiteProperties.getWikiLink())); + actionsContainer.add(buildLinkPanel(GITHUB_ICON, "Report an issue or", "make a suggestion", RuneLiteProperties.getGithubLink())); + actionsContainer.add(buildLinkPanel(DISCORD_ICON, "Talk to us on our", "discord server", RuneLiteProperties.getDiscordInvite())); + actionsContainer.add(buildLinkPanel(PATREON_ICON, "Become a patron to", "help support RuneLite", RuneLiteProperties.getPatreonLink())); + actionsContainer.add(buildLinkPanel(WIKI_ICON, "Information about", "RuneLite and plugins", RuneLiteProperties.getWikiLink())); add(versionPanel, BorderLayout.NORTH); add(actionsContainer, BorderLayout.CENTER); diff --git a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java index a61b1c656f..4fe2413c27 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java @@ -111,7 +111,6 @@ public class ClientUI @Getter private TrayIcon trayIcon; - private final RuneLiteProperties properties; private final RuneLiteConfig config; private final KeyManager keyManager; private final MouseManager mouseManager; @@ -138,7 +137,6 @@ public class ClientUI @Inject private ClientUI( - RuneLiteProperties properties, RuneLiteConfig config, KeyManager keyManager, MouseManager mouseManager, @@ -146,7 +144,6 @@ public class ClientUI ConfigManager configManager, Provider clientThreadProvider) { - this.properties = properties; this.config = config; this.keyManager = keyManager; this.mouseManager = mouseManager; @@ -286,7 +283,7 @@ public class ClientUI return false; } - frame.setTitle(properties.getTitle() + " - " + name); + frame.setTitle(RuneLiteProperties.getTitle() + " - " + name); return true; }); } @@ -315,7 +312,7 @@ public class ClientUI // Try to enable fullscreen on OSX OSXUtil.tryEnableFullscreen(frame); - frame.setTitle(properties.getTitle()); + frame.setTitle(RuneLiteProperties.getTitle()); frame.setIconImage(ICON); frame.getLayeredPane().setCursor(Cursor.getDefaultCursor()); // Prevent substance from using a resize cursor for pointing frame.setLocationRelativeTo(frame.getOwner()); @@ -464,7 +461,7 @@ public class ClientUI frame.revalidateMinimumSize(); // Create tray icon (needs to be created after frame is packed) - trayIcon = SwingUtil.createTrayIcon(ICON, properties.getTitle(), frame); + trayIcon = SwingUtil.createTrayIcon(ICON, RuneLiteProperties.getTitle(), frame); // Move frame around (needs to be done after frame is packed) if (config.rememberScreenBounds()) @@ -844,12 +841,12 @@ public class ClientUI if (player != null && player.getName() != null) { - frame.setTitle(properties.getTitle() + " - " + player.getName()); + frame.setTitle(RuneLiteProperties.getTitle() + " - " + player.getName()); } } else { - frame.setTitle(properties.getTitle()); + frame.setTitle(RuneLiteProperties.getTitle()); } if (frame.isAlwaysOnTopSupported()) From cdfe2e93069458e17fcee827d6633a0ae290d0c8 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Wed, 7 Aug 2019 03:43:41 -0600 Subject: [PATCH 7/9] runelite-client: Add fatal error dialog --- .../java/net/runelite/client/RuneLite.java | 16 +- .../runelite/client/RuneLiteProperties.java | 18 ++ .../net/runelite/client/rs/ClientLoader.java | 33 ++- .../client/rs/VerificationException.java | 2 +- .../java/net/runelite/client/ui/ClientUI.java | 10 +- .../runelite/client/ui/FatalErrorDialog.java | 240 ++++++++++++++++++ .../net/runelite/client/runelite.properties | 5 +- 7 files changed, 301 insertions(+), 23 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/ui/FatalErrorDialog.java 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 e8b4d60a43..0decdb3c48 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -37,6 +37,7 @@ import java.util.Locale; import javax.annotation.Nullable; import javax.inject.Provider; import javax.inject.Singleton; +import javax.swing.SwingUtilities; import joptsimple.ArgumentAcceptingOptionSpec; import joptsimple.OptionParser; import joptsimple.OptionSet; @@ -60,6 +61,7 @@ import net.runelite.client.rs.ClientLoader; import net.runelite.client.rs.ClientUpdateCheckMode; import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.DrawManager; +import net.runelite.client.ui.FatalErrorDialog; import net.runelite.client.ui.SplashScreen; import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.ui.overlay.OverlayRenderer; @@ -78,6 +80,7 @@ public class RuneLite public static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite"); public static final File PROFILES_DIR = new File(RUNELITE_DIR, "profiles"); public static final File SCREENSHOT_DIR = new File(RUNELITE_DIR, "screenshots"); + public static final File LOGS_DIR = new File(RUNELITE_DIR, "logs"); @Getter private static Injector injector; @@ -219,7 +222,11 @@ public class RuneLite assert assertions = true; if (!assertions) { - throw new RuntimeException("Developers should enable assertions; Add `-ea` to your JVM arguments`"); + SwingUtilities.invokeLater(() -> + new FatalErrorDialog("Developers should enable assertions; Add `-ea` to your JVM arguments`") + .addBuildingGuide() + .open()); + return; } } @@ -238,6 +245,13 @@ public class RuneLite final long uptime = rb.getUptime(); log.info("Client initialization took {}ms. Uptime: {}ms", end - start, uptime); } + catch (Exception e) + { + log.warn("Failure during startup", e); + SwingUtilities.invokeLater(() -> + new FatalErrorDialog("RuneLite has encountered an unexpected error during startup.") + .open()); + } finally { SplashScreen.stop(); 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 4ff24fe690..a1ff15e2eb 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLiteProperties.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLiteProperties.java @@ -40,6 +40,9 @@ 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 = "runelite.launcher.version"; + 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"; private static final Properties properties = new Properties(); @@ -100,4 +103,19 @@ public class RuneLiteProperties { return System.getProperty(LAUNCHER_VERSION_PROPERTY); } + + public static String getTroubleshootingLink() + { + return properties.getProperty(TROUBLESHOOTING_LINK); + } + + public static String getBuildingLink() + { + return properties.getProperty(BUILDING_LINK); + } + + public static String getDNSChangeLink() + { + return properties.getProperty(DNS_CHANGE_LINK); + } } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java b/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java index f666703a66..43721b6f06 100644 --- a/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java +++ b/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java @@ -49,11 +49,13 @@ import java.util.Map; import java.util.function.Supplier; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; +import javax.swing.SwingUtilities; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import static net.runelite.client.rs.ClientUpdateCheckMode.AUTO; import static net.runelite.client.rs.ClientUpdateCheckMode.NONE; import static net.runelite.client.rs.ClientUpdateCheckMode.VANILLA; +import net.runelite.client.ui.FatalErrorDialog; import net.runelite.client.ui.SplashScreen; import net.runelite.http.api.RuneLiteAPI; import okhttp3.Request; @@ -64,7 +66,7 @@ import org.apache.commons.compress.compressors.CompressorException; public class ClientLoader implements Supplier { private ClientUpdateCheckMode updateCheckMode; - private Applet client = null; + private Object client = null; public ClientLoader(ClientUpdateCheckMode updateCheckMode) { @@ -78,10 +80,15 @@ public class ClientLoader implements Supplier { client = doLoad(); } - return client; + + if (client instanceof Throwable) + { + throw new RuntimeException((Throwable) client); + } + return (Applet) client; } - private Applet doLoad() + private Object doLoad() { if (updateCheckMode == NONE) { @@ -172,6 +179,15 @@ public class ClientLoader implements Supplier Map hashes; try (InputStream is = ClientLoader.class.getResourceAsStream("/patch/hashes.json")) { + if (is == null) + { + SwingUtilities.invokeLater(() -> + new FatalErrorDialog("The client-patch is missing from the classpath. If you are building " + + "the client you need to re-run maven") + .addBuildingGuide() + .open()); + throw new NullPointerException(); + } hashes = new Gson().fromJson(new InputStreamReader(is), new TypeToken>() { }.getType()); @@ -264,15 +280,10 @@ public class ClientLoader implements Supplier | CompressorException | InvalidHeaderException | CertificateException | VerificationException | SecurityException e) { - if (e instanceof ClassNotFoundException) - { - log.error("Unable to load client - class not found. This means you" - + " are not running RuneLite with Maven as the client patch" - + " is not in your classpath."); - } - log.error("Error loading RS!", e); - return null; + + SwingUtilities.invokeLater(() -> FatalErrorDialog.showNetErrorWindow("loading the client", e)); + return e; } } diff --git a/runelite-client/src/main/java/net/runelite/client/rs/VerificationException.java b/runelite-client/src/main/java/net/runelite/client/rs/VerificationException.java index 040370e8f0..4138a12fd3 100644 --- a/runelite-client/src/main/java/net/runelite/client/rs/VerificationException.java +++ b/runelite-client/src/main/java/net/runelite/client/rs/VerificationException.java @@ -24,7 +24,7 @@ */ package net.runelite.client.rs; -class VerificationException extends Exception +public class VerificationException extends Exception { public VerificationException(String message) { diff --git a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java index 4fe2413c27..e50d312455 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java @@ -53,7 +53,6 @@ import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JOptionPane; -import static javax.swing.JOptionPane.ERROR_MESSAGE; import static javax.swing.JOptionPane.INFORMATION_MESSAGE; import javax.swing.JPanel; import javax.swing.JRootPane; @@ -518,14 +517,7 @@ public class ClientUI }); // Show out of date dialog if needed - if (client == null) - { - SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(frame, - "Error loading client! Check your logs for more details.", - "Unable to load client", - ERROR_MESSAGE)); - } - else if (!(client instanceof Client)) + if (client != null && !(client instanceof Client)) { SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(frame, "RuneLite has not yet been updated to work with the latest\n" diff --git a/runelite-client/src/main/java/net/runelite/client/ui/FatalErrorDialog.java b/runelite-client/src/main/java/net/runelite/client/ui/FatalErrorDialog.java new file mode 100644 index 0000000000..746546fc10 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ui/FatalErrorDialog.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2019 Abex + * 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.ui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Desktop; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.ConnectException; +import java.net.UnknownHostException; +import java.security.GeneralSecurityException; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.imageio.ImageIO; +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextArea; +import javax.swing.UIManager; +import javax.swing.border.EmptyBorder; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.RuneLite; +import net.runelite.client.RuneLiteProperties; +import net.runelite.client.rs.VerificationException; +import net.runelite.client.util.LinkBrowser; + +@Slf4j +public class FatalErrorDialog extends JDialog +{ + private static final AtomicBoolean alreadyOpen = new AtomicBoolean(false); + + private final JPanel rightColumn = new JPanel(); + private final Font font = new Font(Font.DIALOG, Font.PLAIN, 12); + + public FatalErrorDialog(String message) + { + if (alreadyOpen.getAndSet(true)) + { + throw new IllegalStateException("Fatal error during fatal error: " + message); + } + + try + { + UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); + } + catch (Exception e) + { + } + + UIManager.put("Button.select", ColorScheme.DARKER_GRAY_COLOR); + + try + { + BufferedImage logo = ImageIO.read(SplashScreen.class.getResourceAsStream("runelite_transparent.png")); + setIconImage(logo); + + JLabel runelite = new JLabel(); + runelite.setIcon(new ImageIcon(logo)); + runelite.setAlignmentX(Component.CENTER_ALIGNMENT); + runelite.setBackground(ColorScheme.DARK_GRAY_COLOR); + runelite.setOpaque(true); + rightColumn.add(runelite); + } + catch (IOException e) + { + } + + addWindowListener(new WindowAdapter() + { + @Override + public void windowClosing(WindowEvent e) + { + System.exit(-1); + } + }); + + setTitle("Fatal error starting RuneLite"); + setLayout(new BorderLayout()); + + Container pane = getContentPane(); + pane.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + JPanel leftPane = new JPanel(); + leftPane.setBackground(ColorScheme.DARKER_GRAY_COLOR); + leftPane.setLayout(new BorderLayout()); + + JLabel title = new JLabel("There was a fatal error starting RuneLite"); + title.setForeground(Color.WHITE); + title.setFont(font.deriveFont(16.f)); + title.setBorder(new EmptyBorder(10, 10, 10, 10)); + leftPane.add(title, BorderLayout.NORTH); + + leftPane.setPreferredSize(new Dimension(400, 200)); + JTextArea textArea = new JTextArea(message); + textArea.setFont(font); + textArea.setBackground(ColorScheme.DARKER_GRAY_COLOR); + textArea.setForeground(Color.LIGHT_GRAY); + textArea.setLineWrap(true); + textArea.setWrapStyleWord(true); + textArea.setBorder(new EmptyBorder(10, 10, 10, 10)); + textArea.setEditable(false); + leftPane.add(textArea, BorderLayout.CENTER); + + pane.add(leftPane, BorderLayout.CENTER); + + rightColumn.setLayout(new BoxLayout(rightColumn, BoxLayout.Y_AXIS)); + rightColumn.setBackground(ColorScheme.DARK_GRAY_COLOR); + rightColumn.setMaximumSize(new Dimension(200, Integer.MAX_VALUE)); + + addButton("Open logs folder", () -> + { + try + { + Desktop.getDesktop().open(RuneLite.LOGS_DIR); + } + catch (IOException e) + { + log.warn("Unable to open logs", e); + } + }); + addButton("Get help on Discord", () -> LinkBrowser.browse(RuneLiteProperties.getDiscordInvite())); + addButton("Troubleshooting steps", () -> LinkBrowser.browse(RuneLiteProperties.getTroubleshootingLink())); + + pane.add(rightColumn, BorderLayout.EAST); + } + + public void open() + { + addButton("Exit", () -> System.exit(-1)); + + pack(); + SplashScreen.stop(); + setVisible(true); + } + + public FatalErrorDialog addButton(String message, Runnable action) + { + JButton button = new JButton(message); + button.addActionListener(e -> action.run()); + button.setFont(font); + button.setBackground(ColorScheme.DARK_GRAY_COLOR); + button.setForeground(Color.LIGHT_GRAY); + button.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createMatteBorder(1, 0, 0, 0, ColorScheme.DARK_GRAY_COLOR.brighter()), + new EmptyBorder(4, 4, 4, 4) + )); + button.setAlignmentX(Component.CENTER_ALIGNMENT); + button.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE)); + button.setFocusPainted(false); + button.addChangeListener(ev -> + { + if (button.getModel().isPressed()) + { + button.setBackground(ColorScheme.DARKER_GRAY_COLOR); + } + else if (button.getModel().isRollover()) + { + button.setBackground(ColorScheme.DARK_GRAY_HOVER_COLOR); + } + else + { + button.setBackground(ColorScheme.DARK_GRAY_COLOR); + } + }); + + rightColumn.add(button); + rightColumn.revalidate(); + + return this; + } + + public FatalErrorDialog addBuildingGuide() + { + return addButton("Building guide", () -> LinkBrowser.browse(RuneLiteProperties.getBuildingLink())); + } + + public static void showNetErrorWindow(String action, Throwable err) + { + if (err instanceof VerificationException || err instanceof GeneralSecurityException) + { + new FatalErrorDialog("RuneLite was unable to verify the security of its connection to the internet while " + + action + ". You may have a misbehaving antivirus, internet service provider, a proxy, or an incomplete" + + " java installation.") + .open(); + return; + } + + if (err instanceof ConnectException) + { + new FatalErrorDialog("RuneLite is unable to connect to a required server while " + action + ". " + + "Please check your internet connection") + .open(); + return; + } + + if (err instanceof UnknownHostException) + { + new FatalErrorDialog("RuneLite is unable to resolve the address of a required server while " + action + ". " + + "Your DNS resolver may be misconfigured, pointing to an inaccurate resolver, or your internet connection may " + + "be down. ") + .addButton("Change your DNS resolver", () -> LinkBrowser.browse(RuneLiteProperties.getDNSChangeLink())) + .open(); + return; + } + + new FatalErrorDialog("RuneLite encountered a fatal error while " + action + ".").open(); + } +} 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 afeed8517f..c29c1e8e39 100644 --- a/runelite-client/src/main/resources/net/runelite/client/runelite.properties +++ b/runelite-client/src/main/resources/net/runelite/client/runelite.properties @@ -5,4 +5,7 @@ runelite.discord.appid=409416265891971072 runelite.discord.invite=https://discord.gg/R4BQ8tU runelite.github.link=https://github.com/runelite runelite.wiki.link=https://github.com/runelite/runelite/wiki -runelite.patreon.link=https://www.patreon.com/runelite \ No newline at end of file +runelite.patreon.link=https://www.patreon.com/runelite +runelite.wiki.troubleshooting.link=https://github.com/runelite/runelite/wiki/Troubleshooting-problems-with-the-client +runelite.wiki.building.link=https://github.com/runelite/runelite/wiki/Building-with-IntelliJ-IDEA#client-failing-to-start +runelite.dnschange.link=https://1.1.1.1/dns/ From ef4c628068ec487b3bb47e1b0b1add56a20b5641 Mon Sep 17 00:00:00 2001 From: dekvall Date: Tue, 6 Aug 2019 19:01:18 +0200 Subject: [PATCH 8/9] loottracker: remove timestamp from LootTrackerRecord --- .../runelite/client/plugins/loottracker/LootTrackerPanel.java | 2 +- .../runelite/client/plugins/loottracker/LootTrackerPlugin.java | 2 +- .../runelite/client/plugins/loottracker/LootTrackerRecord.java | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java index ef237a78ec..e901a6820f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java @@ -378,7 +378,7 @@ class LootTrackerPanel extends PluginPanel void add(final String eventName, final int actorLevel, LootTrackerItem[] items) { final String subTitle = actorLevel > -1 ? "(lvl-" + actorLevel + ")" : ""; - final LootTrackerRecord record = new LootTrackerRecord(eventName, subTitle, items, System.currentTimeMillis()); + final LootTrackerRecord record = new LootTrackerRecord(eventName, subTitle, items); records.add(record); LootTrackerBox box = buildBox(record); if (box != null) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java index 5fd34877f5..20ce89f8f5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java @@ -614,7 +614,7 @@ public class LootTrackerPlugin extends Plugin buildLootTrackerItem(itemStack.getId(), itemStack.getQty()) ).toArray(LootTrackerItem[]::new); - trackerRecords.add(new LootTrackerRecord(record.getEventId(), "", drops, -1)); + trackerRecords.add(new LootTrackerRecord(record.getEventId(), "", drops)); } return trackerRecords; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java index 0d75ec70c3..da69caff7d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java @@ -32,7 +32,6 @@ class LootTrackerRecord private final String title; private final String subTitle; private final LootTrackerItem[] items; - private final long timestamp; /** * Checks if this record matches specified id From 1abadb0c9c36dddbd3ccdff83ec04eac4cac0c8c Mon Sep 17 00:00:00 2001 From: dekvall Date: Sat, 10 Aug 2019 12:23:20 +0200 Subject: [PATCH 9/9] loottracker: fix order on client reload The panel is currently rebuilt with the earliest loots at the top. For the sake of consistency, it should be the other way around. Closes #9575 --- .../loottracker/LootTrackerPlugin.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java index 20ce89f8f5..8acb76bf35 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java @@ -37,6 +37,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -607,17 +608,17 @@ public class LootTrackerPlugin extends Plugin private Collection convertToLootTrackerRecord(final Collection records) { - Collection trackerRecords = new ArrayList<>(); - for (LootRecord record : records) - { - LootTrackerItem[] drops = record.getDrops().stream().map(itemStack -> - buildLootTrackerItem(itemStack.getId(), itemStack.getQty()) - ).toArray(LootTrackerItem[]::new); + return records.stream() + .sorted(Comparator.comparing(LootRecord::getTime)) + .map(record -> + { + LootTrackerItem[] drops = record.getDrops().stream().map(itemStack -> + buildLootTrackerItem(itemStack.getId(), itemStack.getQty()) + ).toArray(LootTrackerItem[]::new); - trackerRecords.add(new LootTrackerRecord(record.getEventId(), "", drops)); - } - - return trackerRecords; + return new LootTrackerRecord(record.getEventId(), "", drops); + }) + .collect(Collectors.toCollection(ArrayList::new)); } /**