diff --git a/runelite-api/src/main/java/net/runelite/api/ScriptID.java b/runelite-api/src/main/java/net/runelite/api/ScriptID.java index b6929bb2d6..565b86be44 100644 --- a/runelite-api/src/main/java/net/runelite/api/ScriptID.java +++ b/runelite-api/src/main/java/net/runelite/api/ScriptID.java @@ -395,4 +395,16 @@ public final class ScriptID */ @ScriptArguments(integer = 14, string = 3) public static final int CHATBOX_BUILD_LINE_WITH_CLAN = 4483; + + /** + * Drag callback for the camera zoom slider in the options side panel. + */ + @ScriptArguments(integer = 3) + public static final int ZOOM_SLIDER_ONDRAG = 833; + + /** + * Drag callback for the camera zoom slider in the settings. + */ + @ScriptArguments(integer = 6) + public static final int SETTINGS_ZOOM_SLIDER_ONDRAG = 3896; } \ No newline at end of file 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 2d005fd358..c84974df35 100644 --- a/runelite-api/src/main/java/net/runelite/api/SpriteID.java +++ b/runelite-api/src/main/java/net/runelite/api/SpriteID.java @@ -1584,6 +1584,7 @@ public final class SpriteID public static final int HEALTHBAR_DEFAULT_BACK_140PX = 2189; public static final int HEALTHBAR_DEFAULT_FRONT_160PX = 2190; public static final int HEALTHBAR_DEFAULT_BACK_160PX = 2191; + public static final int TAB_CLAN_CHAT = 2307; public static final int WIKI_DESELECTED = 2420; public static final int WIKI_SELECTED = 2421; public static final int FRIENDS_CHAT_RANK_SMILEY_FRIEND = 2825; diff --git a/runelite-api/src/main/java/net/runelite/api/events/ClanChannelChanged.java b/runelite-api/src/main/java/net/runelite/api/events/ClanChannelChanged.java new file mode 100644 index 0000000000..57abd91dbb --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/ClanChannelChanged.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.api.events; + +import javax.annotation.Nullable; +import lombok.Value; +import net.runelite.api.clan.ClanChannel; + +/** + * An event fired when the local player joins or leaves a clan channel. + */ +@Value +public class ClanChannelChanged +{ + /** + * The clan channel + */ + @Nullable + private final ClanChannel clanChannel; + /** + * Whether or not this was the guest clan channel + */ + private boolean guest; +} diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index 594e413f2e..f7b2bc9fec 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -85,6 +85,7 @@ import net.runelite.http.api.worlds.World; import net.runelite.http.api.worlds.WorldResult; import okhttp3.Cache; import okhttp3.OkHttpClient; +import okhttp3.Response; import org.slf4j.LoggerFactory; @Singleton @@ -261,8 +262,8 @@ public class RuneLite OpenOSRS.preload(); - OkHttpClient.Builder okHttpClientBuilder = RuneLiteAPI.CLIENT.newBuilder() - .cache(new Cache(new File(CACHE_DIR, "okhttp"), MAX_OKHTTP_CACHE_SIZE)); + OkHttpClient.Builder okHttpClientBuilder = RuneLiteAPI.CLIENT.newBuilder(); + setupCache(okHttpClientBuilder, new File(CACHE_DIR, "okhttp")); final boolean insecureSkipTlsVerification = options.has("insecure-skip-tls-verification"); if (insecureSkipTlsVerification || RuneLiteProperties.isInsecureSkipTlsVerification()) @@ -496,6 +497,25 @@ public class RuneLite } } + @VisibleForTesting + static void setupCache(OkHttpClient.Builder builder, File cacheDir) + { + builder.cache(new Cache(cacheDir, MAX_OKHTTP_CACHE_SIZE)) + .addNetworkInterceptor(chain -> + { + // This has to be a network interceptor so it gets hit before the cache tries to store stuff + Response res = chain.proceed(chain.request()); + if (res.code() >= 400 && "GET".equals(res.request().method())) + { + // if the request 404'd we don't want to cache it because its probably temporary + res = res.newBuilder() + .header("Cache-Control", "no-store") + .build(); + } + return res; + }); + } + private static void setupInsecureTrustManager(OkHttpClient.Builder okHttpClientBuilder) { try diff --git a/runelite-client/src/main/java/net/runelite/client/game/SpriteManager.java b/runelite-client/src/main/java/net/runelite/client/game/SpriteManager.java index 7c0bd4f2fc..5920682dd7 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/SpriteManager.java +++ b/runelite-client/src/main/java/net/runelite/client/game/SpriteManager.java @@ -26,12 +26,12 @@ package net.runelite.client.game; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import com.google.inject.Inject; import java.awt.image.BufferedImage; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import javax.annotation.Nullable; +import javax.inject.Inject; import javax.inject.Singleton; import javax.swing.ImageIcon; import javax.swing.JButton; @@ -48,20 +48,23 @@ import net.runelite.client.util.ImageUtil; @Singleton public class SpriteManager { - @Inject - private Client client; + private final Client client; + private final ClientThread clientThread; + private final InfoBoxManager infoBoxManager; - @Inject - private ClientThread clientThread; - - @Inject - private InfoBoxManager infoBoxManager; - - public Cache cache = CacheBuilder.newBuilder() + private final Cache cache = CacheBuilder.newBuilder() .maximumSize(128L) .expireAfterAccess(1, TimeUnit.HOURS) .build(); + @Inject + private SpriteManager(Client client, ClientThread clientThread, InfoBoxManager infoBoxManager) + { + this.client = client; + this.clientThread = clientThread; + this.infoBoxManager = infoBoxManager; + } + @Nullable public BufferedImage getSprite(int archive, int file) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraPlugin.java index 904420080a..f650d3efba 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraPlugin.java @@ -367,15 +367,23 @@ public class CameraPlugin extends Plugin implements KeyListener, MouseListener @Subscribe private void onScriptPreFired(ScriptPreFired ev) { - if (ev.getScriptId() == ScriptID.SETTINGS_SLIDER_CHOOSE_ONOP) + switch (ev.getScriptId()) { - int arg = client.getIntStackSize() - 7; - int[] is = client.getIntStack(); - - if (is[arg] == SettingID.CAMERA_ZOOM) + case ScriptID.SETTINGS_SLIDER_CHOOSE_ONOP: { - addZoomTooltip(client.getScriptActiveWidget()); + int arg = client.getIntStackSize() - 7; + int[] is = client.getIntStack(); + + if (is[arg] == SettingID.CAMERA_ZOOM) + { + addZoomTooltip(client.getScriptActiveWidget()); + } + break; } + case ScriptID.ZOOM_SLIDER_ONDRAG: + case ScriptID.SETTINGS_ZOOM_SLIDER_ONDRAG: + sliderTooltip = makeSliderTooltip(); + break; } } @@ -390,12 +398,14 @@ public class CameraPlugin extends Plugin implements KeyListener, MouseListener private void addZoomTooltip(Widget w) { - w.setOnMouseRepeatListener((JavaScriptCallback) ev -> - { - int value = client.getVar(VarClientInt.CAMERA_ZOOM_RESIZABLE_VIEWPORT); - int max = config.innerLimit() ? config.INNER_ZOOM_LIMIT : CameraPlugin.DEFAULT_INNER_ZOOM_LIMIT; - sliderTooltip = new Tooltip("Camera Zoom: " + value + " / " + max); - }); + w.setOnMouseRepeatListener((JavaScriptCallback) ev -> sliderTooltip = makeSliderTooltip()); + } + + private Tooltip makeSliderTooltip() + { + int value = client.getVar(VarClientInt.CAMERA_ZOOM_RESIZABLE_VIEWPORT); + int max = config.innerLimit() ? config.INNER_ZOOM_LIMIT : CameraPlugin.DEFAULT_INNER_ZOOM_LIMIT; + return new Tooltip("Camera Zoom: " + value + " / " + max); } @Subscribe diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatchannel/ChatChannelConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatchannel/ChatChannelConfig.java index d8b4fede63..eeffc30f4d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatchannel/ChatChannelConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatchannel/ChatChannelConfig.java @@ -92,18 +92,6 @@ public interface ChatChannelConfig extends Config return true; } - @ConfigItem( - keyName = "clanCounter", - name = "Members Counter", - description = "Show the amount of friends chat members near you.", - position = 3, - section = friendsChatSection - ) - default boolean showCounter() - { - return false; - } - @ConfigItem( keyName = "chatsData", name = "", @@ -129,7 +117,7 @@ public interface ChatChannelConfig extends Config position = 4, section = friendsChatSection ) - default boolean showJoinLeave() + default boolean showFriendsChatJoinLeave() { return false; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatchannel/ChatChannelPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatchannel/ChatChannelPlugin.java index ed9613da66..b77c6745cd 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatchannel/ChatChannelPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatchannel/ChatChannelPlugin.java @@ -32,7 +32,6 @@ import com.google.common.collect.Lists; import com.google.common.util.concurrent.Runnables; import com.google.inject.Provides; import java.awt.Color; -import java.awt.image.BufferedImage; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; @@ -53,9 +52,7 @@ import net.runelite.api.GameState; import net.runelite.api.Ignore; import net.runelite.api.MessageNode; import net.runelite.api.NameableContainer; -import net.runelite.api.Player; import net.runelite.api.ScriptID; -import net.runelite.api.SpriteID; import net.runelite.api.VarClientStr; import net.runelite.api.Varbits; import net.runelite.api.clan.ClanChannel; @@ -71,8 +68,6 @@ import net.runelite.api.events.FriendsChatMemberJoined; import net.runelite.api.events.FriendsChatMemberLeft; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.GameTick; -import net.runelite.api.events.PlayerDespawned; -import net.runelite.api.events.PlayerSpawned; import net.runelite.api.events.ScriptCallbackEvent; import net.runelite.api.events.ScriptPostFired; import net.runelite.api.events.VarClientStrChanged; @@ -88,7 +83,6 @@ import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; import net.runelite.client.game.ChatIconManager; -import net.runelite.client.game.SpriteManager; import net.runelite.client.game.chatbox.ChatboxPanelManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; @@ -96,7 +90,6 @@ import static net.runelite.client.ui.JagexColors.CHAT_FC_NAME_OPAQUE_BACKGROUND; import static net.runelite.client.ui.JagexColors.CHAT_FC_NAME_TRANSPARENT_BACKGROUND; import static net.runelite.client.ui.JagexColors.CHAT_FC_TEXT_OPAQUE_BACKGROUND; import static net.runelite.client.ui.JagexColors.CHAT_FC_TEXT_TRANSPARENT_BACKGROUND; -import net.runelite.client.ui.overlay.infobox.InfoBoxManager; import net.runelite.client.util.Text; @PluginDescriptor( @@ -119,12 +112,6 @@ public class ChatChannelPlugin extends Plugin @Inject private ChatChannelConfig config; - @Inject - private InfoBoxManager infoBoxManager; - - @Inject - private SpriteManager spriteManager; - @Inject private ClientThread clientThread; @@ -138,8 +125,6 @@ public class ChatChannelPlugin extends Plugin private ChatMessageManager chatMessageManager; private List chats; - private final List members = new ArrayList<>(); - private MembersIndicator membersIndicator; /** * queue of temporary messages added to the client */ @@ -186,8 +171,6 @@ public class ChatChannelPlugin extends Plugin { chats = null; clientThread.invoke(() -> colorIgnoredPlayers(Color.WHITE)); - members.clear(); - resetCounter(); rebuildFriendsChat(); inputMode = null; } @@ -202,15 +185,6 @@ public class ChatChannelPlugin extends Plugin rebuildFriendsChat(); } - if (config.showCounter()) - { - clientThread.invoke(this::addCounter); - } - else - { - resetCounter(); - } - Color ignoreColor = config.showIgnores() ? config.showIgnoresColor() : Color.WHITE; clientThread.invoke(() -> colorIgnoredPlayers(ignoreColor)); } @@ -221,29 +195,13 @@ public class ChatChannelPlugin extends Plugin { final FriendsChatMember member = event.getMember(); - if (member.getWorld() == client.getWorld()) - { - final Player local = client.getLocalPlayer(); - final String memberName = Text.toJagexName(member.getName()); - - for (final Player player : client.getPlayers()) - { - if (player != null && player != local && memberName.equals(Text.toJagexName(player.getName()))) - { - members.add(player); - addCounter(); - break; - } - } - } - // members getting initialized isn't relevant if (joinedTick == client.getTickCount()) { return; } - if (!config.showJoinLeave() || + if (!config.showFriendsChatJoinLeave() || member.getRank().getValue() < config.joinLeaveRank().getValue()) { return; @@ -258,28 +216,7 @@ public class ChatChannelPlugin extends Plugin { final FriendsChatMember member = event.getMember(); - if (member.getWorld() == client.getWorld()) - { - final String memberName = Text.toJagexName(member.getName()); - final Iterator each = members.iterator(); - - while (each.hasNext()) - { - if (memberName.equals(Text.toJagexName(each.next().getName()))) - { - each.remove(); - - if (members.isEmpty()) - { - resetCounter(); - } - - break; - } - } - } - - if (!config.showJoinLeave() || + if (!config.showFriendsChatJoinLeave() || member.getRank().getValue() < config.joinLeaveRank().getValue()) { return; @@ -378,11 +315,6 @@ public class ChatChannelPlugin extends Plugin } } - if (!config.showJoinLeave()) - { - return; - } - timeoutMessages(); addActivityMessages(); @@ -625,35 +557,10 @@ public class ChatChannelPlugin extends Plugin if (gameState == GameState.LOGIN_SCREEN || gameState == GameState.CONNECTION_LOST || gameState == GameState.HOPPING) { - members.clear(); - resetCounter(); - joinMessages.clear(); } } - @Subscribe - public void onPlayerSpawned(PlayerSpawned event) - { - final Player local = client.getLocalPlayer(); - final Player player = event.getPlayer(); - - if (player != local && player.isFriendsChatMember()) - { - members.add(player); - addCounter(); - } - } - - @Subscribe - public void onPlayerDespawned(PlayerDespawned event) - { - if (members.remove(event.getPlayer()) && members.isEmpty()) - { - resetCounter(); - } - } - @Subscribe public void onFriendsChatChanged(FriendsChatChanged event) { @@ -661,11 +568,6 @@ public class ChatChannelPlugin extends Plugin { joinedTick = client.getTickCount(); } - else - { - members.clear(); - resetCounter(); - } activityBuffer.clear(); } @@ -779,14 +681,9 @@ public class ChatChannelPlugin extends Plugin } } - int getMembersSize() - { - return members.size(); - } - private void insertRankIcon(final ChatMessage message) { - final FriendsChatRank rank = getRank(message.getName()); + final FriendsChatRank rank = getRank(Text.removeTags(message.getName())); if (rank != null && rank != FriendsChatRank.UNRANKED) { @@ -879,24 +776,6 @@ public class ChatChannelPlugin extends Plugin config.chatsData(Text.toCSV(chats)); } - private void resetCounter() - { - infoBoxManager.removeInfoBox(membersIndicator); - membersIndicator = null; - } - - private void addCounter() - { - if (!config.showCounter() || membersIndicator != null || members.isEmpty()) - { - return; - } - - final BufferedImage image = spriteManager.getSprite(SpriteID.TAB_FRIENDS_CHAT, 0); - membersIndicator = new MembersIndicator(image, this); - infoBoxManager.addInfoBox(membersIndicator); - } - private void confirmKickPlayer(final String kickPlayerName) { chatboxPanelManager.openTextMenuInput("Attempting to kick: " + kickPlayerName) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterPlugin.java index 4cdcc62404..6baaeb79a5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterPlugin.java @@ -55,6 +55,7 @@ import net.runelite.api.MessageNode; import net.runelite.api.Player; import net.runelite.api.clan.ClanChannel; import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.OverheadTextChanged; import net.runelite.api.events.ScriptCallbackEvent; import net.runelite.client.config.ConfigManager; @@ -139,6 +140,19 @@ public class ChatFilterPlugin extends Plugin client.refreshChat(); } + @Subscribe + public void onGameStateChanged(GameStateChanged gameStateChanged) + { + switch (gameStateChanged.getGameState()) + { + // Login drops references to all messages and also resets the global message id counter. + // Invalidate the message id so it doesn't collide later when rebuilding the chatfilter. + case HOPPING: + case LOGGING_IN: + duplicateChatCache.values().forEach(d -> d.messageId = -1); + } + } + @Subscribe public void onScriptCallbackEvent(ScriptCallbackEvent event) { @@ -205,7 +219,10 @@ public class ChatFilterPlugin extends Plugin if (!blockMessage && shouldCollapse) { Duplicate duplicateCacheEntry = duplicateChatCache.get(name + ":" + message); - if (duplicateCacheEntry != null) + // If messageId is -1 then this is a replayed message, which we can't easily collapse since we don't know + // the most recent message. This is only for public chat since it is the only thing both replayed and also + // collapsed. Just allow uncollapsed playback. + if (duplicateCacheEntry != null && duplicateCacheEntry.messageId != -1) { blockMessage = duplicateCacheEntry.messageId != messageId || ((chatMessageType == PUBLICCHAT || chatMessageType == MODCHAT) && diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsService.java b/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsService.java index e39e4bd79d..381c808623 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsService.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsService.java @@ -38,6 +38,7 @@ import net.runelite.api.clan.ClanChannelMember; import net.runelite.api.clan.ClanRank; import net.runelite.api.clan.ClanSettings; import net.runelite.api.clan.ClanTitle; +import net.runelite.client.util.Text; @Singleton public class PlayerIndicatorsService @@ -130,7 +131,7 @@ public class PlayerIndicatorsService return FriendsChatRank.UNRANKED; } - FriendsChatMember friendsChatMember = friendsChatManager.findByName(player.getName()); + FriendsChatMember friendsChatMember = friendsChatManager.findByName(Text.removeTags(player.getName())); return friendsChatMember != null ? friendsChatMember.getRank() : FriendsChatRank.UNRANKED; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatchannel/MembersIndicator.java b/runelite-client/src/main/java/net/runelite/client/plugins/team/MembersIndicator.java similarity index 75% rename from runelite-client/src/main/java/net/runelite/client/plugins/chatchannel/MembersIndicator.java rename to runelite-client/src/main/java/net/runelite/client/plugins/team/MembersIndicator.java index a8d2b87775..2f7563716b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatchannel/MembersIndicator.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/team/MembersIndicator.java @@ -22,32 +22,18 @@ * (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.chatchannel; +package net.runelite.client.plugins.team; import java.awt.Color; import java.awt.image.BufferedImage; -import net.runelite.client.ui.overlay.infobox.Counter; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.ui.overlay.infobox.InfoBox; -class MembersIndicator extends Counter +abstract class MembersIndicator extends InfoBox { - private final ChatChannelPlugin plugin; - - MembersIndicator(BufferedImage image, ChatChannelPlugin plugin) + MembersIndicator(BufferedImage image, Plugin plugin) { - super(image, plugin, plugin.getMembersSize()); - this.plugin = plugin; - } - - @Override - public int getCount() - { - return plugin.getMembersSize(); - } - - @Override - public String getTooltip() - { - return plugin.getMembersSize() + " friends chat member(s) near you"; + super(image, plugin); } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/teamcapes/TeamCapesOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/team/TeamCapesOverlay.java similarity index 93% rename from runelite-client/src/main/java/net/runelite/client/plugins/teamcapes/TeamCapesOverlay.java rename to runelite-client/src/main/java/net/runelite/client/plugins/team/TeamCapesOverlay.java index 1d54c85935..d4f119c164 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/teamcapes/TeamCapesOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/team/TeamCapesOverlay.java @@ -22,7 +22,7 @@ * (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.teamcapes; +package net.runelite.client.plugins.team; import java.awt.Dimension; import java.awt.Graphics2D; @@ -41,12 +41,12 @@ import net.runelite.client.ui.overlay.components.ImageComponent; class TeamCapesOverlay extends OverlayPanel { - private final TeamCapesPlugin plugin; - private final TeamCapesConfig config; + private final TeamPlugin plugin; + private final TeamConfig config; private final ItemManager manager; @Inject - private TeamCapesOverlay(TeamCapesPlugin plugin, TeamCapesConfig config, ItemManager manager) + private TeamCapesOverlay(TeamPlugin plugin, TeamConfig config, ItemManager manager) { super(plugin); setPosition(OverlayPosition.TOP_LEFT); @@ -63,7 +63,7 @@ class TeamCapesOverlay extends OverlayPanel public Dimension render(Graphics2D graphics) { Map teams = plugin.getTeams(); - if (teams.isEmpty()) + if (teams.isEmpty() || !config.teamCapesOverlay()) { return null; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/team/TeamConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/team/TeamConfig.java new file mode 100644 index 0000000000..8f46f59101 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/team/TeamConfig.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2018, Mathieu Bernier + * 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.team; + +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(TeamConfig.GROUP) +public interface TeamConfig extends Config +{ + String GROUP = "teamCapes"; + + @ConfigSection( + name = "Team", + description = "Configuration for teams", + position = 10 + ) + String teamSection = "teamSection"; + + @ConfigSection( + name = "Friends Chat", + description = "Configuration for friends chat", + position = 20 + ) + String friendsChatSection = "friendsChatSection"; + + @ConfigSection( + name = "Clan Chat", + description = "Configuration for clan chat", + position = 30 + ) + String clanChatSection = "clanChatSection"; + + @ConfigItem( + keyName = "teamCapesOverlay", + name = "Team cape overlay", + description = "Configures whether to show the team cape overlay.", + position = 0, + section = teamSection + ) + default boolean teamCapesOverlay() + { + return false; + } + + @ConfigItem( + keyName = "minimumCapeCount", + name = "Minimum Cape Count", + description = "Configures the minimum number of team capes which must be present before being displayed.", + position = 1, + section = teamSection + ) + default int getMinimumCapeCount() + { + return 1; + } + + @ConfigItem( + keyName = "friendsChatMemberCounter", + name = "Friends Chat Members Counter", + description = "Show the amount of friends chat members near you.", + position = 0, + section = friendsChatSection + ) + default boolean friendsChatMemberCounter() + { + return false; + } + + @ConfigItem( + keyName = "clanChatMemberCounter", + name = "Clan Chat Members Counter", + description = "Show the amount of clan chat members near you.", + position = 0, + section = clanChatSection + ) + default boolean clanChatMemberCounter() + { + return false; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/team/TeamPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/team/TeamPlugin.java new file mode 100644 index 0000000000..4d78816524 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/team/TeamPlugin.java @@ -0,0 +1,458 @@ +/* + * Copyright (c) 2017, Devin French + * Copyright (c) 2021, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.team; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.inject.Provides; +import java.awt.image.BufferedImage; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; +import javax.inject.Inject; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.FriendsChatMember; +import net.runelite.api.GameState; +import net.runelite.api.Player; +import net.runelite.api.SpriteID; +import net.runelite.api.clan.ClanChannel; +import net.runelite.api.clan.ClanChannelMember; +import net.runelite.api.events.ClanChannelChanged; +import net.runelite.api.events.ClanMemberJoined; +import net.runelite.api.events.ClanMemberLeft; +import net.runelite.api.events.FriendsChatChanged; +import net.runelite.api.events.FriendsChatMemberJoined; +import net.runelite.api.events.FriendsChatMemberLeft; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.PlayerChanged; +import net.runelite.api.events.PlayerDespawned; +import net.runelite.api.events.PlayerSpawned; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; +import net.runelite.client.game.SpriteManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ui.overlay.infobox.InfoBoxManager; +import net.runelite.client.util.Text; + +@PluginDescriptor( + name = "Team", + description = "Shows how many team and clan mates are nearby", + tags = {"overlay", "players", "cape", "clan", "friend"}, + configName = "TeamCapesPlugin", // the old plugin's name + enabledByDefault = false +) +@Slf4j +public class TeamPlugin extends Plugin +{ + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private OverlayManager overlayManager; + + @Inject + private TeamConfig config; + + @Inject + private TeamCapesOverlay overlay; + + @Inject + private SpriteManager spriteManager; + + @Inject + private InfoBoxManager infoBoxManager; + + // Team number -> Number of players + @Getter(AccessLevel.PACKAGE) + private Map teams = new LinkedHashMap<>(); + // Player -> Team number + private final Map playerTeam = new HashMap<>(); + + private final BiMap players = HashBiMap.create(); + private int friendsChatCount; + private int clanChatCount; + private MembersIndicator friendsChatIndicator; + private MembersIndicator clanChatIndicator; + + @Provides + TeamConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(TeamConfig.class); + } + + @Override + protected void startUp() throws Exception + { + overlayManager.add(overlay); + + clientThread.invokeLater(() -> client.getPlayers().forEach(this::updateTeam)); + } + + @Override + protected void shutDown() throws Exception + { + overlayManager.remove(overlay); + teams.clear(); + playerTeam.clear(); + players.clear(); + removeFriendsChatCounter(); + removeClanChatCounter(); + friendsChatCount = 0; + clanChatCount = 0; + } + + @Subscribe + public void onConfigChanged(ConfigChanged configChanged) + { + if (configChanged.getGroup().equals(TeamConfig.GROUP)) + { + if (config.friendsChatMemberCounter()) + { + clientThread.invoke(this::addFriendsChatCounter); + } + else + { + removeFriendsChatCounter(); + } + + if (config.clanChatMemberCounter()) + { + clientThread.invoke(this::addClanChatCounter); + } + else + { + removeClanChatCounter(); + } + } + } + + @Subscribe + public void onGameStateChanged(GameStateChanged state) + { + GameState gameState = state.getGameState(); + + if (gameState == GameState.LOGIN_SCREEN || gameState == GameState.CONNECTION_LOST || gameState == GameState.HOPPING) + { + players.clear(); + removeFriendsChatCounter(); + removeClanChatCounter(); + } + } + + @Subscribe + public void onPlayerSpawned(PlayerSpawned event) + { + final Player local = client.getLocalPlayer(); + final Player player = event.getPlayer(); + + if (player != local) + { + players.put(Text.removeTags(player.getName()), player); + + if (player.isFriendsChatMember()) + { + ++friendsChatCount; + addFriendsChatCounter(); + } + + if (player.isClanMember()) + { + ++clanChatCount; + addClanChatCounter(); + } + } + } + + @Subscribe + public void onPlayerDespawned(PlayerDespawned playerDespawned) + { + Player player = playerDespawned.getPlayer(); + Integer team = playerTeam.remove(player); + if (team != null) + { + teams.computeIfPresent(team, (key, value) -> value > 1 ? value - 1 : null); + sortTeams(); + } + + players.inverse().remove(player); + + if (player.isFriendsChatMember()) + { + if (friendsChatCount > 0) + { + if (--friendsChatCount == 0) + { + removeFriendsChatCounter(); + } + } + } + + if (player.isClanMember()) + { + if (clanChatCount > 0) + { + if (--clanChatCount == 0) + { + removeClanChatCounter(); + } + } + } + } + + @Subscribe + public void onPlayerChanged(PlayerChanged playerChanged) + { + Player player = playerChanged.getPlayer(); + updateTeam(player); + } + + private void updateTeam(Player player) + { + int oldTeam = playerTeam.getOrDefault(player, 0); + if (oldTeam == player.getTeam()) + { + return; + } + + log.debug("{} has changed teams: {} -> {}", player.getName(), oldTeam, player.getTeam()); + + if (oldTeam > 0) + { + teams.computeIfPresent(oldTeam, (key, value) -> value > 1 ? value - 1 : null); + playerTeam.remove(player); + } + + if (player.getTeam() > 0) + { + teams.merge(player.getTeam(), 1, Integer::sum); + playerTeam.put(player, player.getTeam()); + } + + sortTeams(); + } + + private void sortTeams() + { + // Sort teams by value in descending order and then by key in ascending order, limited to 5 entries + teams = teams.entrySet().stream() + .sorted( + Comparator.comparing(Map.Entry::getValue, Comparator.reverseOrder()) + .thenComparingInt(Map.Entry::getKey) + ) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); + } + + @Subscribe + public void onFriendsChatChanged(FriendsChatChanged event) + { + if (!event.isJoined()) + { + removeFriendsChatCounter(); + friendsChatCount = 0; + } + } + + @Subscribe + public void onClanChannelChanged(ClanChannelChanged event) + { + if (!event.isGuest()) + { + removeClanChatCounter(); + clanChatCount = 0; + + ClanChannel clanChannel = event.getClanChannel(); + if (clanChannel != null) + { + for (ClanChannelMember member : clanChannel.getMembers()) + { + final String memberName = Text.toJagexName(member.getName()); + + final Player player = players.get(memberName); + if (player != null) + { + ++clanChatCount; + } + } + + addClanChatCounter(); + } + } + } + + @Subscribe + public void onFriendsChatMemberJoined(FriendsChatMemberJoined event) + { + final FriendsChatMember member = event.getMember(); + + if (member.getWorld() == client.getWorld()) + { + final String memberName = Text.toJagexName(member.getName()); + + final Player player = players.get(memberName); + if (player != null) + { + ++friendsChatCount; + addFriendsChatCounter(); + } + } + } + + @Subscribe + public void onFriendsChatMemberLeft(FriendsChatMemberLeft event) + { + final FriendsChatMember member = event.getMember(); + + if (member.getWorld() == client.getWorld()) + { + final String memberName = Text.toJagexName(member.getName()); + final Player player = players.get(memberName); + if (player != null) + { + if (friendsChatCount > 0) + { + if (--friendsChatCount == 0) + { + removeFriendsChatCounter(); + } + } + } + } + } + + @Subscribe + public void onClanMemberJoined(ClanMemberJoined clanMemberJoined) + { + final ClanChannelMember member = clanMemberJoined.getClanMember(); + + if (member.getWorld() == client.getWorld()) + { + final String memberName = Text.toJagexName(member.getName()); + + final Player player = players.get(memberName); + if (player != null) + { + ++clanChatCount; + addClanChatCounter(); + } + } + } + + @Subscribe + public void onClanMemberLeft(ClanMemberLeft clanMemberLeft) + { + final ClanChannelMember member = clanMemberLeft.getClanMember(); + + if (member.getWorld() == client.getWorld()) + { + final String memberName = Text.toJagexName(member.getName()); + final Player player = players.get(memberName); + if (player != null) + { + if (clanChatCount > 0) + { + if (--clanChatCount == 0) + { + removeClanChatCounter(); + } + } + } + } + } + + private void addFriendsChatCounter() + { + if (!config.friendsChatMemberCounter() || friendsChatIndicator != null || friendsChatCount == 0) + { + return; + } + + final BufferedImage image = spriteManager.getSprite(SpriteID.TAB_FRIENDS_CHAT, 0); + friendsChatIndicator = new MembersIndicator(image, this) + { + @Override + public String getText() + { + return Integer.toString(friendsChatCount); + } + + @Override + public String getTooltip() + { + return friendsChatCount + " friends chat member(s) near you"; + } + }; + infoBoxManager.addInfoBox(friendsChatIndicator); + } + + private void removeFriendsChatCounter() + { + infoBoxManager.removeInfoBox(friendsChatIndicator); + friendsChatIndicator = null; + } + + private void addClanChatCounter() + { + if (!config.clanChatMemberCounter() || clanChatIndicator != null || clanChatCount == 0) + { + return; + } + + final BufferedImage image = spriteManager.getSprite(SpriteID.TAB_CLAN_CHAT, 0); + clanChatIndicator = new MembersIndicator(image, this) + { + @Override + public String getText() + { + return Integer.toString(clanChatCount); + } + + @Override + public String getTooltip() + { + return clanChatCount + " clan chat member(s) near you"; + } + }; + infoBoxManager.addInfoBox(clanChatIndicator); + } + + private void removeClanChatCounter() + { + infoBoxManager.removeInfoBox(clanChatIndicator); + clanChatIndicator = null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/teamcapes/TeamCapesPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/teamcapes/TeamCapesPlugin.java deleted file mode 100644 index c36b7ddd76..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/teamcapes/TeamCapesPlugin.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) 2017, Devin French - * 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.teamcapes; - -import com.google.inject.Provides; -import java.util.Comparator; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.Collectors; -import javax.inject.Inject; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.Client; -import net.runelite.api.Player; -import net.runelite.api.events.PlayerChanged; -import net.runelite.api.events.PlayerDespawned; -import net.runelite.client.callback.ClientThread; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.overlay.OverlayManager; - -@PluginDescriptor( - name = "Team Capes", - description = "Show the different team capes in your area and the amount of each", - tags = {"overlay", "players"}, - enabledByDefault = false -) -@Slf4j -public class TeamCapesPlugin extends Plugin -{ - @Inject - private Client client; - - @Inject - private ClientThread clientThread; - - @Inject - private OverlayManager overlayManager; - - @Inject - private TeamCapesOverlay overlay; - - // Team number -> Number of players - @Getter(AccessLevel.PACKAGE) - private Map teams = new LinkedHashMap<>(); - // Player -> Team number - private final Map playerTeam = new HashMap<>(); - - @Provides - TeamCapesConfig provideConfig(ConfigManager configManager) - { - return configManager.getConfig(TeamCapesConfig.class); - } - - @Override - protected void startUp() throws Exception - { - overlayManager.add(overlay); - - clientThread.invokeLater(() -> client.getPlayers().forEach(this::update)); - } - - @Override - protected void shutDown() throws Exception - { - overlayManager.remove(overlay); - teams.clear(); - playerTeam.clear(); - } - - @Subscribe - public void onPlayerChanged(PlayerChanged playerChanged) - { - Player player = playerChanged.getPlayer(); - update(player); - } - - private void update(Player player) - { - int oldTeam = playerTeam.getOrDefault(player, 0); - if (oldTeam == player.getTeam()) - { - return; - } - - log.debug("{} has changed teams: {} -> {}", player.getName(), oldTeam, player.getTeam()); - - if (oldTeam > 0) - { - teams.computeIfPresent(oldTeam, (key, value) -> value > 1 ? value - 1 : null); - playerTeam.remove(player); - } - - if (player.getTeam() > 0) - { - teams.merge(player.getTeam(), 1, Integer::sum); - playerTeam.put(player, player.getTeam()); - } - - sort(); - } - - @Subscribe - public void onPlayerDespawned(PlayerDespawned playerDespawned) - { - Player player = playerDespawned.getPlayer(); - Integer team = playerTeam.remove(player); - if (team != null) - { - teams.computeIfPresent(team, (key, value) -> value > 1 ? value - 1 : null); - sort(); - } - } - - private void sort() - { - // Sort teams by value in descending order and then by key in ascending order, limited to 5 entries - teams = teams.entrySet().stream() - .sorted( - Comparator.comparing(Map.Entry::getValue, Comparator.reverseOrder()) - .thenComparingInt(Map.Entry::getKey) - ) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); - } -} 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 0a0716c00e..af9c35fa53 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 @@ -29,6 +29,7 @@ package net.runelite.client.plugins.xptracker; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; +import java.awt.GridLayout; import java.math.RoundingMode; import java.text.DecimalFormat; import java.util.ArrayList; @@ -53,7 +54,6 @@ import net.runelite.api.Skill; import net.runelite.api.WorldType; import net.runelite.client.game.SkillIconManager; import net.runelite.client.ui.ColorScheme; -import net.runelite.client.ui.DynamicGridLayout; import net.runelite.client.ui.FontManager; import net.runelite.client.ui.SkillColor; import net.runelite.client.ui.components.MouseDragEventForwarder; @@ -190,7 +190,7 @@ class XpInfoBox extends JPanel headerPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); headerPanel.setLayout(new BorderLayout()); - statsPanel.setLayout(new DynamicGridLayout(2, 2)); + statsPanel.setLayout(new GridLayout(2, 2)); statsPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); statsPanel.setBorder(new EmptyBorder(9, 2, 9, 2)); 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 3040440b4d..2347876076 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 @@ -24,10 +24,10 @@ */ package net.runelite.client.ui.components; -import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; +import java.awt.GridLayout; import java.util.Collections; import java.util.List; import javax.swing.JLabel; @@ -53,7 +53,7 @@ public class ProgressBar extends DimmableJPanel public ProgressBar() { - setLayout(new BorderLayout()); + setLayout(new GridLayout(1, 3)); // The background color should be overridden setBackground(Color.GREEN.darker()); setForeground(Color.GREEN.brighter()); @@ -66,6 +66,7 @@ public class ProgressBar extends DimmableJPanel rightLabel.setFont(FontManager.getRunescapeSmallFont()); rightLabel.setForeground(Color.WHITE); + rightLabel.setHorizontalAlignment(SwingConstants.RIGHT); rightLabel.setBorder(new EmptyBorder(2, 0, 0, 5)); centerLabel.setFont(FontManager.getRunescapeSmallFont()); @@ -74,9 +75,9 @@ public class ProgressBar extends DimmableJPanel centerLabel.setBorder(new EmptyBorder(2, 0, 0, 0)); // Adds components to be automatically redrawn when paintComponents is called - add(leftLabel, BorderLayout.WEST); - add(centerLabel, BorderLayout.CENTER); - add(rightLabel, BorderLayout.EAST); + add(leftLabel); + add(centerLabel); + add(rightLabel); } @Override diff --git a/runelite-client/src/test/java/net/runelite/client/OkHttpCacheSanityTest.java b/runelite-client/src/test/java/net/runelite/client/OkHttpCacheSanityTest.java new file mode 100644 index 0000000000..2478042ed2 --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/OkHttpCacheSanityTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2020 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client; + +import java.io.IOException; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Locale; +import java.util.concurrent.TimeUnit; +import net.runelite.http.api.RuneLiteAPI; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class OkHttpCacheSanityTest +{ + @Rule + public TemporaryFolder cacheFolder = new TemporaryFolder(); + + @Rule + public MockWebServer server = new MockWebServer(); + + private static final DateTimeFormatter TIME_FMT = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss z", Locale.US) + .withZone(ZoneId.of("GMT")); + + private static final String BODY_404 = "404 not found"; + private static final String BODY_200 = "{success:true}"; + + /** + * The specific thing we are trying to catch here is when a 404 + * response is served by cloudflare after the upstream has gotten + * an updated document. CF serves the 404 response with a Date header, + * but no ETag or Last-Modified. OkHttp then uses the Date header, which + * is from after the upstream was edited, in a If-Modified-Since request, + * which does hit the upstream, returning 304 Not Modified. Since there + * is no body OkHttp serves the cached 404 as up-to-date. Better yet, since + * there is now an ETag in the 304 response it requests subsequent updates + * with the ETag, which will always 304 Not Modified, causing clients in + * this state to get incorrect 404s until the ETag changes. + */ + @Test + public void testCacheSanity() throws IOException, InterruptedException + { + OkHttpClient.Builder builder = RuneLiteAPI.CLIENT.newBuilder(); + RuneLite.setupCache(builder, cacheFolder.getRoot()); + OkHttpClient client = builder.build(); + + Instant lastModified = Instant.now().minusSeconds(20); + + server.enqueue(new MockResponse() + .setResponseCode(404) + .setHeader("Content-Type", "text/html") + .setHeader("Date", TIME_FMT.format(Instant.now().minusSeconds(10))) + .setBody(BODY_404)); + try (Response res = client.newCall(new Request.Builder() + .url(server.url("/manifest")) + .build()).execute()) + { + Assert.assertEquals(404, res.code()); + } + RecordedRequest req = server.takeRequest(1, TimeUnit.SECONDS); + Assert.assertNotNull("cache did not make a initial request", req); + + server.enqueue(new MockResponse() + .setResponseCode(200) + .setHeader("Content-Type", "text/html") + .setHeader("Date", TIME_FMT.format(Instant.now().minusSeconds(5))) + .setHeader("Last-Modified", TIME_FMT.format(lastModified)) + .setBody(BODY_200)); + try (Response res = client.newCall(new Request.Builder() + .url(server.url("/manifest")) + .build()).execute()) + { + Assert.assertEquals(200, res.code()); + } + req = server.takeRequest(1, TimeUnit.SECONDS); + Assert.assertNotNull("cache did not make a request", req); + Assert.assertNull(req.getHeader("If-Modified-Since")); + + server.enqueue(new MockResponse() + .setResponseCode(304) + .setHeader("Content-Type", "text/html") + .setHeader("Date", TIME_FMT.format(Instant.now())) + .setHeader("Last-Modified", TIME_FMT.format(lastModified))); + try (Response res = client.newCall(new Request.Builder() + .url(server.url("/manifest")) + .build()).execute()) + { + Assert.assertEquals(200, res.code()); + } + req = server.takeRequest(1, TimeUnit.SECONDS); + Assert.assertNotNull("cache did not make a conditional request", req); + Assert.assertNotNull(req.getHeader("If-Modified-Since")); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/teamcapes/TeamCapesConfig.java b/runelite-client/src/test/java/net/runelite/client/plugins/itemidentification/ItemIdentificationTest.java similarity index 69% rename from runelite-client/src/main/java/net/runelite/client/plugins/teamcapes/TeamCapesConfig.java rename to runelite-client/src/test/java/net/runelite/client/plugins/itemidentification/ItemIdentificationTest.java index 17e42e0f24..bc04938c90 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/teamcapes/TeamCapesConfig.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/itemidentification/ItemIdentificationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Mathieu Bernier + * Copyright (c) 2021, Jordan Atwood * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,23 +22,17 @@ * (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.teamcapes; +package net.runelite.client.plugins.itemidentification; -import net.runelite.client.config.Config; -import net.runelite.client.config.ConfigGroup; -import net.runelite.client.config.ConfigItem; +import net.runelite.api.ItemID; +import static org.junit.Assert.assertEquals; +import org.junit.Test; -@ConfigGroup("teamCapes") -public interface TeamCapesConfig extends Config +public class ItemIdentificationTest { - @ConfigItem( - keyName = "minimumCapeCount", - name = "Minimum Cape Count", - description = "Configures the minimum number of team capes which must be present before being displayed.", - position = 0 - ) - default int getMinimumCapeCount() + @Test + public void testInit() { - return 1; + assertEquals(ItemIdentification.YEW_SEED, ItemIdentification.get(ItemID.YEW_SEED)); } -} \ No newline at end of file +} 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 e334c94d32..6f2f7061d3 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java @@ -82,6 +82,7 @@ import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.CanvasSizeChanged; import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.ClanChannelChanged; import net.runelite.api.events.ClientTick; import net.runelite.api.events.DraggingWidgetChanged; import net.runelite.api.events.FriendsChatChanged; @@ -2264,5 +2265,25 @@ public abstract class RSClientMixin implements RSClient return ClanRank.CLAN_RANK_1; } } + + @Inject + @FieldHook("guestClanChannel") + public static void onGuestClanChannelChanged(int idx) + { + client.getCallbacks().post(new ClanChannelChanged(client.getGuestClanChannel(), true)); + } + + @Inject + @FieldHook("currentClanChannels") + public static void onCurrentClanChannelsChanged(int idx) + { + if (idx == -1) + { + // don't fire on array field itself being set + return; + } + + client.getCallbacks().post(new ClanChannelChanged(client.getClanChannel(), false)); + } } diff --git a/runescape-client/src/main/java/Client.java b/runescape-client/src/main/java/Client.java index f78e80aca5..cd8521fd19 100644 --- a/runescape-client/src/main/java/Client.java +++ b/runescape-client/src/main/java/Client.java @@ -285,6 +285,7 @@ public final class Client extends GameEngine implements Usernamed { @ObfuscatedSignature( descriptor = "[Ly;" ) + @Export("currentClanChannels") static ClanChannel[] currentClanChannels; @ObfuscatedName("pi") static int[] field867;