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 095af54819..eb356d2069 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -522,6 +522,11 @@ public interface Client extends GameEngine */ void setDraggedOnWidget(Widget widget); + /** + * Gets Interface ID of the root widget + */ + int getTopLevelInterfaceId(); + /** * Gets the root widgets. * 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 67ccfc56ae..2d005fd358 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; 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/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 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..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 @@ -25,6 +26,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; @@ -49,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; @@ -214,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) { @@ -246,18 +233,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); @@ -290,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/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(); 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() 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..3c3c1bba95 --- /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) + { + plugin.leaveParty(); + } + } + 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..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 @@ -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,6 +40,7 @@ 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; @@ -44,11 +48,13 @@ 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 +63,30 @@ 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 +96,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 +134,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 +150,12 @@ 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 WorldPoint lastLocation; private boolean sendAlert; @Override @@ -141,17 +167,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,8 +205,9 @@ 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; + lastLocation = null; } @Provides @@ -177,22 +223,53 @@ public class PartyPlugin extends Plugin event.getEntry().getTarget().equals("Party") && event.getEntry().getOption().equals("Leave")) { - party.changeParty(null); + leaveParty(); + } + } - if (!config.messages()) + 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) + { + if (event.getGroup().equals(PartyConfig.GROUP)) + { + final PartyMember localMember = party.getLocalMember(); + + 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 +313,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) { @@ -277,7 +383,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); } @@ -291,41 +405,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 +456,8 @@ public class PartyPlugin extends Plugin partyData.setPrayer(event.getValue()); partyData.setMaxPrayer(event.getMax()); } + + SwingUtilities.invokeLater(() -> panel.updateMember(partyData.getMember().getMemberId())); } @Subscribe @@ -375,7 +485,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 +496,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 +504,12 @@ public class PartyPlugin extends Plugin @Subscribe public void onUserSync(final UserSync event) + { + checkStateChanged(true); + lastLocation = null; + } + + private void checkStateChanged(boolean forceSend) { final int currentHealth = client.getBoostedSkillLevel(Skill.HITPOINTS); final int currentPrayer = client.getBoostedSkillLevel(Skill.PRAYER); @@ -401,16 +517,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 +560,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 +571,8 @@ public class PartyPlugin extends Plugin } worldMapManager.remove(removed.getWorldMapPoint()); + + SwingUtilities.invokeLater(() -> panel.removeMember(event.getMemberId())); } } @@ -445,6 +583,12 @@ public class PartyPlugin extends Plugin partyDataMap.clear(); pendingTilePings.clear(); worldMapManager.removeIf(PartyWorldMapPoint.class::isInstance); + + SwingUtilities.invokeLater(() -> + { + panel.removeAllMembers(); + panel.removeAllRequests(); + }); } @Subscribe @@ -464,6 +608,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 +629,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 +662,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..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 @@ -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 @@ -68,11 +69,6 @@ public class PartyStatsOverlay extends OverlayPanel @Override public Dimension render(Graphics2D graphics) { - if (!config.stats()) - { - return null; - } - final Map partyDataMap = plugin.getPartyDataMap(); if (partyDataMap.isEmpty()) { @@ -81,22 +77,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 +92,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/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; 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); } 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(); + } } 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..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; @@ -41,6 +46,8 @@ 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 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; @@ -55,6 +62,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; @@ -66,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; @@ -81,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()); @@ -91,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) { @@ -129,7 +148,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(); @@ -143,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())); @@ -215,4 +234,25 @@ 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)); + if (s.length() >= MAX_USERNAME_LEN) + { + s = s.substring(0, MAX_USERNAME_LEN); + } + return s; + } } 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/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 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 0000000000..3f4915d041 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/party/cancel_icon.png differ 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 0000000000..0a60af0872 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/party/confirm_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/party/panel_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/party/panel_icon.png new file mode 100644 index 0000000000..10a86ecd80 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/party/panel_icon.png differ 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 diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/discord/DiscordStateTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/discord/DiscordStateTest.java index 815fff0639..dae0188f42 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/discord/DiscordStateTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/discord/DiscordStateTest.java @@ -27,9 +27,7 @@ package net.runelite.client.plugins.discord; import com.google.inject.Guice; import com.google.inject.testing.fieldbinder.Bind; import com.google.inject.testing.fieldbinder.BoundFieldModule; -import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.UUID; import javax.inject.Inject; import javax.inject.Named; import net.runelite.api.Client; @@ -83,7 +81,6 @@ public class DiscordStateTest public void before() { Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); - when(partyService.getLocalPartyId()).thenReturn(UUID.nameUUIDFromBytes("test".getBytes(StandardCharsets.UTF_8))); } @Test diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java index b9b603182e..cc063a4e42 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java @@ -600,7 +600,7 @@ public abstract class RSClientMixin implements RSClient @Override public Widget[] getWidgetRoots() { - int topGroup = getWidgetRoot(); + int topGroup = getTopLevelInterfaceId(); if (topGroup == -1) { return new Widget[]{}; diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java index 18452db23d..d98f1ba0a0 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java @@ -164,7 +164,7 @@ public abstract class RSWidgetMixin implements RSWidget } final int id = getId(); - if (TO_GROUP(id) == client.getWidgetRoot()) + if (TO_GROUP(id) == client.getTopLevelInterfaceId()) { // this is a root widget return -1; @@ -261,7 +261,7 @@ public abstract class RSWidgetMixin implements RSWidget // If the parent is hidden, this widget is also hidden. // Widget has no parent and is not the root widget (which is always visible), // so it's not visible. - return parent == null ? TO_GROUP(getId()) != client.getWidgetRoot() : parent.isHidden(); + return parent == null ? TO_GROUP(getId()) != client.getTopLevelInterfaceId() : parent.isHidden(); } @Inject diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java b/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java index c992630680..d399da781c 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java @@ -547,7 +547,7 @@ public interface RSClient extends RSGameEngine, Client * parentId -1, which are the widget roots. */ @Import("rootInterface") - int getWidgetRoot(); + int getTopLevelInterfaceId(); @Import("WorldMapElement_cached") @Override