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 05c057a38d..6ac6aa42bb 100644 --- a/runelite-api/src/main/java/net/runelite/api/ScriptID.java +++ b/runelite-api/src/main/java/net/runelite/api/ScriptID.java @@ -45,6 +45,11 @@ public final class ScriptID */ public static final int CHATBOX_INPUT = 96; + /** + * Rebuilds the chatbox + */ + public static final int BUILD_CHATBOX = 216; + /** * Opens the Private Message chat interface * diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanActivityType.java b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanActivityType.java new file mode 100644 index 0000000000..86cc5e60a5 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanActivityType.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018, trimbe + * 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.clanchat; + +enum ClanActivityType +{ + JOINED, + LEFT +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChatConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChatConfig.java index 6a8000391c..cfddcb84a5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChatConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChatConfig.java @@ -24,6 +24,7 @@ */ package net.runelite.client.plugins.clanchat; +import net.runelite.api.ClanMemberRank; import net.runelite.client.config.Config; import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigItem; @@ -34,7 +35,8 @@ public interface ClanChatConfig extends Config @ConfigItem( keyName = "clanChatIcons", name = "Clan Chat Icons", - description = "Show clan chat icons next to clan members." + description = "Show clan chat icons next to clan members.", + position = 1 ) default boolean clanChatIcons() { @@ -44,7 +46,8 @@ public interface ClanChatConfig extends Config @ConfigItem( keyName = "recentChats", name = "Recent Chats", - description = "Show recent clan chats." + description = "Show recent clan chats.", + position = 2 ) default boolean recentChats() { @@ -54,7 +57,8 @@ public interface ClanChatConfig extends Config @ConfigItem( keyName = "clanCounter", name = "Clan Members Counter", - description = "Show the amount of clan members near you." + description = "Show the amount of clan members near you.", + position = 3 ) default boolean showClanCounter() { @@ -78,4 +82,26 @@ public interface ClanChatConfig extends Config description = "" ) void chatsData(String str); + + @ConfigItem( + keyName = "showJoinLeave", + name = "Show Join/Leave", + description = "Adds a temporary message notifying when a member joins or leaves.", + position = 4 + ) + default boolean showJoinLeave() + { + return false; + } + + @ConfigItem( + keyName = "joinLeaveRank", + name = "Join/Leave rank", + description = "Only show join/leave messages for members at or above this rank.", + position = 5 + ) + default ClanMemberRank joinLeaveRank() + { + return ClanMemberRank.UNRANKED; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChatPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChatPlugin.java index 2b99092397..3e57a0723f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChatPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanChatPlugin.java @@ -1,5 +1,7 @@ /* * Copyright (c) 2017, Devin French + * Copyright (c) 2019, Adam + * Copyright (c) 2018, trimbe * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -27,19 +29,31 @@ package net.runelite.client.plugins.clanchat; import com.google.common.base.Strings; import com.google.common.collect.Lists; 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; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; import javax.inject.Inject; +import net.runelite.api.ChatLineBuffer; import net.runelite.api.ChatMessageType; import net.runelite.api.ClanMemberRank; import net.runelite.api.Client; import net.runelite.api.GameState; +import net.runelite.api.MessageNode; 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.events.ChatMessage; import net.runelite.api.events.ClanChanged; +import net.runelite.api.events.ClanMemberJoined; +import net.runelite.api.events.ClanMemberLeft; import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.GameTick; @@ -50,13 +64,19 @@ import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetInfo; import net.runelite.api.widgets.WidgetType; import net.runelite.client.callback.ClientThread; +import net.runelite.client.chat.ChatMessageBuilder; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.game.ClanManager; import net.runelite.client.game.SpriteManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; +import static net.runelite.client.ui.JagexColors.CHAT_CLAN_NAME_OPAQUE_BACKGROUND; +import static net.runelite.client.ui.JagexColors.CHAT_CLAN_NAME_TRANSPARENT_BACKGROUND; +import static net.runelite.client.ui.JagexColors.CHAT_CLAN_TEXT_OPAQUE_BACKGROUND; +import static net.runelite.client.ui.JagexColors.CHAT_CLAN_TEXT_TRANSPARENT_BACKGROUND; import net.runelite.client.ui.overlay.infobox.InfoBoxManager; +import net.runelite.client.util.ColorUtil; import net.runelite.client.util.Text; @PluginDescriptor( @@ -69,6 +89,8 @@ public class ClanChatPlugin extends Plugin private static final int MAX_CHATS = 10; private static final String CLAN_CHAT_TITLE = "Clan Chat"; private static final String RECENT_TITLE = "Recent Clan Chats"; + private static final int JOIN_LEAVE_DURATION = 20; + private static final int MESSAGE_DELAY = 10; @Inject private Client client; @@ -91,6 +113,12 @@ public class ClanChatPlugin extends Plugin private List chats = new ArrayList<>(); private List clanMembers = new ArrayList<>(); private ClanChatIndicator clanMemberCounter; + /** + * queue of temporary messages added to the client + */ + private final Deque clanJoinMessages = new ArrayDeque<>(); + private Map activityBuffer = new HashMap<>(); + private int clanJoinedTick; @Provides ClanChatConfig getConfig(ConfigManager configManager) @@ -133,6 +161,49 @@ public class ClanChatPlugin extends Plugin } } + @Subscribe + public void onClanMemberJoined(ClanMemberJoined event) + { + // clan members getting initialized isn't relevant + if (clanJoinedTick == client.getTickCount()) + { + return; + } + + if (!config.showJoinLeave() || + clanManager.getRank(event.getName()).getValue() < config.joinLeaveRank().getValue()) + { + return; + } + + // attempt to filter out world hopping joins + if (!activityBuffer.containsKey(event.getName())) + { + ClanMemberActivity joinActivity = new ClanMemberActivity(ClanActivityType.JOINED, + event.getName(), client.getTickCount()); + activityBuffer.put(event.getName(), joinActivity); + } + else + { + activityBuffer.remove(event.getName()); + } + } + + @Subscribe + public void onClanMemberLeft(ClanMemberLeft event) + { + if (!config.showJoinLeave() || + clanManager.getRank(event.getName()).getValue() < config.joinLeaveRank().getValue()) + { + return; + } + + ClanMemberActivity leaveActivity = new ClanMemberActivity(ClanActivityType.LEFT, + event.getName(), client.getTickCount()); + + activityBuffer.put(event.getName(), leaveActivity); + } + @Subscribe public void onGameTick(GameTick gameTick) { @@ -157,6 +228,112 @@ public class ClanChatPlugin extends Plugin loadClanChats(); } } + + if (!config.showJoinLeave()) + { + return; + } + + timeoutClanMessages(); + + addClanActivityMessages(); + } + + private void timeoutClanMessages() + { + if (clanJoinMessages.isEmpty()) + { + return; + } + + boolean removed = false; + + for (Iterator it = clanJoinMessages.iterator(); it.hasNext(); ) + { + ClanJoinMessage clanJoinMessage = it.next(); + MessageNode messageNode = clanJoinMessage.getMessageNode(); + final int createdTick = clanJoinMessage.getTick(); + + if (client.getTickCount() > createdTick + JOIN_LEAVE_DURATION) + { + it.remove(); + + // If this message has been reused since, it will get a different id + if (clanJoinMessage.getGetMessageId() == messageNode.getId()) + { + ChatLineBuffer ccInfoBuffer = client.getChatLineMap().get(ChatMessageType.CLANCHAT_INFO.getType()); + if (ccInfoBuffer != null) + { + ccInfoBuffer.removeMessageNode(messageNode); + removed = true; + } + } + } + else + { + // Everything else in the deque is newer + break; + } + } + + if (removed) + { + clientThread.invoke(() -> client.runScript(ScriptID.BUILD_CHATBOX)); + } + } + + private void addClanActivityMessages() + { + Iterator activityIt = activityBuffer.values().iterator(); + + while (activityIt.hasNext()) + { + ClanMemberActivity activity = activityIt.next(); + + if (activity.getTick() < client.getTickCount() - MESSAGE_DELAY) + { + activityIt.remove(); + addActivityMessage(activity.getMember(), activity.getActivityType()); + } + } + } + + private void addActivityMessage(String memberName, ClanActivityType activityType) + { + final String activityMessage = activityType == ClanActivityType.JOINED ? " has joined." : " has left."; + final ClanMemberRank rank = clanManager.getRank(memberName); + String rankTag = ""; + Color textColor = CHAT_CLAN_TEXT_OPAQUE_BACKGROUND; + Color channelColor = CHAT_CLAN_NAME_OPAQUE_BACKGROUND; + + if (client.isResized() && client.getVar(Varbits.TRANSPARENT_CHATBOX) == 1) + { + textColor = CHAT_CLAN_TEXT_TRANSPARENT_BACKGROUND; + channelColor = CHAT_CLAN_NAME_TRANSPARENT_BACKGROUND; + } + + if (rank != null && rank != ClanMemberRank.UNRANKED) + { + int iconNumber = clanManager.getIconNumber(rank); + rankTag = " "; + } + + ChatMessageBuilder message = new ChatMessageBuilder(); + String messageString = message + .append("[") + .append(ColorUtil.wrapWithColorTag(client.getClanChatName(), channelColor) + rankTag) + .append("] ") + .append(ColorUtil.wrapWithColorTag(memberName + activityMessage, textColor)) + .build(); + + client.addChatMessage(ChatMessageType.CLANCHAT_INFO, "", messageString, ""); + + final ChatLineBuffer chatLineBuffer = client.getChatLineMap().get(ChatMessageType.CLANCHAT_INFO.getType()); + final MessageNode[] lines = chatLineBuffer.getLines(); + final MessageNode line = lines[0]; + + ClanJoinMessage clanJoinMessage = new ClanJoinMessage(line, line.getId(), client.getTickCount()); + clanJoinMessages.addLast(clanJoinMessage); } @Subscribe @@ -190,6 +367,13 @@ public class ClanChatPlugin extends Plugin clanMembers.clear(); removeClanCounter(); } + + if (state.getGameState() == GameState.LOGIN_SCREEN + || state.getGameState() == GameState.HOPPING + || state.getGameState() == GameState.CONNECTION_LOST) + { + clanJoinMessages.clear(); + } } @Subscribe @@ -216,6 +400,8 @@ public class ClanChatPlugin extends Plugin { if (event.isJoined()) { + clanJoinedTick = client.getTickCount(); + clientThread.invokeLater(() -> { for (Player player : client.getPlayers()) @@ -234,6 +420,8 @@ public class ClanChatPlugin extends Plugin clanMembers.clear(); removeClanCounter(); } + + activityBuffer.clear(); } int getClanAmount() diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanJoinMessage.java b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanJoinMessage.java new file mode 100644 index 0000000000..8ac0b0069b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanJoinMessage.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019, 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.clanchat; + +import lombok.Value; +import net.runelite.api.MessageNode; + +@Value +class ClanJoinMessage +{ + private final MessageNode messageNode; + private final int getMessageId; + private final int tick; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanMemberActivity.java b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanMemberActivity.java new file mode 100644 index 0000000000..f739437b15 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/clanchat/ClanMemberActivity.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018, trimbe + * 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.clanchat; + +import lombok.AllArgsConstructor; +import lombok.Value; + +@Value +@AllArgsConstructor +class ClanMemberActivity +{ + private ClanActivityType activityType; + private String member; + private Integer tick; +} diff --git a/runelite-client/src/main/java/net/runelite/client/ui/JagexColors.java b/runelite-client/src/main/java/net/runelite/client/ui/JagexColors.java index 7e2faadfad..ac5fbeed6f 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/JagexColors.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/JagexColors.java @@ -37,6 +37,7 @@ public class JagexColors public static final Color CHAT_PUBLIC_TEXT_OPAQUE_BACKGROUND = Color.BLUE; public static final Color CHAT_PRIVATE_MESSAGE_TEXT_OPAQUE_BACKGROUND = Color.CYAN; public static final Color CHAT_CLAN_TEXT_OPAQUE_BACKGROUND = new Color(127, 0, 0); + public static final Color CHAT_CLAN_NAME_OPAQUE_BACKGROUND = Color.BLUE; public static final Color CHAT_GAME_EXAMINE_TEXT_OPAQUE_BACKGROUND = Color.BLACK; public static final Color CHAT_TYPED_TEXT_OPAQUE_BACKGROUND = Color.BLUE; @@ -45,7 +46,8 @@ public class JagexColors */ public static final Color CHAT_PUBLIC_TEXT_TRANSPARENT_BACKGROUND = new Color(144, 144, 255); public static final Color CHAT_PRIVATE_MESSAGE_TEXT_TRANSPARENT_BACKGROUND = Color.CYAN; - public static final Color CHAT_CLAN_TEXT_TRANSPARENT_BACKGROUND = new Color(127, 0, 0); + public static final Color CHAT_CLAN_TEXT_TRANSPARENT_BACKGROUND = new Color(239, 80, 80); + public static final Color CHAT_CLAN_NAME_TRANSPARENT_BACKGROUND = new Color(144, 112, 255); public static final Color CHAT_GAME_EXAMINE_TEXT_TRANSPARENT_BACKGROUND = Color.WHITE; public static final Color CHAT_TYPED_TEXT_TRANSPARENT_BACKGROUND = new Color(144, 144, 255);