From 6162c581c06f65c698738507717846b7127e2166 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Wed, 19 May 2021 07:55:12 -0600 Subject: [PATCH 01/15] SpriteID: update to 2021-5-19 --- .../src/main/java/net/runelite/api/SpriteID.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/runelite-api/src/main/java/net/runelite/api/SpriteID.java b/runelite-api/src/main/java/net/runelite/api/SpriteID.java index d5bb718245..77cae9b584 100644 --- a/runelite-api/src/main/java/net/runelite/api/SpriteID.java +++ b/runelite-api/src/main/java/net/runelite/api/SpriteID.java @@ -983,13 +983,13 @@ public final class SpriteID public static final int UNKNOWN_SLANTED_TAB_LONG_HOVERED = 1016; public static final int CHATBOX = 1017; public static final int CHATBOX_BUTTONS_BACKGROUND_STONES = 1018; - public static final int CHATBOX_BUTTON = 1019; - public static final int CHATBOX_BUTTON_HOVERED = 1020; - public static final int CHATBOX_BUTTON_NEW_MESSAGES = 1021; - public static final int CHATBOX_BUTTON_SELECTED = 1022; - public static final int CHATBOX_BUTTON_SELECTED_HOVERED = 1023; - public static final int CHATBOX_REPORT_BUTTON = 1024; - public static final int CHATBOX_REPORT_BUTTON_HOVERED = 1025; + public static final int CHATBOX_BUTTON = 3051; + public static final int CHATBOX_BUTTON_HOVERED = 3052; + public static final int CHATBOX_BUTTON_NEW_MESSAGES = 3055; + public static final int CHATBOX_BUTTON_SELECTED = 3053; + public static final int CHATBOX_BUTTON_SELECTED_HOVERED = 3054; + public static final int CHATBOX_REPORT_BUTTON = 3057; + public static final int CHATBOX_REPORT_BUTTON_HOVERED = 3058; public static final int TAB_STONE_TOP_LEFT_SELECTED = 1026; public static final int TAB_STONE_TOP_RIGHT_SELECTED = 1027; public static final int TAB_STONE_BOTTOM_LEFT_SELECTED = 1028; @@ -1065,7 +1065,6 @@ public final class SpriteID public static final int ABLEGAMERS_PROMO_BANNER = 1098; public static final int YOUNGMINDS_PROMO_BANNER = 1099; public static final int DONATEGAMES_PROMO_BANNER = 1100; - public static final int UNKNOWN_GREEN_FRIEND_ICON = 1101; public static final int MINIMAP_ORB_HITPOINTS_VENOM = 1102; public static final int PAYPAL_DONATE_BUTTON = 1103; public static final int GAMEBLAST15_PROMO_BANNER = 1104; From ff033177de05529c74e062d4e8da3065c6a9f872 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Wed, 19 May 2021 08:00:09 -0600 Subject: [PATCH 02/15] screenshot: use new report button offset --- .../runelite/client/plugins/screenshot/ScreenshotOverlay.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotOverlay.java index a5fd92a862..854f5ed53a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotOverlay.java @@ -50,7 +50,7 @@ import net.runelite.client.ui.overlay.OverlayPriority; class ScreenshotOverlay extends Overlay { private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MMM. dd, yyyy"); - private static final int REPORT_BUTTON_X_OFFSET = 404; + private static final int REPORT_BUTTON_X_OFFSET = 437; private final Client client; private final DrawManager drawManager; From f8853fff31e62e05fb51f0dc342683dd18941ecc Mon Sep 17 00:00:00 2001 From: Max Weber Date: Wed, 19 May 2021 08:01:46 -0600 Subject: [PATCH 03/15] interfacestyles: update chatbox sprite ids These are all the wrong size, but they aren't horribly broken like this --- .../interfacestyles/2005/{1019.png => 3051.png} | Bin .../interfacestyles/2005/{1020.png => 3052.png} | Bin .../interfacestyles/2005/{1022.png => 3053.png} | Bin .../interfacestyles/2005/{1023.png => 3054.png} | Bin .../interfacestyles/2005/{1021.png => 3055.png} | Bin .../interfacestyles/2005/{1024.png => 3057.png} | Bin .../interfacestyles/2005/{1025.png => 3058.png} | Bin 7 files changed, 0 insertions(+), 0 deletions(-) rename runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/{1019.png => 3051.png} (100%) rename runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/{1020.png => 3052.png} (100%) rename runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/{1022.png => 3053.png} (100%) rename runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/{1023.png => 3054.png} (100%) rename runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/{1021.png => 3055.png} (100%) rename runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/{1024.png => 3057.png} (100%) rename runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/{1025.png => 3058.png} (100%) diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/1019.png b/runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/3051.png similarity index 100% rename from runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/1019.png rename to runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/3051.png diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/1020.png b/runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/3052.png similarity index 100% rename from runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/1020.png rename to runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/3052.png diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/1022.png b/runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/3053.png similarity index 100% rename from runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/1022.png rename to runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/3053.png diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/1023.png b/runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/3054.png similarity index 100% rename from runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/1023.png rename to runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/3054.png diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/1021.png b/runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/3055.png similarity index 100% rename from runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/1021.png rename to runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/3055.png diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/1024.png b/runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/3057.png similarity index 100% rename from runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/1024.png rename to runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/3057.png diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/1025.png b/runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/3058.png similarity index 100% rename from runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/1025.png rename to runelite-client/src/main/resources/net/runelite/client/plugins/interfacestyles/2005/3058.png From c69337d9544f37a595214200f50a4175725ad790 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Wed, 19 May 2021 08:18:56 -0600 Subject: [PATCH 04/15] rl-api: import getTopLevelInterfaceId --- runelite-api/src/main/java/net/runelite/api/Client.java | 5 +++++ 1 file changed, 5 insertions(+) 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 86579ba5be..114119274f 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -486,6 +486,11 @@ public interface Client extends GameEngine */ void setDraggedOnWidget(Widget widget); + /** + * Gets Interface ID of the root widget + */ + int getTopLevelInterfaceId(); + /** * Gets the root widgets. * From bd2cd07917b76bee26454d5a6352e4828eabd044 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Wed, 19 May 2021 08:23:23 -0600 Subject: [PATCH 05/15] screenshot: don't attempt to draw a date when there is no report button previously this would just silently ignore the screenshot if you were on the orb of oculus or fullscreen world map interfaces until you were back onto one of the normal tlis where the overlay is rendered. --- .../client/plugins/screenshot/ScreenshotPlugin.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java index 0dada91ae6..dbda81a49c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java @@ -27,6 +27,7 @@ package net.runelite.client.plugins.screenshot; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.inject.Provides; import java.awt.Graphics; import java.awt.Image; @@ -34,6 +35,7 @@ import java.awt.image.BufferedImage; import java.lang.reflect.InvocationTargetException; import java.time.LocalDate; import java.util.Map; +import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.function.Consumer; import java.util.regex.Matcher; @@ -56,6 +58,7 @@ import net.runelite.api.events.GameTick; import net.runelite.api.events.ScriptCallbackEvent; import net.runelite.api.events.WidgetLoaded; import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetID; import static net.runelite.api.widgets.WidgetID.BARROWS_REWARD_GROUP_ID; import static net.runelite.api.widgets.WidgetID.CHAMBERS_OF_XERIC_REWARD_GROUP_ID; import static net.runelite.api.widgets.WidgetID.CLUE_SCROLL_REWARD_GROUP_ID; @@ -110,6 +113,10 @@ public class ScreenshotPlugin extends Plugin "You feel something weird sneaking into your backpack", "You have a funny feeling like you would have been followed"); private static final Pattern BA_HIGH_GAMBLE_REWARD_PATTERN = Pattern.compile("(?.+)!
High level gamble count: (?.+)"); + private static final Set REPORT_BUTTON_TLIS = ImmutableSet.of( + WidgetID.FIXED_VIEWPORT_GROUP_ID, + WidgetID.RESIZABLE_VIEWPORT_OLD_SCHOOL_BOX_GROUP_ID, + WidgetID.RESIZABLE_VIEWPORT_BOTTOM_LINE_GROUP_ID); private String clueType; private Integer clueNumber; @@ -689,7 +696,7 @@ public class ScreenshotPlugin extends Plugin executor.submit(() -> takeScreenshot(fileName, subDir, img)); }; - if (config.displayDate()) + if (config.displayDate() && REPORT_BUTTON_TLIS.contains(client.getTopLevelInterfaceId())) { screenshotOverlay.queueForTimestamp(imageCallback); } From f150d344d3ffd43e658254b40586652972b5f411 Mon Sep 17 00:00:00 2001 From: emerald000 Date: Wed, 19 May 2021 06:14:33 -0400 Subject: [PATCH 06/15] Rename Armadylian Guards to Armadylean Guards --- .../cluescrolls/clues/CoordinateClue.java | 72 +++++++++---------- .../plugins/cluescrolls/clues/Enemy.java | 2 +- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java index 5c0e093b6c..ff2b5bb432 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java @@ -171,42 +171,42 @@ public class CoordinateClue extends ClueScroll implements TextClueScroll, Locati .put(new WorldPoint(3143, 3774, 0), new CoordinateClueInfo("In level 32 Wilderness, by the black chinchompa hunting area.", ZAMORAK_WIZARD)) .put(new WorldPoint(2992, 3941, 0), new CoordinateClueInfo("Wilderness Agility Course, past the log balance.", ZAMORAK_WIZARD)) // Elite - .put(new WorldPoint(2357, 3151, 0), new CoordinateClueInfo("Lletya.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(3587, 3180, 0), new CoordinateClueInfo("Meiyerditch.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2820, 3078, 0), new CoordinateClueInfo("Tai Bwo Wannai. Hardwood Grove. 100 Trading sticks or elite Karamja diary completion is needed to enter.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(3811, 3060, 0), new CoordinateClueInfo("Small island north-east of Mos Le'Harmless.", ARMADYLIAN_OR_BANDOSIAN_GUARD, true, Varbits.FIRE_PIT_MOS_LE_HARMLESS)) - .put(new WorldPoint(2180, 3282, 0), new CoordinateClueInfo("North of Iorwerth Camp.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2870, 2997, 0), new CoordinateClueInfo("North-east corner in Shilo Village.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(3302, 2988, 0), new CoordinateClueInfo("On top of a cliff to the west of Pollnivneach.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2511, 2980, 0), new CoordinateClueInfo("Just south of Gu'Tanoth, west of gnome glider.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2732, 3372, 0), new CoordinateClueInfo("Legends' Guild.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(3573, 3425, 0), new CoordinateClueInfo("North of Dessous's tomb from Desert Treasure.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(3828, 2848, 0), new CoordinateClueInfo("East of Harmony Island.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(3225, 2838, 0), new CoordinateClueInfo("South of Desert Treasure pyramid.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(1773, 3510, 0), new CoordinateClueInfo("Ruins north of the Hosidius mine.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(3822, 3562, 0), new CoordinateClueInfo("North-east of Dragontooth Island. Bring a Ghostspeak Amulet and 25 Ecto-tokens to reach the island.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(3603, 3564, 0), new CoordinateClueInfo("North of the wrecked ship, outside of Port Phasmatys.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2936, 2721, 0), new CoordinateClueInfo("Eastern shore of Crash Island.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2697, 2705, 0), new CoordinateClueInfo("South-west of Ape Atoll.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2778, 3678, 0), new CoordinateClueInfo("Mountain Camp.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2827, 3740, 0), new CoordinateClueInfo("West of the entrance to the Ice Path, where the Troll child resides.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2359, 3799, 0), new CoordinateClueInfo("Neitiznot.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2194, 3807, 0), new CoordinateClueInfo("Pirates' Cove.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2700, 3808, 0), new CoordinateClueInfo("Northwestern part of the Trollweiss and Rellekka Hunter area (DKS).", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(3215, 3835, 0), new CoordinateClueInfo("Wilderness. Lava Dragon Isle.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(3369, 3894, 0), new CoordinateClueInfo("Wilderness. Fountain of Rune.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2065, 3923, 0), new CoordinateClueInfo("Outside the western wall on Lunar Isle.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(3188, 3933, 0), new CoordinateClueInfo("Wilderness. Resource Area.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2997, 3953, 0), new CoordinateClueInfo("Wilderness. Inside Agility Training Area.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(3380, 3963, 0), new CoordinateClueInfo("Wilderness. North of Volcano.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(3051, 3736, 0), new CoordinateClueInfo("East of the Wilderness Obelisk in 28 Wilderness.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2316, 3814, 0), new CoordinateClueInfo("West of Neitiznot, near the bridge.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2872, 3937, 0), new CoordinateClueInfo("Weiss.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2484, 4016, 0), new CoordinateClueInfo("Northeast corner of the Island of Stone.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2222, 3331, 0), new CoordinateClueInfo("Prifddinas, west of the Tower of Voices", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(3560, 3987, 0), new CoordinateClueInfo("Lithkren. Digsite pendant teleport if unlocked, otherwise take rowboat from west of Mushroom Meadow Mushtree.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2318, 2954, 0), new CoordinateClueInfo("North-east corner of the Isle of Souls.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) - .put(new WorldPoint(2094, 2889, 0), new CoordinateClueInfo("West side of the Isle of Souls.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2357, 3151, 0), new CoordinateClueInfo("Lletya.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3587, 3180, 0), new CoordinateClueInfo("Meiyerditch.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2820, 3078, 0), new CoordinateClueInfo("Tai Bwo Wannai. Hardwood Grove. 100 Trading sticks or elite Karamja diary completion is needed to enter.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3811, 3060, 0), new CoordinateClueInfo("Small island north-east of Mos Le'Harmless.", ARMADYLEAN_OR_BANDOSIAN_GUARD, true, Varbits.FIRE_PIT_MOS_LE_HARMLESS)) + .put(new WorldPoint(2180, 3282, 0), new CoordinateClueInfo("North of Iorwerth Camp.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2870, 2997, 0), new CoordinateClueInfo("North-east corner in Shilo Village.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3302, 2988, 0), new CoordinateClueInfo("On top of a cliff to the west of Pollnivneach.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2511, 2980, 0), new CoordinateClueInfo("Just south of Gu'Tanoth, west of gnome glider.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2732, 3372, 0), new CoordinateClueInfo("Legends' Guild.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3573, 3425, 0), new CoordinateClueInfo("North of Dessous's tomb from Desert Treasure.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3828, 2848, 0), new CoordinateClueInfo("East of Harmony Island.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3225, 2838, 0), new CoordinateClueInfo("South of Desert Treasure pyramid.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(1773, 3510, 0), new CoordinateClueInfo("Ruins north of the Hosidius mine.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3822, 3562, 0), new CoordinateClueInfo("North-east of Dragontooth Island. Bring a Ghostspeak Amulet and 25 Ecto-tokens to reach the island.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3603, 3564, 0), new CoordinateClueInfo("North of the wrecked ship, outside of Port Phasmatys.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2936, 2721, 0), new CoordinateClueInfo("Eastern shore of Crash Island.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2697, 2705, 0), new CoordinateClueInfo("South-west of Ape Atoll.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2778, 3678, 0), new CoordinateClueInfo("Mountain Camp.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2827, 3740, 0), new CoordinateClueInfo("West of the entrance to the Ice Path, where the Troll child resides.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2359, 3799, 0), new CoordinateClueInfo("Neitiznot.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2194, 3807, 0), new CoordinateClueInfo("Pirates' Cove.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2700, 3808, 0), new CoordinateClueInfo("Northwestern part of the Trollweiss and Rellekka Hunter area (DKS).", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3215, 3835, 0), new CoordinateClueInfo("Wilderness. Lava Dragon Isle.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3369, 3894, 0), new CoordinateClueInfo("Wilderness. Fountain of Rune.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2065, 3923, 0), new CoordinateClueInfo("Outside the western wall on Lunar Isle.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3188, 3933, 0), new CoordinateClueInfo("Wilderness. Resource Area.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2997, 3953, 0), new CoordinateClueInfo("Wilderness. Inside Agility Training Area.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3380, 3963, 0), new CoordinateClueInfo("Wilderness. North of Volcano.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3051, 3736, 0), new CoordinateClueInfo("East of the Wilderness Obelisk in 28 Wilderness.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2316, 3814, 0), new CoordinateClueInfo("West of Neitiznot, near the bridge.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2872, 3937, 0), new CoordinateClueInfo("Weiss.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2484, 4016, 0), new CoordinateClueInfo("Northeast corner of the Island of Stone.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2222, 3331, 0), new CoordinateClueInfo("Prifddinas, west of the Tower of Voices", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3560, 3987, 0), new CoordinateClueInfo("Lithkren. Digsite pendant teleport if unlocked, otherwise take rowboat from west of Mushroom Meadow Mushtree.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2318, 2954, 0), new CoordinateClueInfo("North-east corner of the Isle of Souls.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2094, 2889, 0), new CoordinateClueInfo("West side of the Isle of Souls.", ARMADYLEAN_OR_BANDOSIAN_GUARD)) // Master .put(new WorldPoint(2178, 3209, 0), new CoordinateClueInfo("South of Iorwerth Camp.", BRASSICAN_MAGE)) .put(new WorldPoint(2155, 3100, 0), new CoordinateClueInfo("South of Port Tyras (BJS if 76 Agility).", BRASSICAN_MAGE)) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/Enemy.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/Enemy.java index 6590079639..1aef9b493d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/Enemy.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/Enemy.java @@ -42,7 +42,7 @@ public enum Enemy //appears for hard clue coordinate steps not in the wilderness SARADOMIN_WIZARD("Saradomin Wizard"), //appears for elite clue coordinate steps all areas - ARMADYLIAN_OR_BANDOSIAN_GUARD("Armadylian OR Bandosian Guard"), + ARMADYLEAN_OR_BANDOSIAN_GUARD("Armadylean OR Bandosian Guard"), //appears for master clue coordinate and hot cold clues when single-way combat BRASSICAN_MAGE("Brassican Mage"), //appears for master clue coordinate and hot cold clues when multi-way combat From 4efd609e40081ce6d4c35b87bf83961ba32b72e8 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 19 May 2021 16:39:50 -0400 Subject: [PATCH 07/15] chat timestamps: add timestamps for all message types --- runelite-client/src/main/scripts/ChatBuilder.rs2asm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/runelite-client/src/main/scripts/ChatBuilder.rs2asm b/runelite-client/src/main/scripts/ChatBuilder.rs2asm index c4967ebecf..9f8b32d874 100644 --- a/runelite-client/src/main/scripts/ChatBuilder.rs2asm +++ b/runelite-client/src/main/scripts/ChatBuilder.rs2asm @@ -1077,7 +1077,13 @@ LABEL913: LABEL927: jump LABEL942 LABEL928: + iload 10 ; The id of the messageNode of the message being built + sconst "" + sconst "addTimestamp" + runelite_callback + pop_int ; pop message id sload 14 + join_string 2 ; prepend the timestamp iload 8 iload 9 iconst 10616888 From 94ad05fa0f59ea557e079f51071b921800d4ca7e Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 19 May 2021 22:49:24 -0400 Subject: [PATCH 08/15] minimap: add clan chat dot color configuration --- .../client/plugins/minimap/MinimapConfig.java | 122 +++++++++--------- .../client/plugins/minimap/MinimapDot.java | 9 +- .../client/plugins/minimap/MinimapPlugin.java | 42 +++--- 3 files changed, 90 insertions(+), 83 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapConfig.java index 3ccd0e970f..b084568d52 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapConfig.java @@ -28,69 +28,19 @@ import java.awt.Color; import net.runelite.client.config.Config; import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.ConfigSection; -@ConfigGroup("minimap") +@ConfigGroup(MinimapConfig.GROUP) public interface MinimapConfig extends Config { - @ConfigItem( - keyName = "item", - name = "Item color", - description = "Set the minimap color items are drawn in" - ) - default Color itemColor() //mapdot 0 - { - return new Color(255, 0, 0); - } + String GROUP = "minimap"; - @ConfigItem( - keyName = "npc", - name = "NPC color", - description = "Set the minimap color NPCs are drawn in" + @ConfigSection( + name = "Minimap dot colors", + description = "The colors of dots on the minimap.", + position = 0 ) - default Color npcColor() //mapdot 1 - { - return new Color(255, 255, 0); - } - - @ConfigItem( - keyName = "player", - name = "Player color", - description = "Set the minimap Color players are drawn in" - ) - default Color playerColor() //mapdot 2 - { - return new Color(255, 255, 255); - } - - @ConfigItem( - keyName = "friend", - name = "Friends color", - description = "Set the minimap color your friends are drawn in" - ) - default Color friendColor() //mapdot 3 - { - return new Color(0, 255, 0); - } - - @ConfigItem( - keyName = "team", - name = "Team color", - description = "Set the minimap color your team is drawn in" - ) - default Color teamColor() //mapdot 4 - { - return new Color(19, 110, 247); - } - - @ConfigItem( - keyName = "clan", - name = "Friends Chat color", - description = "Set the minimap color your friends chat members are drawn in" - ) - default Color friendsChatColor() //mapdot 5 - { - return new Color(170, 0, 190); - } + String minimapDotSection = "minimapDotSection"; @ConfigItem( keyName = "hideMinimap", @@ -101,4 +51,60 @@ public interface MinimapConfig extends Config { return false; } + + @ConfigItem( + keyName = "item", + name = "Item color", + description = "Set the minimap color items are drawn in", + section = minimapDotSection + ) + Color itemColor(); + + @ConfigItem( + keyName = "npc", + name = "NPC color", + description = "Set the minimap color NPCs are drawn in", + section = minimapDotSection + ) + Color npcColor(); + + @ConfigItem( + keyName = "player", + name = "Player color", + description = "Set the minimap Color players are drawn in", + section = minimapDotSection + ) + Color playerColor(); + + @ConfigItem( + keyName = "friend", + name = "Friends color", + description = "Set the minimap color your friends are drawn in", + section = minimapDotSection + ) + Color friendColor(); + + @ConfigItem( + keyName = "team", + name = "Team color", + description = "Set the minimap color your team is drawn in", + section = minimapDotSection + ) + Color teamColor(); + + @ConfigItem( + keyName = "clan", // old name from prior to clans + name = "Friends Chat color", + description = "Set the minimap color your friends chat members are drawn in", + section = minimapDotSection + ) + Color friendsChatColor(); + + @ConfigItem( + keyName = "clanchat", + name = "Clan Chat color", + description = "Set the minimap color your clan chat members are drawn in", + section = minimapDotSection + ) + Color clanChatColor(); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapDot.java b/runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapDot.java index 01d95312d9..c86c7ae0f9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapDot.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapDot.java @@ -28,7 +28,7 @@ import java.awt.Color; import net.runelite.api.Client; import net.runelite.api.SpritePixels; -public class MinimapDot +class MinimapDot { private static final int MAP_DOT_WIDTH = 4; private static final int MAP_DOT_HEIGHT = 5; @@ -67,12 +67,9 @@ public class MinimapDot return pixels; } - public static SpritePixels create(Client client, Color color) + static SpritePixels create(Client client, Color color) { int[] pixels = createPixels(color); - - SpritePixels dotSprite = client.createSpritePixels(pixels, MAP_DOT_WIDTH, MAP_DOT_HEIGHT); - - return dotSprite; + return client.createSpritePixels(pixels, MAP_DOT_WIDTH, MAP_DOT_HEIGHT); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapPlugin.java index c70f1b75b7..055dd7695e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapPlugin.java @@ -44,12 +44,18 @@ import net.runelite.client.plugins.PluginDescriptor; @PluginDescriptor( name = "Minimap", - description = "Customize the color of minimap dots", + description = "Customize the color of minimap dots, and hide the minimap", tags = {"items", "npcs", "players"} ) public class MinimapPlugin extends Plugin { - private static final int NUM_MAPDOTS = 6; + private static final int DOT_ITEM = 0; + private static final int DOT_NPC = 1; + private static final int DOT_PLAYER = 2; + private static final int DOT_FRIEND = 3; + private static final int DOT_TEAM = 4; + private static final int DOT_FRIENDSCHAT = 5; + private static final int DOT_CLAN = 6; @Inject private Client client; @@ -66,7 +72,7 @@ public class MinimapPlugin extends Plugin } @Override - protected void startUp() throws Exception + protected void startUp() { updateMinimapWidgetVisibility(config.hideMinimap()); storeOriginalDots(); @@ -74,7 +80,7 @@ public class MinimapPlugin extends Plugin } @Override - protected void shutDown() throws Exception + protected void shutDown() { updateMinimapWidgetVisibility(false); restoreOriginalDots(); @@ -93,7 +99,7 @@ public class MinimapPlugin extends Plugin @Subscribe public void onConfigChanged(ConfigChanged event) { - if (!event.getGroup().equals("minimap")) + if (!event.getGroup().equals(MinimapConfig.GROUP)) { return; } @@ -149,23 +155,21 @@ public class MinimapPlugin extends Plugin return; } - Color[] minimapDotColors = getColors(); - for (int i = 0; i < mapDots.length && i < minimapDotColors.length; ++i) - { - mapDots[i] = MinimapDot.create(this.client, minimapDotColors[i]); - } + applyDot(mapDots, DOT_ITEM, config.itemColor()); + applyDot(mapDots, DOT_NPC, config.npcColor()); + applyDot(mapDots, DOT_PLAYER, config.playerColor()); + applyDot(mapDots, DOT_FRIEND, config.friendColor()); + applyDot(mapDots, DOT_TEAM, config.teamColor()); + applyDot(mapDots, DOT_FRIENDSCHAT, config.friendsChatColor()); + applyDot(mapDots, DOT_CLAN, config.clanChatColor()); } - private Color[] getColors() + private void applyDot(SpritePixels[] mapDots, int id, Color color) { - Color[] colors = new Color[NUM_MAPDOTS]; - colors[0] = config.itemColor(); - colors[1] = config.npcColor(); - colors[2] = config.playerColor(); - colors[3] = config.friendColor(); - colors[4] = config.teamColor(); - colors[5] = config.friendsChatColor(); - return colors; + if (id < mapDots.length && color != null) + { + mapDots[id] = MinimapDot.create(client, color); + } } private void storeOriginalDots() From 2a118bdb4d74fed561e571384d5e55297e2b2c71 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 16 May 2021 20:10:57 -0400 Subject: [PATCH 09/15] ui: queue repaint when progressbar is edited --- .../client/plugins/xptracker/XpInfoBox.java | 4 ---- .../client/ui/components/ProgressBar.java | 24 ++++++++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) 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 e188a1df57..0a0716c00e 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 @@ -304,14 +304,11 @@ class XpInfoBox extends JPanel tooltipLabel == XpProgressBarLabel.PERCENTAGE ? "of goal" : "till goal lvl")); progressBar.setDimmed(skillPaused); - - progressBar.repaint(); } else if (!paused && skillPaused) { // React to the skill state now being paused progressBar.setDimmed(true); - progressBar.repaint(); paused = true; pauseSkill.setText("Unpause"); } @@ -319,7 +316,6 @@ class XpInfoBox extends JPanel { // React to the skill being unpaused (without update) progressBar.setDimmed(false); - progressBar.repaint(); paused = false; pauseSkill.setText("Pause"); } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/components/ProgressBar.java b/runelite-client/src/main/java/net/runelite/client/ui/components/ProgressBar.java index 54b2eec733..3040440b4d 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/components/ProgressBar.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/components/ProgressBar.java @@ -33,7 +33,6 @@ import java.util.List; import javax.swing.JLabel; import javax.swing.SwingConstants; import javax.swing.border.EmptyBorder; -import lombok.Setter; import net.runelite.client.ui.FontManager; import net.runelite.client.ui.components.shadowlabel.JShadowedLabel; @@ -42,13 +41,8 @@ import net.runelite.client.ui.components.shadowlabel.JShadowedLabel; */ public class ProgressBar extends DimmableJPanel { - @Setter private int maximumValue; - - @Setter private int value; - - @Setter private List positions = Collections.emptyList(); private final JLabel leftLabel = new JShadowedLabel(); @@ -157,4 +151,22 @@ public class ProgressBar extends DimmableJPanel return (value * 100) / maximumValue; } + + public void setMaximumValue(int maximumValue) + { + this.maximumValue = maximumValue; + repaint(); + } + + public void setValue(int value) + { + this.value = value; + repaint(); + } + + public void setPositions(List positions) + { + this.positions = positions; + repaint(); + } } From 38cd07d025c6d6e6eb73d19534f866c5bcb5e25d Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 17 May 2021 18:25:28 -0400 Subject: [PATCH 10/15] discord plugin: sanity check user id and avatar id before building url --- .../client/plugins/discord/DiscordPlugin.java | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) 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 75c076b4c9..966cc381c7 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 @@ -25,6 +25,7 @@ */ package net.runelite.client.plugins.discord; +import com.google.common.base.CharMatcher; import com.google.common.base.Strings; import com.google.inject.Inject; import com.google.inject.Provides; @@ -246,18 +247,30 @@ public class DiscordPlugin extends Plugin return; } - String url = "https://cdn.discordapp.com/avatars/" + event.getUserId() + "/" + event.getAvatarId() + ".png"; + CharMatcher matcher = CharMatcher.anyOf("abcdef0123456789"); + if (!matcher.matchesAllOf(event.getUserId()) || !matcher.matchesAllOf(event.getAvatarId())) + { + // userid is actually a snowflake, but the matcher is sufficient + return; + } + + final String url; if (Strings.isNullOrEmpty(event.getAvatarId())) { final String[] split = memberById.getName().split("#", 2); - - if (split.length == 2) + if (split.length != 2) { - int disc = Integer.valueOf(split[1]); - int avatarId = disc % 5; - url = "https://cdn.discordapp.com/embed/avatars/" + avatarId + ".png"; + return; } + + int disc = Integer.parseInt(split[1]); + int avatarId = disc % 5; + url = "https://cdn.discordapp.com/embed/avatars/" + avatarId + ".png"; + } + else + { + url = "https://cdn.discordapp.com/avatars/" + event.getUserId() + "/" + event.getAvatarId() + ".png"; } log.debug("Got user avatar {}", url); From fa145986b270ea545f9624084cb30ec9bb02680e Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 17 May 2021 18:44:46 -0400 Subject: [PATCH 11/15] party service: sanitize user names --- .../java/net/runelite/client/ws/PartyService.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/ws/PartyService.java b/runelite-client/src/main/java/net/runelite/client/ws/PartyService.java index a96b6704ff..4c70a624e6 100644 --- a/runelite-client/src/main/java/net/runelite/client/ws/PartyService.java +++ b/runelite-client/src/main/java/net/runelite/client/ws/PartyService.java @@ -41,6 +41,7 @@ import net.runelite.client.chat.QueuedMessage; import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.PartyChanged; +import net.runelite.client.util.Text; import static net.runelite.client.util.Text.JAGEX_PRINTABLE_CHAR_MATCHER; import net.runelite.http.api.ws.messages.party.Join; import net.runelite.http.api.ws.messages.party.Part; @@ -55,6 +56,7 @@ public class PartyService { public static final int PARTY_MAX = 15; private static final int MAX_MESSAGE_LEN = 150; + private static final int MAX_USERNAME_LEN = 32; // same as Discord private final WSClient wsClient; private final SessionManager sessionManager; @@ -129,7 +131,7 @@ public class PartyService return; } - final PartyMember partyMember = new PartyMember(message.getMemberId(), message.getName()); + final PartyMember partyMember = new PartyMember(message.getMemberId(), cleanUsername(message.getName())); members.add(partyMember); final PartyMember localMember = getLocalMember(); @@ -215,4 +217,14 @@ public class PartyService { return localPartyId.equals(partyId); } + + private static String cleanUsername(String username) + { + String s = Text.removeTags(JAGEX_PRINTABLE_CHAR_MATCHER.retainFrom(username)); + if (s.length() >= MAX_USERNAME_LEN) + { + s = s.substring(0, MAX_USERNAME_LEN); + } + return s; + } } From ebfc3dab89fe964078447d986ba6b7c71addaa50 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 16 May 2021 17:13:17 -0400 Subject: [PATCH 12/15] info panel: adjust nav button priority --- .../main/java/net/runelite/client/plugins/info/InfoPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPlugin.java index 6b85037e67..23e0b1314a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPlugin.java @@ -55,7 +55,7 @@ public class InfoPlugin extends Plugin navButton = NavigationButton.builder() .tooltip("Info") .icon(icon) - .priority(9) + .priority(10) .panel(panel) .build(); From 8450795baba13823e9165c9ecc406a7fb64c4ad6 Mon Sep 17 00:00:00 2001 From: "JOROUSS-2070\\JoRouss" Date: Mon, 29 Mar 2021 01:07:10 -0400 Subject: [PATCH 13/15] party: add party panel --- .../client/events/PartyMemberAvatar.java | 36 +++ .../client/plugins/discord/DiscordPlugin.java | 19 +- .../client/plugins/discord/DiscordState.java | 35 +-- .../client/plugins/party/PartyConfig.java | 47 +++- .../client/plugins/party/PartyMemberBox.java | 192 ++++++++++++++ .../client/plugins/party/PartyPanel.java | 214 ++++++++++++++++ .../client/plugins/party/PartyPlugin.java | 240 +++++++++++++----- .../client/plugins/party/PartyRequestBox.java | 111 ++++++++ .../plugins/party/PartyStatsOverlay.java | 25 +- .../client/plugins/party/data/PartyData.java | 8 +- .../party/messages/CharacterNameUpdate.java | 34 +++ .../net/runelite/client/ws/PartyService.java | 34 ++- .../java/net/runelite/client/ws/WSClient.java | 2 +- .../client/plugins/party/cancel_icon.png | Bin 0 -> 299 bytes .../client/plugins/party/confirm_icon.png | Bin 0 -> 273 bytes .../client/plugins/party/panel_icon.png | Bin 0 -> 710 bytes .../plugins/discord/DiscordStateTest.java | 3 - 17 files changed, 863 insertions(+), 137 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/events/PartyMemberAvatar.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/party/PartyMemberBox.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/party/PartyRequestBox.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/party/messages/CharacterNameUpdate.java create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/party/cancel_icon.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/party/confirm_icon.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/party/panel_icon.png diff --git a/runelite-client/src/main/java/net/runelite/client/events/PartyMemberAvatar.java b/runelite-client/src/main/java/net/runelite/client/events/PartyMemberAvatar.java new file mode 100644 index 0000000000..d1c44c87d5 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/events/PartyMemberAvatar.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021, Jonathan Rousseau + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.events; + +import java.awt.image.BufferedImage; +import java.util.UUID; +import lombok.Value; + +@Value +public class PartyMemberAvatar +{ + private final UUID memberId; + private final BufferedImage image; +} 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 966cc381c7..a4bb4c7d1e 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 @@ -1,6 +1,7 @@ /* * Copyright (c) 2018, Tomas Slusny * Copyright (c) 2018, PandahRS + * Copyright (c) 2021, Jonathan Rousseau * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,7 +51,6 @@ import net.runelite.api.events.StatChanged; import net.runelite.client.config.ConfigManager; import net.runelite.client.discord.DiscordService; import net.runelite.client.discord.events.DiscordJoinGame; -import net.runelite.client.discord.events.DiscordJoinRequest; import net.runelite.client.discord.events.DiscordReady; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; @@ -215,20 +215,6 @@ public class DiscordPlugin extends Plugin partyService.setUsername(event.getUsername() + "#" + event.getDiscriminator()); } - @Subscribe - public void onDiscordJoinRequest(DiscordJoinRequest request) - { - // In order for the "Invite to join" message to work we need to have a valid party in Discord presence. - // We lazily create the party here in order to avoid the (1 of 15) being permanently in the Discord status. - if (!partyService.isInParty()) - { - // Change to my party id, which is advertised in the Discord presence secret. This will open the socket, - // send a join, and cause a UserJoin later for me, which will then update the presence and allow the - // "Invite to join" to continue. - partyService.changeParty(partyService.getLocalPartyId()); - } - } - @Subscribe public void onDiscordJoinGame(DiscordJoinGame joinGame) { @@ -303,7 +289,8 @@ public class DiscordPlugin extends Plugin { image = ImageIO.read(inputStream); } - memberById.setAvatar(image); + + partyService.setPartyMemberAvatar(memberById.getMemberId(), image); } finally { 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 f311511d26..b9039cee6d 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 @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2021, Jonathan Rousseau * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,7 +32,6 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import javax.inject.Inject; @@ -55,7 +55,6 @@ class DiscordState private Instant updated; } - private final UUID partyId = UUID.randomUUID(); private final List events = new ArrayList<>(); private final DiscordService discordService; private final DiscordConfig config; @@ -106,15 +105,10 @@ class DiscordState .largeImageText(lastPresence.getLargeImageText()) .startTimestamp(lastPresence.getStartTimestamp()) .smallImageKey(lastPresence.getSmallImageKey()) - .partyMax(lastPresence.getPartyMax()) - .partySize(party.getMembers().size()); + .partyMax(lastPresence.getPartyMax()); - if (!party.isInParty() || party.isPartyOwner()) - { - // This is only used to identify the invites on Discord's side. Our party ids are the secret. - presenceBuilder.partyId(partyId.toString()); - presenceBuilder.joinSecret(party.getLocalPartyId().toString()); - } + + setPresencePartyInfo(presenceBuilder); discordService.updatePresence(presenceBuilder.build()); } @@ -205,8 +199,7 @@ class DiscordState .details(MoreObjects.firstNonNull(details, "")) .largeImageText(runeliteTitle + " v" + versionShortHand) .smallImageKey(imageKey) - .partyMax(PARTY_MAX) - .partySize(party.getMembers().size()); + .partyMax(PARTY_MAX); final Instant startTime; switch (config.elapsedTimeType()) @@ -233,11 +226,7 @@ class DiscordState presenceBuilder.startTimestamp(startTime); - if (!party.isInParty() || party.isPartyOwner()) - { - presenceBuilder.partyId(partyId.toString()); - presenceBuilder.joinSecret(party.getLocalPartyId().toString()); - } + setPresencePartyInfo(presenceBuilder); final DiscordPresence presence = presenceBuilder.build(); @@ -286,4 +275,16 @@ class DiscordState updatePresenceWithLatestEvent(); } } + + private void setPresencePartyInfo(DiscordPresence.DiscordPresenceBuilder presenceBuilder) + { + if (party.isInParty()) + { + presenceBuilder.partySize(party.getMembers().size()); + + // Set public party id and secret + presenceBuilder.partyId(party.getPublicPartyId().toString()); + presenceBuilder.joinSecret(party.getPartyId().toString()); + } + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyConfig.java index cf33302d21..5d38da1b3d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyConfig.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2021, Jonathan Rousseau * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,23 +29,16 @@ import net.runelite.client.config.Config; import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigItem; -@ConfigGroup("party") +@ConfigGroup(PartyConfig.GROUP) public interface PartyConfig extends Config { - @ConfigItem( - keyName = "stats", - name = "Stats", - description = "Enables party stats overlay showing HP, prayer and player name" - ) - default boolean stats() - { - return true; - } + String GROUP = "party"; @ConfigItem( keyName = "pings", name = "Pings", - description = "Enables party pings (shift + left-click)" + description = "Enables party pings (shift + left-click)", + position = 1 ) default boolean pings() { @@ -54,7 +48,8 @@ public interface PartyConfig extends Config @ConfigItem( keyName = "sounds", name = "Sound on ping", - description = "Enables sound notification on party ping" + description = "Enables sound notification on party ping", + position = 2 ) default boolean sounds() { @@ -64,7 +59,8 @@ public interface PartyConfig extends Config @ConfigItem( keyName = "messages", name = "Join messages", - description = "Enables join/leave game messages" + description = "Enables members join/leave game messages", + position = 3 ) default boolean messages() { @@ -74,10 +70,33 @@ public interface PartyConfig extends Config @ConfigItem( keyName = "recolorNames", name = "Recolor names", - description = "Recolor stats overlay names based on unique color hash" + description = "Recolor party members names based on unique color hash", + position = 4 ) default boolean recolorNames() { return true; } + + @ConfigItem( + keyName = "autoOverlay", + name = "Auto overlay", + description = "Automatically add an overlay with player data when a member joins", + position = 5 + ) + default boolean autoOverlay() + { + return true; + } + + @ConfigItem( + keyName = "includeSelf", + name = "Include yourself", + description = "Shows yourself in the panel as part of the party", + position = 6 + ) + default boolean includeSelf() + { + return false; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyMemberBox.java b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyMemberBox.java new file mode 100644 index 0000000000..7b3839b406 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyMemberBox.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2021, Jonathan Rousseau + * 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.party; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.SwingConstants; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import lombok.AccessLevel; +import lombok.Getter; +import net.runelite.client.plugins.party.data.PartyData; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.DynamicGridLayout; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.components.MouseDragEventForwarder; +import net.runelite.client.ui.components.ProgressBar; +import net.runelite.client.util.ImageUtil; + +class PartyMemberBox extends JPanel +{ + private static final Color HP_FG = new Color(0, 146, 54, 230); + private static final Color HP_BG = new Color(102, 15, 16, 230); + private static final Color PRAY_FG = new Color(0, 149, 151); + private static final Color PRAY_BG = Color.black; + + @Getter(AccessLevel.PACKAGE) + private final PartyData memberPartyData; + + private final ProgressBar hpBar = new ProgressBar(); + private final ProgressBar prayerBar = new ProgressBar(); + + private final JLabel topName = new JLabel(); + private final JLabel bottomName = new JLabel(); + + private final JLabel avatar = new JLabel(); + + private final PartyConfig config; + + private boolean avatarSet; + + PartyMemberBox(final PartyConfig config, final JComponent panel, final PartyData memberPartyData) + { + this.config = config; + this.memberPartyData = memberPartyData; + + setLayout(new BorderLayout()); + setBorder(new EmptyBorder(5, 0, 0, 0)); + + /* The box's wrapping container */ + final JPanel container = new JPanel(); + container.setLayout(new BorderLayout()); + container.setBackground(ColorScheme.DARKER_GRAY_COLOR); + container.setBorder(new EmptyBorder(5, 5, 5, 5)); + + // Create Toggle overlay + final JMenuItem overlay = new JMenuItem("Toggle overlay"); + overlay.addActionListener(e -> memberPartyData.setShowOverlay(!memberPartyData.isShowOverlay())); + + // Create popup menu + final JPopupMenu popupMenu = new JPopupMenu(); + popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5)); + popupMenu.add(overlay); + + // create a line border with the specified color and width + Border border = BorderFactory.createLineBorder(Color.gray, 1); + avatar.setBorder(border); + + avatar.setHorizontalAlignment(SwingConstants.CENTER); + avatar.setVerticalAlignment(SwingConstants.CENTER); + avatar.setPreferredSize(new Dimension(35, 35)); + + /* Contains the avatar and the names */ + final JPanel headerPanel = new JPanel(); + headerPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + headerPanel.setLayout(new BorderLayout()); + headerPanel.setBorder(new EmptyBorder(0, 0, 3, 0)); + + /* Contains ServiceName name and osrs name */ + final JPanel namesPanel = new JPanel(); + namesPanel.setLayout(new DynamicGridLayout(2, 1)); + namesPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + namesPanel.setBorder(new EmptyBorder(2, 5, 2, 5)); + + topName.setFont(FontManager.getRunescapeSmallFont()); + bottomName.setFont(FontManager.getRunescapeSmallFont()); + + topName.putClientProperty("html.disable", Boolean.TRUE); + bottomName.putClientProperty("html.disable", Boolean.TRUE); + + namesPanel.add(topName); // top + namesPanel.add(bottomName); // bottom + + headerPanel.add(avatar, BorderLayout.WEST); + headerPanel.add(namesPanel, BorderLayout.CENTER); + + JPanel progressWrapper = new JPanel(); + progressWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR); + progressWrapper.setLayout(new DynamicGridLayout(2, 1, 0, 2)); + + hpBar.setBackground(HP_BG); + hpBar.setForeground(HP_FG); + + prayerBar.setBackground(PRAY_BG); + prayerBar.setForeground(PRAY_FG); + + progressWrapper.add(hpBar); // top + progressWrapper.add(prayerBar); // bottom + + container.add(headerPanel, BorderLayout.NORTH); + container.add(progressWrapper, BorderLayout.SOUTH); + + container.setComponentPopupMenu(popupMenu); + + // forward mouse drag events to parent panel for drag and drop reordering + MouseDragEventForwarder mouseDragEventForwarder = new MouseDragEventForwarder(panel); + container.addMouseListener(mouseDragEventForwarder); + container.addMouseMotionListener(mouseDragEventForwarder); + + add(container, BorderLayout.NORTH); + + update(); + } + + void update() + { + // Avatar + if (!avatarSet && memberPartyData.getMember().getAvatar() != null) + { + ImageIcon icon = new ImageIcon(ImageUtil.resizeImage(memberPartyData.getMember().getAvatar(), 32, 32)); + icon.getImage().flush(); + avatar.setIcon(icon); + + avatarSet = true; + } + + // Update progress bars + hpBar.setValue(memberPartyData.getHitpoints()); + hpBar.setMaximumValue(memberPartyData.getMaxHitpoints()); + hpBar.setCenterLabel(progressBarLabel(memberPartyData.getHitpoints(), memberPartyData.getMaxHitpoints())); + + prayerBar.setValue(memberPartyData.getPrayer()); + prayerBar.setMaximumValue(memberPartyData.getMaxPrayer()); + prayerBar.setCenterLabel(progressBarLabel(memberPartyData.getPrayer(), memberPartyData.getMaxPrayer())); + + // Update name labels + Color playerColor = config.recolorNames() ? memberPartyData.getColor() : Color.WHITE; + boolean isLoggedIn = !memberPartyData.getCharacterName().isEmpty(); + + topName.setForeground(playerColor); + topName.setText(memberPartyData.getMember().getName()); + + bottomName.setForeground(isLoggedIn ? playerColor : Color.GRAY); + bottomName.setText(isLoggedIn ? memberPartyData.getCharacterName() : "Logged out"); + } + + private static String progressBarLabel(int current, int max) + { + return current + "/" + max; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPanel.java new file mode 100644 index 0000000000..98cecb81bf --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPanel.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2021, Jonathan Rousseau + * 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.party; + +import com.google.inject.Inject; +import java.awt.BorderLayout; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; +import net.runelite.client.plugins.party.data.PartyData; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.PluginPanel; +import net.runelite.client.ui.components.DragAndDropReorderPane; +import net.runelite.client.ui.components.PluginErrorPanel; +import net.runelite.client.ws.PartyService; + +class PartyPanel extends PluginPanel +{ + private static final String BTN_CREATE_TEXT = "Create party"; + private static final String BTN_LEAVE_TEXT = "Leave party"; + + private final PartyPlugin plugin; + private final PartyService party; + private final PartyConfig config; + + private final Map requestBoxes = new HashMap<>(); + private final Map memberBoxes = new HashMap<>(); + + private final JButton startButton = new JButton(); + + private final PluginErrorPanel noPartyPanel = new PluginErrorPanel(); + private final PluginErrorPanel partyEmptyPanel = new PluginErrorPanel(); + private final JComponent memberBoxPanel = new DragAndDropReorderPane(); + private final JComponent requestBoxPanel = new DragAndDropReorderPane(); + + @Inject + PartyPanel(final PartyPlugin plugin, final PartyConfig config, final PartyService party) + { + this.plugin = plugin; + this.party = party; + this.config = config; + + setBorder(new EmptyBorder(10, 10, 10, 10)); + setBackground(ColorScheme.DARK_GRAY_COLOR); + setLayout(new BorderLayout()); + + final JPanel layoutPanel = new JPanel(); + BoxLayout boxLayout = new BoxLayout(layoutPanel, BoxLayout.Y_AXIS); + layoutPanel.setLayout(boxLayout); + add(layoutPanel, BorderLayout.NORTH); + + final JPanel topPanel = new JPanel(); + + topPanel.setBorder(new EmptyBorder(0, 0, 10, 0)); + topPanel.setLayout(new BorderLayout()); + + topPanel.add(startButton, BorderLayout.CENTER); + + layoutPanel.add(topPanel); + layoutPanel.add(requestBoxPanel); + layoutPanel.add(memberBoxPanel); + + startButton.setText(party.isInParty() ? BTN_LEAVE_TEXT : BTN_CREATE_TEXT); + startButton.setFocusable(false); + + topPanel.add(startButton); + + startButton.addActionListener(e -> + { + if (party.isInParty()) + { + // Leave party + final int result = JOptionPane.showOptionDialog(startButton, + "Are you sure you want to leave the party?", + "Leave party?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, + null, new String[]{"Yes", "No"}, "No"); + + if (result == JOptionPane.YES_OPTION) + { + party.changeParty(null); + } + } + else + { + // Create party + party.changeParty(party.getLocalPartyId()); + } + }); + + noPartyPanel.setContent("Not in a party", "Create a party to begin."); + partyEmptyPanel.setContent("Party created", "You can now invite friends!"); + + updateParty(); + } + + void updateParty() + { + remove(noPartyPanel); + remove(partyEmptyPanel); + + startButton.setText(party.isInParty() ? BTN_LEAVE_TEXT : BTN_CREATE_TEXT); + + if (!party.isInParty()) + { + add(noPartyPanel); + } + else if (plugin.getPartyDataMap().size() <= 1) + { + add(partyEmptyPanel); + } + } + + void addMember(PartyData partyData) + { + if (!memberBoxes.containsKey(partyData.getMember().getMemberId())) + { + PartyMemberBox partyMemberBox = new PartyMemberBox(config, memberBoxPanel, partyData); + memberBoxes.put(partyData.getMember().getMemberId(), partyMemberBox); + memberBoxPanel.add(partyMemberBox); + memberBoxPanel.revalidate(); + } + updateParty(); + } + + void removeAllMembers() + { + memberBoxes.forEach((key, value) -> memberBoxPanel.remove(value)); + memberBoxPanel.revalidate(); + memberBoxes.clear(); + updateParty(); + } + + void removeMember(UUID memberId) + { + final PartyMemberBox memberBox = memberBoxes.remove(memberId); + + if (memberBox != null) + { + memberBoxPanel.remove(memberBox); + memberBoxPanel.revalidate(); + } + + updateParty(); + } + + void updateMember(UUID userId) + { + final PartyMemberBox memberBox = memberBoxes.get(userId); + + if (memberBox != null) + { + memberBox.update(); + } + } + + void updateAll() + { + memberBoxes.forEach((key, value) -> value.update()); + } + + void addRequest(String userId, String userName) + { + PartyRequestBox partyRequestBox = new PartyRequestBox(plugin, requestBoxPanel, userId, userName); + requestBoxes.put(userId, partyRequestBox); + requestBoxPanel.add(partyRequestBox); + requestBoxPanel.revalidate(); + } + + void removeAllRequests() + { + requestBoxes.forEach((key, value) -> requestBoxPanel.remove(value)); + requestBoxPanel.revalidate(); + requestBoxes.clear(); + } + + void removeRequest(String userId) + { + final PartyRequestBox requestBox = requestBoxes.remove(userId); + + if (requestBox != null) + { + requestBoxPanel.remove(requestBox); + requestBoxPanel.revalidate(); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java index d6c008d40d..5aca5fdcbf 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2019, Tomas Slusny + * Copyright (c) 2021, Jonathan Rousseau * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,9 +25,11 @@ */ package net.runelite.client.plugins.party; +import com.google.common.base.Strings; import com.google.inject.Binder; import com.google.inject.Provides; import java.awt.Color; +import java.awt.image.BufferedImage; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collections; @@ -37,18 +40,20 @@ import java.util.UUID; import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Named; +import javax.swing.SwingUtilities; import lombok.Getter; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.KeyCode; -import net.runelite.api.MenuAction; import net.runelite.api.MenuEntry; +import net.runelite.api.Player; import net.runelite.api.Skill; import net.runelite.api.SoundEffectID; import net.runelite.api.Tile; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.CommandExecuted; +import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.GameTick; import net.runelite.api.events.MenuOptionClicked; import net.runelite.client.callback.ClientThread; @@ -57,21 +62,29 @@ import net.runelite.client.chat.ChatMessageBuilder; import net.runelite.client.chat.ChatMessageManager; import net.runelite.client.chat.QueuedMessage; import net.runelite.client.config.ConfigManager; +import net.runelite.client.discord.DiscordService; +import net.runelite.client.discord.events.DiscordJoinRequest; import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.events.OverlayMenuClicked; +import net.runelite.client.events.ConfigChanged; import net.runelite.client.events.PartyChanged; +import net.runelite.client.events.PartyMemberAvatar; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.party.data.PartyData; import net.runelite.client.plugins.party.data.PartyTilePingData; +import net.runelite.client.plugins.party.messages.CharacterNameUpdate; import net.runelite.client.plugins.party.messages.LocationUpdate; import net.runelite.client.plugins.party.messages.SkillUpdate; import net.runelite.client.plugins.party.messages.TilePing; import net.runelite.client.task.Schedule; +import net.runelite.client.ui.ClientToolbar; +import net.runelite.client.ui.NavigationButton; import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.ui.overlay.worldmap.WorldMapPoint; import net.runelite.client.ui.overlay.worldmap.WorldMapPointManager; import net.runelite.client.util.ColorUtil; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.Text; import net.runelite.client.ws.PartyMember; import net.runelite.client.ws.PartyService; import net.runelite.client.ws.WSClient; @@ -81,7 +94,8 @@ import net.runelite.http.api.ws.messages.party.UserSync; @PluginDescriptor( name = "Party", - description = "Shows useful information about current party" + description = "Party management and basic info", + enabledByDefault = false ) public class PartyPlugin extends Plugin { @@ -118,6 +132,12 @@ public class PartyPlugin extends Plugin @Inject private ClientThread clientThread; + @Inject + private ClientToolbar clientToolbar; + + @Inject + private DiscordService discordService; + @Inject @Named("developerMode") boolean developerMode; @@ -128,8 +148,11 @@ public class PartyPlugin extends Plugin @Getter private final List pendingTilePings = Collections.synchronizedList(new ArrayList<>()); + private PartyPanel panel; + private NavigationButton navButton; + private int lastHp, lastPray; - private boolean doSync; + private String lastCharacterName = ""; private boolean sendAlert; @Override @@ -141,17 +164,36 @@ public class PartyPlugin extends Plugin @Override protected void startUp() throws Exception { + panel = injector.getInstance(PartyPanel.class); + + final BufferedImage icon = ImageUtil.loadImageResource(PartyPlugin.class, "panel_icon.png"); + + navButton = NavigationButton.builder() + .tooltip("Party") + .icon(icon) + .priority(9) + .panel(panel) + .build(); + + clientToolbar.addNavigation(navButton); + overlayManager.add(partyStatsOverlay); overlayManager.add(partyPingOverlay); wsClient.registerMessage(SkillUpdate.class); wsClient.registerMessage(TilePing.class); wsClient.registerMessage(LocationUpdate.class); - doSync = true; // Delay sync so eventbus can process correctly. + wsClient.registerMessage(CharacterNameUpdate.class); + // Delay sync so the eventbus can register prior to the sync response + SwingUtilities.invokeLater(this::requestSync); } @Override protected void shutDown() throws Exception { + clientToolbar.removeNavigation(navButton); + + panel = null; + partyDataMap.clear(); pendingTilePings.clear(); worldMapManager.removeIf(PartyWorldMapPoint.class::isInstance); @@ -160,7 +202,7 @@ public class PartyPlugin extends Plugin wsClient.unregisterMessage(SkillUpdate.class); wsClient.unregisterMessage(TilePing.class); wsClient.unregisterMessage(LocationUpdate.class); - doSync = false; + wsClient.unregisterMessage(CharacterNameUpdate.class); sendAlert = false; } @@ -171,28 +213,28 @@ public class PartyPlugin extends Plugin } @Subscribe - public void onOverlayMenuClicked(OverlayMenuClicked event) + public void onConfigChanged(ConfigChanged event) { - if (event.getEntry().getMenuAction() == MenuAction.RUNELITE_OVERLAY && - event.getEntry().getTarget().equals("Party") && - event.getEntry().getOption().equals("Leave")) + if (event.getGroup().equals(PartyConfig.GROUP)) { - party.changeParty(null); + final PartyMember localMember = party.getLocalMember(); - if (!config.messages()) + if (localMember != null) { - return; + if (config.includeSelf()) + { + final PartyData partyData = getPartyData(localMember.getMemberId()); + assert partyData != null; + SwingUtilities.invokeLater(() -> panel.addMember(partyData)); + } + else + { + SwingUtilities.invokeLater(() -> panel.removeMember(localMember.getMemberId())); + } } - final String leaveMessage = new ChatMessageBuilder() - .append(ChatColorType.HIGHLIGHT) - .append("You have left the party.") - .build(); - - chatMessageManager.queue(QueuedMessage.builder() - .type(ChatMessageType.FRIENDSCHATNOTIFICATION) - .runeLiteFormattedMessage(leaveMessage) - .build()); + // rebuild the panel in the event the "Recolor names" option changes + SwingUtilities.invokeLater(panel::updateAll); } } @@ -236,6 +278,35 @@ public class PartyPlugin extends Plugin wsClient.send(tilePing); } + @Subscribe + public void onDiscordJoinRequest(DiscordJoinRequest request) + { + final String requestMessage = new ChatMessageBuilder() + .append(ChatColorType.HIGHLIGHT) + .append("New join request received. Check your Party panel.") + .build(); + + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.FRIENDSCHATNOTIFICATION) + .runeLiteFormattedMessage(requestMessage) + .build()); + + String userName = request.getUsername() + "#" + request.getDiscriminator(); + SwingUtilities.invokeLater(() -> panel.addRequest(request.getUserId(), userName)); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + checkStateChanged(false); + } + + public void replyToRequest(String userId, int reply) + { + discordService.respondToRequest(userId, reply); + panel.removeRequest(userId); + } + @Subscribe public void onTilePing(TilePing event) { @@ -291,41 +362,35 @@ public class PartyPlugin extends Plugin sendInstructionMessage(); } - if (doSync && !party.getMembers().isEmpty()) + checkStateChanged(false); + } + + void requestSync() + { + if (!party.getMembers().isEmpty()) { // Request sync final UserSync userSync = new UserSync(); userSync.setMemberId(party.getLocalMember().getMemberId()); ws.send(userSync); } + } - doSync = false; + @Subscribe + public void onCharacterNameUpdate(final CharacterNameUpdate event) + { + final PartyData partyData = getPartyData(event.getMemberId()); - final int currentHealth = client.getBoostedSkillLevel(Skill.HITPOINTS); - final int currentPrayer = client.getBoostedSkillLevel(Skill.PRAYER); - final int realHealth = client.getRealSkillLevel(Skill.HITPOINTS); - final int realPrayer = client.getRealSkillLevel(Skill.PRAYER); - final PartyMember localMember = party.getLocalMember(); - - if (localMember != null) + if (partyData == null) { - if (currentHealth != lastHp) - { - final SkillUpdate update = new SkillUpdate(Skill.HITPOINTS, currentHealth, realHealth); - update.setMemberId(localMember.getMemberId()); - ws.send(update); - } - - if (currentPrayer != lastPray) - { - final SkillUpdate update = new SkillUpdate(Skill.PRAYER, currentPrayer, realPrayer); - update.setMemberId(localMember.getMemberId()); - ws.send(update); - } + return; } - lastHp = currentHealth; - lastPray = currentPrayer; + String name = event.getCharacterName(); + name = Text.removeTags(Text.toJagexName(name)); + + partyData.setCharacterName(name); + SwingUtilities.invokeLater(() -> panel.updateMember(partyData.getMember().getMemberId())); } @Subscribe @@ -348,6 +413,8 @@ public class PartyPlugin extends Plugin partyData.setPrayer(event.getValue()); partyData.setMaxPrayer(event.getMax()); } + + SwingUtilities.invokeLater(() -> panel.updateMember(partyData.getMember().getMemberId())); } @Subscribe @@ -375,7 +442,7 @@ public class PartyPlugin extends Plugin final String joinMessage = new ChatMessageBuilder() .append(ChatColorType.HIGHLIGHT) - .append(partyData.getName()) + .append(partyData.getMember().getName()) .append(" has joined the party!") .build(); @@ -386,7 +453,7 @@ public class PartyPlugin extends Plugin final PartyMember localMember = party.getLocalMember(); - if (localMember != null && partyData.getMemberId().equals(localMember.getMemberId())) + if (localMember != null && partyData.getMember().getMemberId().equals(localMember.getMemberId())) { sendAlert = true; } @@ -394,6 +461,11 @@ public class PartyPlugin extends Plugin @Subscribe public void onUserSync(final UserSync event) + { + checkStateChanged(true); + } + + private void checkStateChanged(boolean forceSend) { final int currentHealth = client.getBoostedSkillLevel(Skill.HITPOINTS); final int currentPrayer = client.getBoostedSkillLevel(Skill.PRAYER); @@ -401,16 +473,36 @@ public class PartyPlugin extends Plugin final int realPrayer = client.getRealSkillLevel(Skill.PRAYER); final PartyMember localMember = party.getLocalMember(); + final Player localPlayer = client.getLocalPlayer(); + final String characterName = Strings.nullToEmpty(localPlayer != null && client.getGameState().getState() >= GameState.LOADING.getState() ? localPlayer.getName() : null); + if (localMember != null) { - final SkillUpdate hpUpdate = new SkillUpdate(Skill.HITPOINTS, currentHealth, realHealth); - hpUpdate.setMemberId(localMember.getMemberId()); - ws.send(hpUpdate); + if (forceSend || currentHealth != lastHp) + { + final SkillUpdate update = new SkillUpdate(Skill.HITPOINTS, currentHealth, realHealth); + update.setMemberId(localMember.getMemberId()); + ws.send(update); + } - final SkillUpdate prayUpdate = new SkillUpdate(Skill.PRAYER, currentPrayer, realPrayer); - prayUpdate.setMemberId(localMember.getMemberId()); - ws.send(prayUpdate); + if (forceSend || currentPrayer != lastPray) + { + final SkillUpdate update = new SkillUpdate(Skill.PRAYER, currentPrayer, realPrayer); + update.setMemberId(localMember.getMemberId()); + ws.send(update); + } + + if (forceSend || !characterName.equals(lastCharacterName)) + { + final CharacterNameUpdate update = new CharacterNameUpdate(characterName); + update.setMemberId(localMember.getMemberId()); + ws.send(update); + } } + + lastHp = currentHealth; + lastPray = currentPrayer; + lastCharacterName = characterName; } @Subscribe @@ -424,7 +516,7 @@ public class PartyPlugin extends Plugin { final String joinMessage = new ChatMessageBuilder() .append(ChatColorType.HIGHLIGHT) - .append(removed.getName()) + .append(removed.getMember().getName()) .append(" has left the party!") .build(); @@ -435,6 +527,8 @@ public class PartyPlugin extends Plugin } worldMapManager.remove(removed.getWorldMapPoint()); + + SwingUtilities.invokeLater(() -> panel.removeMember(event.getMemberId())); } } @@ -445,6 +539,12 @@ public class PartyPlugin extends Plugin partyDataMap.clear(); pendingTilePings.clear(); worldMapManager.removeIf(PartyWorldMapPoint.class::isInstance); + + SwingUtilities.invokeLater(() -> + { + panel.removeAllMembers(); + panel.removeAllRequests(); + }); } @Subscribe @@ -464,6 +564,12 @@ public class PartyPlugin extends Plugin } } + @Subscribe + public void onPartyMemberAvatar(PartyMemberAvatar event) + { + SwingUtilities.invokeLater(() -> panel.updateMember(event.getMemberId())); + } + @Nullable PartyData getPartyData(final UUID uuid) { @@ -479,18 +585,32 @@ public class PartyPlugin extends Plugin return partyDataMap.computeIfAbsent(uuid, (u) -> { - final String name = memberById.getName(); final WorldMapPoint worldMapPoint = new PartyWorldMapPoint(new WorldPoint(0, 0, 0), memberById); - worldMapPoint.setTooltip(name); + worldMapPoint.setTooltip(memberById.getName()); // When first joining a party, other members can join before getting a join for self PartyMember partyMember = party.getLocalMember(); - if (partyMember == null || !u.equals(partyMember.getMemberId())) + + boolean isSelf = partyMember != null && u.equals(partyMember.getMemberId()); + + if (!isSelf) { worldMapManager.add(worldMapPoint); } - return new PartyData(u, name, worldMapPoint, ColorUtil.fromObject(name)); + PartyData partyData = new PartyData(memberById, worldMapPoint, ColorUtil.fromObject(memberById.getName())); + partyData.setShowOverlay(config.autoOverlay()); + + if (config.includeSelf() || !isSelf) + { + SwingUtilities.invokeLater(() -> panel.addMember(partyData)); + } + else + { + SwingUtilities.invokeLater(panel::updateParty); + } + + return partyData; }); } @@ -498,7 +618,7 @@ public class PartyPlugin extends Plugin { final String helpMessage = new ChatMessageBuilder() .append(ChatColorType.HIGHLIGHT) - .append("To leave party hold SHIFT and right click party stats overlay.") + .append("To leave the party, click \"Leave party\" on the party panel.") .build(); chatMessageManager.queue(QueuedMessage.builder() diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyRequestBox.java b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyRequestBox.java new file mode 100644 index 0000000000..f36b262285 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyRequestBox.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2021, Jonathan Rousseau + * 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.party; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.GridLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.DynamicGridLayout; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.components.MouseDragEventForwarder; +import net.runelite.client.ui.components.shadowlabel.JShadowedLabel; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.SwingUtil; +import net.runelite.discord.DiscordRPC; + +class PartyRequestBox extends JPanel +{ + private static final ImageIcon CONFIRM_ICON = new ImageIcon(ImageUtil.loadImageResource(PartyPlugin.class, "confirm_icon.png")); + private static final ImageIcon CONFIRM_HOVER_ICON = new ImageIcon(ImageUtil.alphaOffset(ImageUtil.bufferedImageFromImage(CONFIRM_ICON.getImage()), 0.54f)); + private static final ImageIcon CANCEL_ICON = new ImageIcon(ImageUtil.loadImageResource(PartyPlugin.class, "cancel_icon.png")); + private static final ImageIcon CANCEL_HOVER_ICON = new ImageIcon(ImageUtil.alphaOffset(ImageUtil.bufferedImageFromImage(CANCEL_ICON.getImage()), 0.6f)); + + PartyRequestBox(final PartyPlugin plugin, final JComponent panel, String userId, String userName) + { + setLayout(new BorderLayout()); + setBorder(new EmptyBorder(5, 0, 0, 0)); + + /* The box's wrapping container */ + final JPanel container = new JPanel(); + container.setLayout(new BorderLayout()); + container.setBackground(ColorScheme.DARKER_GRAY_COLOR); + container.setBorder(new EmptyBorder(5, 5, 5, 5)); + + JPanel namesPanel = new JPanel(); + namesPanel.setLayout(new DynamicGridLayout(2, 1)); + namesPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + namesPanel.setBorder(new EmptyBorder(2, 5, 2, 5)); + + JShadowedLabel nameLabel = new JShadowedLabel(); + nameLabel.setFont(FontManager.getRunescapeSmallFont()); + nameLabel.setForeground(Color.WHITE); + nameLabel.setText(userName); + + JShadowedLabel messageLabel = new JShadowedLabel(); + messageLabel.setFont(FontManager.getRunescapeSmallFont()); + messageLabel.setForeground(Color.WHITE); + messageLabel.setText("Wants to join your party!"); + + namesPanel.add(nameLabel); + namesPanel.add(messageLabel); + + JPanel actionsContainer = new JPanel(new GridLayout(1, 2, 8, 0)); + actionsContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + JButton confirmButton = new JButton(CONFIRM_ICON); + SwingUtil.removeButtonDecorations(confirmButton); + confirmButton.setToolTipText("Invite"); + confirmButton.setRolloverIcon(CONFIRM_HOVER_ICON); + confirmButton.addActionListener(e -> plugin.replyToRequest(userId, DiscordRPC.DISCORD_REPLY_YES)); + confirmButton.setPreferredSize(new Dimension(18, 18)); + + JButton cancelButton = new JButton(CANCEL_ICON); + SwingUtil.removeButtonDecorations(cancelButton); + cancelButton.setToolTipText("Reject"); + cancelButton.setRolloverIcon(CANCEL_HOVER_ICON); + cancelButton.addActionListener(e -> plugin.replyToRequest(userId, DiscordRPC.DISCORD_REPLY_NO)); + cancelButton.setPreferredSize(new Dimension(18, 18)); + + actionsContainer.add(confirmButton); + actionsContainer.add(cancelButton); + + container.add(namesPanel, BorderLayout.WEST); + container.add(actionsContainer, BorderLayout.EAST); + + // forward mouse drag events to parent panel for drag and drop reordering + MouseDragEventForwarder mouseDragEventForwarder = new MouseDragEventForwarder(panel); + container.addMouseListener(mouseDragEventForwarder); + container.addMouseMotionListener(mouseDragEventForwarder); + + add(container, BorderLayout.NORTH); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyStatsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyStatsOverlay.java index 5635646339..18a1ea41d9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyStatsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyStatsOverlay.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2019, Tomas Slusny + * Copyright (c) 2021, Jonathan Rousseau * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,9 +33,7 @@ import java.awt.Rectangle; import java.util.Map; import java.util.UUID; import javax.inject.Inject; -import net.runelite.api.MenuAction; import net.runelite.client.plugins.party.data.PartyData; -import net.runelite.client.ui.overlay.OverlayMenuEntry; import net.runelite.client.ui.overlay.OverlayPanel; import net.runelite.client.ui.overlay.components.ComponentConstants; import net.runelite.client.ui.overlay.components.PanelComponent; @@ -62,17 +61,11 @@ public class PartyStatsOverlay extends OverlayPanel this.config = config; panelComponent.setBorder(new Rectangle()); panelComponent.setGap(new Point(0, ComponentConstants.STANDARD_BORDER / 2)); - getMenuEntries().add(new OverlayMenuEntry(MenuAction.RUNELITE_OVERLAY, "Leave", "Party")); } @Override public Dimension render(Graphics2D graphics) { - if (!config.stats()) - { - return null; - } - final Map partyDataMap = plugin.getPartyDataMap(); if (partyDataMap.isEmpty()) { @@ -81,22 +74,14 @@ public class PartyStatsOverlay extends OverlayPanel panelComponent.setBackgroundColor(null); - boolean only1 = plugin.getPartyDataMap().size() == 1; - synchronized (plugin.getPartyDataMap()) { partyDataMap.forEach((k, v) -> { - if (party.getLocalMember() != null && party.getLocalMember().getMemberId().equals(k)) - { - if (only1) - { - panelComponent.getChildren().add(TitleComponent.builder() - .text("No other party members") - .color(Color.RED) - .build()); - } + boolean isSelf = party.getLocalMember() != null && party.getLocalMember().getMemberId().equals(k); + if (!v.isShowOverlay() || (!config.includeSelf() && isSelf)) + { return; } @@ -104,7 +89,7 @@ public class PartyStatsOverlay extends OverlayPanel panel.getChildren().clear(); final TitleComponent name = TitleComponent.builder() - .text(v.getName()) + .text(v.getCharacterName().isEmpty() ? v.getMember().getName() : v.getCharacterName()) .color(config.recolorNames() ? v.getColor() : Color.WHITE) .build(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/party/data/PartyData.java b/runelite-client/src/main/java/net/runelite/client/plugins/party/data/PartyData.java index 2593c4fe8f..f61a3b8866 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/party/data/PartyData.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/party/data/PartyData.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2019, Tomas Slusny + * Copyright (c) 2021, Jonathan Rousseau * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,20 +26,19 @@ package net.runelite.client.plugins.party.data; import java.awt.Color; -import java.util.UUID; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; import net.runelite.client.ui.overlay.components.PanelComponent; import net.runelite.client.ui.overlay.worldmap.WorldMapPoint; +import net.runelite.client.ws.PartyMember; @Setter @Getter @RequiredArgsConstructor public class PartyData { - private final UUID memberId; - private final String name; + private final PartyMember member; private final WorldMapPoint worldMapPoint; private final PanelComponent panel = new PanelComponent(); private final Color color; @@ -47,4 +47,6 @@ public class PartyData private int maxHitpoints; private int prayer; private int maxPrayer; + private String characterName = ""; + private boolean showOverlay; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/party/messages/CharacterNameUpdate.java b/runelite-client/src/main/java/net/runelite/client/plugins/party/messages/CharacterNameUpdate.java new file mode 100644 index 0000000000..aff6c058c9 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/party/messages/CharacterNameUpdate.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021, Jonathan Rousseau + * 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.party.messages; + +import lombok.Data; +import net.runelite.http.api.ws.messages.party.PartyMemberMessage; + +@Data +public class CharacterNameUpdate extends PartyMemberMessage +{ + private final String characterName; +} diff --git a/runelite-client/src/main/java/net/runelite/client/ws/PartyService.java b/runelite-client/src/main/java/net/runelite/client/ws/PartyService.java index 4c70a624e6..dc00df2e4d 100644 --- a/runelite-client/src/main/java/net/runelite/client/ws/PartyService.java +++ b/runelite-client/src/main/java/net/runelite/client/ws/PartyService.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, Adam + * Copyright (c) 2021, Jonathan Rousseau * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,10 +25,14 @@ */ package net.runelite.client.ws; +import com.google.common.base.Charsets; +import com.google.common.hash.Hashing; +import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; +import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; import lombok.Getter; @@ -42,6 +47,7 @@ import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.PartyChanged; import net.runelite.client.util.Text; +import net.runelite.client.events.PartyMemberAvatar; import static net.runelite.client.util.Text.JAGEX_PRINTABLE_CHAR_MATCHER; import net.runelite.http.api.ws.messages.party.Join; import net.runelite.http.api.ws.messages.party.Part; @@ -68,7 +74,10 @@ public class PartyService private UUID localPartyId = UUID.randomUUID(); @Getter - private UUID partyId; + private UUID publicPartyId; // public party id, for advertising on discord, derived from the secret + + @Getter + private UUID partyId; // secret party id @Setter private String username; @@ -83,8 +92,14 @@ public class PartyService eventBus.register(this); } - public void changeParty(UUID newParty) + public void changeParty(@Nullable UUID newParty) { + if (username == null) + { + log.warn("Tried to join a party with no username"); + return; + } + if (wsClient.sessionExists()) { wsClient.send(new Part()); @@ -93,6 +108,8 @@ public class PartyService log.debug("Party change to {}", newParty); members.clear(); partyId = newParty; + // The public party ID needs to be consistent across party members, but not a secret + publicPartyId = newParty != null ? UUID.nameUUIDFromBytes(Hashing.sha256().hashString(newParty.toString(), Charsets.UTF_8).asBytes()) : null; if (partyId == null) { @@ -145,7 +162,7 @@ public class PartyService } } - @Subscribe + @Subscribe(priority = 1) // run prior to plugins so that the member is removed by the time the plugins see it. public void onUserPart(final UserPart message) { members.removeIf(member -> member.getMemberId().equals(message.getMemberId())); @@ -218,6 +235,17 @@ public class PartyService return localPartyId.equals(partyId); } + public void setPartyMemberAvatar(UUID memberID, BufferedImage image) + { + final PartyMember memberById = getMemberById(memberID); + + if (memberById != null) + { + memberById.setAvatar(image); + eventBus.post(new PartyMemberAvatar(memberID, image)); + } + } + private static String cleanUsername(String username) { String s = Text.removeTags(JAGEX_PRINTABLE_CHAR_MATCHER.retainFrom(username)); diff --git a/runelite-client/src/main/java/net/runelite/client/ws/WSClient.java b/runelite-client/src/main/java/net/runelite/client/ws/WSClient.java index 21e7cbafc3..88fc16abed 100644 --- a/runelite-client/src/main/java/net/runelite/client/ws/WSClient.java +++ b/runelite-client/src/main/java/net/runelite/client/ws/WSClient.java @@ -191,7 +191,7 @@ public class WSClient extends WebSocketListener implements AutoCloseable @Override public void onFailure(WebSocket webSocket, Throwable t, Response response) { - log.warn("Error in websocket {}:{}", response, t); + log.warn("Error in websocket: {}", response, t); this.webSocket = null; } } diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/party/cancel_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/party/cancel_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3f4915d0419444a5b2307d16643b4a5e36cf62f9 GIT binary patch literal 299 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa&H|6fVg?3oVGw3ym^DWND0tV? z#W6%<;@pddUPl51*dFM==Q$)Mz3qk*cfNp9({zDtQ70Daq-+${ey`AW=b+m()o2d6 zgNM?>l0SJo)P8=}oaevD?oBqwW%Fk5`)~5OEVZ&NJxBOmVRKpdv~I~Q4&B-|DmeuQ zSa-?vMTy(8=LB8;?jBNc-0DM7!?eb07lTb}>vfERuN!XdTF#g4uk{=e4e6hER(3JY@|k;E?cvG% uC&Da&s~4rW9&b@wcGc44kZIh;zidH`nZ;AQ*G>R>l)=;0&t;ucLK6UgICIPZ literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/party/confirm_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/party/confirm_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0a60af087207955948e2325d125a60673398e0eb GIT binary patch literal 273 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|&H|6fVg?3oVGw3ym^DWND0tY@ z#WBR9_wEHlFJ?y(wg>r>ACx|0`IhMxDZp|$a`_c&j*XkI_;qWjG{5uwX0dEjrBl!B z9fni?-K{=cV^jApw#f5%fNV$1BS!OP<_Vq(qG9`YX-qiokT-kR-h0xooNjDh9CJxB z&P?#d-9UxUdMvAAf?CyW%u9c7W3@aebXf1n=S9Ua34-#wCLGmcVe`FvuCPBa;jw9o z%!v%~Ls2((F0uC6cqnsCeBl@2Ppr2N+8gt(-+k zE-HwqU7u=3iK1j!GD>awwdg!}&D@VSV)cW|d-tCIIrq$c=f1fw2k3uN7Zdz#@&Q{x z(nU!f)_*IhSJHgIN0OQ)^-F5C{xkD)1Gri<@SbX>fXyBs0qww?!|xTa&gJ6^H0%hH z@Z95506ql#Zf8wkdmuOb&RE1@Dy*oV=PVCHMo>rutrkOF&-*t zSy5NvN?=6N565`Iw4`r#-(*dah66sbm8-^6P~D#AxCk@=yMZ^rytTW)N*njlnY9R$ zb_jR@Oaqg^D6kRO<(Xj0{Eh%4)}H~M0Q-FNxCV>B5O6M4?S<4e#I2NWh21v-=Ya=6 zM=XI06#?G?CEyXz3heYuoG=8`0r%`(QUb;+G-rJYT=E1>=mc6KnmZP2F{10OOyESs z0b9Pdh~~b<8;$4y4qz{$dP&!uTjWoUNg9i2ChXb}(Mi&P^m`kgiKUvN9oXx!%>a4e zw8!q^xPZqom2m}FWlP*;g*k5RvK6U-9kva#FeqR)rUH&y@E-e9=>xV{YqLp9w)cys z9f$=5CIpltqHYUVxl&(KD^N*~`CN6_(##832nbGDps|3)N1yq=3~0)s6?kd^ZhGul zC<09Z`<(2#xo82ldTd#lwI6ZaV}G5ObkGixX}(MPB&p=w!P!|a>9C}el5RNclR5m~ z1sf!N_8DtnP*RuA7&;N{57=s9TgVtnU{k Date: Sun, 16 May 2021 15:19:49 -0400 Subject: [PATCH 14/15] party: only send location update when the location changes --- .../runelite/client/plugins/party/PartyPlugin.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java index 5aca5fdcbf..90e5df9591 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java @@ -153,6 +153,7 @@ public class PartyPlugin extends Plugin private int lastHp, lastPray; private String lastCharacterName = ""; + private WorldPoint lastLocation; private boolean sendAlert; @Override @@ -204,6 +205,7 @@ public class PartyPlugin extends Plugin wsClient.unregisterMessage(LocationUpdate.class); wsClient.unregisterMessage(CharacterNameUpdate.class); sendAlert = false; + lastLocation = null; } @Provides @@ -348,7 +350,15 @@ public class PartyPlugin extends Plugin return; } - final LocationUpdate locationUpdate = new LocationUpdate(client.getLocalPlayer().getWorldLocation()); + WorldPoint location = client.getLocalPlayer().getWorldLocation(); + if (location.equals(lastLocation)) + { + return; + } + + lastLocation = location; + + final LocationUpdate locationUpdate = new LocationUpdate(location); locationUpdate.setMemberId(localMember.getMemberId()); wsClient.send(locationUpdate); } @@ -463,6 +473,7 @@ public class PartyPlugin extends Plugin public void onUserSync(final UserSync event) { checkStateChanged(true); + lastLocation = null; } private void checkStateChanged(boolean forceSend) From ab2454ca75670f9df63bc117f59c5126ca02db4f Mon Sep 17 00:00:00 2001 From: "JOROUSS-2070\\JoRouss" Date: Thu, 20 May 2021 18:14:51 -0400 Subject: [PATCH 15/15] party: readd overlay leave party menu entry --- .../client/plugins/party/PartyPanel.java | 2 +- .../client/plugins/party/PartyPlugin.java | 33 +++++++++++++++++++ .../plugins/party/PartyStatsOverlay.java | 3 ++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPanel.java index 98cecb81bf..3c3c1bba95 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPanel.java @@ -105,7 +105,7 @@ class PartyPanel extends PluginPanel if (result == JOptionPane.YES_OPTION) { - party.changeParty(null); + plugin.leaveParty(); } } else diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java index 90e5df9591..789c0740e1 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java @@ -46,6 +46,7 @@ import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.KeyCode; +import net.runelite.api.MenuAction; import net.runelite.api.MenuEntry; import net.runelite.api.Player; import net.runelite.api.Skill; @@ -65,6 +66,7 @@ import net.runelite.client.config.ConfigManager; import net.runelite.client.discord.DiscordService; import net.runelite.client.discord.events.DiscordJoinRequest; import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.OverlayMenuClicked; import net.runelite.client.events.ConfigChanged; import net.runelite.client.events.PartyChanged; import net.runelite.client.events.PartyMemberAvatar; @@ -214,6 +216,37 @@ public class PartyPlugin extends Plugin return configManager.getConfig(PartyConfig.class); } + @Subscribe + public void onOverlayMenuClicked(OverlayMenuClicked event) + { + if (event.getEntry().getMenuAction() == MenuAction.RUNELITE_OVERLAY && + event.getEntry().getTarget().equals("Party") && + event.getEntry().getOption().equals("Leave")) + { + leaveParty(); + } + } + + void leaveParty() + { + party.changeParty(null); + + if (!config.messages()) + { + return; + } + + final String leaveMessage = new ChatMessageBuilder() + .append(ChatColorType.HIGHLIGHT) + .append("You have left the party.") + .build(); + + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.FRIENDSCHATNOTIFICATION) + .runeLiteFormattedMessage(leaveMessage) + .build()); + } + @Subscribe public void onConfigChanged(ConfigChanged event) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyStatsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyStatsOverlay.java index 18a1ea41d9..e103649d35 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyStatsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyStatsOverlay.java @@ -33,7 +33,9 @@ import java.awt.Rectangle; import java.util.Map; import java.util.UUID; import javax.inject.Inject; +import net.runelite.api.MenuAction; import net.runelite.client.plugins.party.data.PartyData; +import net.runelite.client.ui.overlay.OverlayMenuEntry; import net.runelite.client.ui.overlay.OverlayPanel; import net.runelite.client.ui.overlay.components.ComponentConstants; import net.runelite.client.ui.overlay.components.PanelComponent; @@ -61,6 +63,7 @@ public class PartyStatsOverlay extends OverlayPanel this.config = config; panelComponent.setBorder(new Rectangle()); panelComponent.setGap(new Point(0, ComponentConstants.STANDARD_BORDER / 2)); + getMenuEntries().add(new OverlayMenuEntry(MenuAction.RUNELITE_OVERLAY, "Leave", "Party")); } @Override