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 b8d4397495..3c73c8ae95 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -40,7 +40,7 @@ public interface Client extends GameEngine int getRealSkillLevel(Skill skill); - void sendGameMessage(ChatMessageType type, String message); + void addChatMessage(ChatMessageType type, String name, String message, String sender); GameState getGameState(); diff --git a/runelite-api/src/main/java/net/runelite/api/events/ChatMessage.java b/runelite-api/src/main/java/net/runelite/api/events/ChatMessage.java index ed1fa29e2e..99cfa51092 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/ChatMessage.java +++ b/runelite-api/src/main/java/net/runelite/api/events/ChatMessage.java @@ -33,7 +33,7 @@ import net.runelite.api.ChatMessageType; public class ChatMessage { private ChatMessageType type; - private String sender; + private String name; private String message; - private String clan; + private String sender; } diff --git a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java index 351a5377f7..9f4413ec60 100644 --- a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java +++ b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java @@ -204,7 +204,7 @@ public class Hooks eventBus.post(menuEntry); } - public static void addChatMessage(int type, String sender, String message, String clan) + public static void addChatMessage(int type, String name, String message, String sender) { if (log.isDebugEnabled()) { @@ -212,7 +212,7 @@ public class Hooks } ChatMessageType chatMessageType = ChatMessageType.of(type); - ChatMessage chatMessage = new ChatMessage(chatMessageType, sender, message, clan); + ChatMessage chatMessage = new ChatMessage(chatMessageType, name, message, sender); eventBus.post(chatMessage); } diff --git a/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java b/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java index 41bbb52a90..e40cbf48a3 100644 --- a/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java +++ b/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java @@ -24,11 +24,12 @@ */ package net.runelite.client.chat; +import com.google.common.base.MoreObjects; +import com.google.common.base.Strings; import com.google.common.eventbus.Subscribe; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.Map; import java.util.Objects; import java.util.Queue; @@ -36,7 +37,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; @@ -100,83 +100,91 @@ public class ChatMessageManager return this; } - public void queue(ChatMessageType type, String message) + public void queue(QueuedMessage message) { - queuedMessages.add(new QueuedMessage(type, message)); + queuedMessages.add(message); } public void process() { - for (Iterator it = queuedMessages.iterator(); it.hasNext();) + if (!queuedMessages.isEmpty()) { - QueuedMessage message = it.next(); - add(message.getType(), message.getMessage()); - it.remove(); + queuedMessages.forEach(this::add); + queuedMessages.clear(); } } - public void add(final ChatMessageType type, final String mesage) + private void add(QueuedMessage message) { final Client client = clientProvider.get(); - client.sendGameMessage(type, mesage); // this updates chat cycle - final ChatLineBuffer chatLineBuffer = client.getChatLineMap().get(type.getType()); + + // this updates chat cycle + client.addChatMessage( + message.getType(), + MoreObjects.firstNonNull(message.getName(), ""), + MoreObjects.firstNonNull(message.getValue(), message.getRuneLiteFormattedMessage()), + message.getSender()); + + // Get last message from line buffer (the one we just added) + final ChatLineBuffer chatLineBuffer = client.getChatLineMap().get(message.getType().getType()); final MessageNode[] lines = chatLineBuffer.getLines(); final MessageNode line = lines[0]; - update(line.getType(), mesage, line); + + // Update the message with RuneLite additions + line.setRuneLiteFormatMessage(message.getRuneLiteFormattedMessage()); + update(line); } - public void update(final ChatMessageType type, final String message, final MessageNode target) + public void update(final MessageNode target) { - final Client client = clientProvider.get(); - final Set chatColors = colorCache.get(type); - - // If we do not have any colors cached or recoloring is disabled, simply set message - if (!config.chatCommandsRecolorEnabled() || chatColors == null || chatColors.isEmpty()) + if (Strings.isNullOrEmpty(target.getRuneLiteFormatMessage())) { - target.setRuneLiteFormatMessage(message); - target.setValue(message); return; } + final Client client = clientProvider.get(); + final boolean transparent = client.isResized() && client.getSetting(Varbits.TRANSPARANT_CHATBOX) != 0; + final Set chatColors = colorCache.get(target.getType()); + + // If we do not have any colors cached or recoloring is disabled, simply set clean message + if (!config.chatCommandsRecolorEnabled() || chatColors == null || chatColors.isEmpty()) + { + target.setValue(target.getRuneLiteFormatMessage()); + return; + } + + target.setValue(recolorMessage(transparent, target.getRuneLiteFormatMessage(), target.getType())); + } + + private String recolorMessage(boolean transparent, String message, ChatMessageType messageType) + { + final Set chatColors = colorCache.get(messageType); final AtomicReference resultMessage = new AtomicReference<>(message); // Replace custom formatting with actual colors chatColors.stream() - .filter(chatColor -> chatColor.isTransparent() == - (client.isResized() && client.getSetting(Varbits.TRANSPARANT_CHATBOX) != 0)) + .filter(chatColor -> chatColor.isTransparent() == transparent) .forEach(chatColor -> resultMessage.getAndUpdate(oldMessage -> oldMessage.replaceAll( "", ""))); - target.setRuneLiteFormatMessage(message); - target.setValue(resultMessage.get()); + return resultMessage.get(); } public void refreshAll() { - if (!config.chatCommandsRecolorEnabled()) - { - return; - } - final Client client = clientProvider.get(); executor.submit(() -> { - final Set chatLines = client.getChatLineMap().values().stream() + client.getChatLineMap().values().stream() .filter(Objects::nonNull) .flatMap(clb -> Arrays.stream(clb.getLines())) .filter(Objects::nonNull) - .filter(mn -> mn.getRuneLiteFormatMessage() != null) - .collect(Collectors.toSet()); + .forEach(this::update); - chatLines.forEach(chatLine -> update(chatLine.getType(), chatLine.getRuneLiteFormatMessage(), chatLine)); - - if (!chatLines.isEmpty()) - { - client.refreshChat(); - } + client.refreshChat(); }); } } diff --git a/runelite-client/src/main/java/net/runelite/client/chat/QueuedMessage.java b/runelite-client/src/main/java/net/runelite/client/chat/QueuedMessage.java index e25172fa46..cc3bce295b 100644 --- a/runelite-client/src/main/java/net/runelite/client/chat/QueuedMessage.java +++ b/runelite-client/src/main/java/net/runelite/client/chat/QueuedMessage.java @@ -24,14 +24,17 @@ */ package net.runelite.client.chat; -import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import net.runelite.api.ChatMessageType; @Data -@AllArgsConstructor -class QueuedMessage +@Builder +public class QueuedMessage { private final ChatMessageType type; - private final String message; + private final String value; + private String name; + private String sender; + private String runeLiteFormattedMessage; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java index 90ecb7d759..207c02ff47 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java @@ -38,14 +38,14 @@ import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.ItemComposition; import net.runelite.api.MessageNode; +import net.runelite.api.events.ConfigChanged; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.SetMessage; import net.runelite.client.chat.ChatColor; import net.runelite.client.chat.ChatColorType; import net.runelite.client.chat.ChatMessageBuilder; import net.runelite.client.chat.ChatMessageManager; import net.runelite.client.config.ConfigManager; -import net.runelite.api.events.ConfigChanged; -import net.runelite.api.events.GameStateChanged; -import net.runelite.api.events.SetMessage; import net.runelite.client.game.ItemManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; @@ -185,7 +185,7 @@ public class ChatCommandsPlugin extends Plugin log.debug("Running price lookup for {}", search); - executor.submit(() -> itemPriceLookup(setMessage.getType(), setMessage.getMessageNode(), search)); + executor.submit(() -> itemPriceLookup(setMessage.getMessageNode(), search)); } else if (config.lvl() && message.toLowerCase().startsWith("!lvl") && message.length() > 5) { @@ -203,7 +203,7 @@ public class ChatCommandsPlugin extends Plugin * @param messageNode The chat message containing the command. * @param search The item given with the command. */ - private void itemPriceLookup(ChatMessageType type, MessageNode messageNode, String search) + private void itemPriceLookup(MessageNode messageNode, String search) { SearchResult result; @@ -263,7 +263,8 @@ public class ChatCommandsPlugin extends Plugin String response = builder.build(); log.debug("Setting response {}", response); - chatMessageManager.update(type, response, messageNode); + messageNode.setRuneLiteFormatMessage(response); + chatMessageManager.update(messageNode); client.refreshChat(); } } @@ -325,7 +326,9 @@ public class ChatCommandsPlugin extends Plugin .build(); log.debug("Setting response {}", response); - chatMessageManager.update(type, response, setMessage.getMessageNode()); + final MessageNode messageNode = setMessage.getMessageNode(); + messageNode.setRuneLiteFormatMessage(response); + chatMessageManager.update(messageNode); client.refreshChat(); } catch (IOException ex) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java new file mode 100644 index 0000000000..2d1bae7dbc --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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.chathistory; + +import com.google.common.collect.EvictingQueue; +import com.google.common.collect.Sets; +import com.google.common.eventbus.Subscribe; +import java.util.Queue; +import java.util.Set; +import javax.inject.Inject; +import net.runelite.api.ChatMessageType; +import net.runelite.api.events.SetMessage; +import net.runelite.client.chat.ChatMessageManager; +import net.runelite.client.chat.QueuedMessage; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; + +@PluginDescriptor(name = "Chat history") +public class ChatHistoryPlugin extends Plugin +{ + private static final String WELCOME_MESSAGE = "Welcome to RuneScape."; + private static final Set ALLOWED_HISTORY = Sets.newHashSet( + ChatMessageType.PUBLIC, + ChatMessageType.CLANCHAT, + ChatMessageType.PRIVATE_MESSAGE_RECEIVED, + ChatMessageType.PRIVATE_MESSAGE_SENT, + ChatMessageType.PRIVATE_MESSAGE_RECEIVED_MOD, + ChatMessageType.GAME + ); + + private Queue messageQueue; + + @Inject + private ChatMessageManager chatMessageManager; + + @Override + protected void startUp() + { + messageQueue = EvictingQueue.create(100); + } + + @Override + protected void shutDown() + { + messageQueue.clear(); + messageQueue = null; + } + + @Subscribe + public void onSetMessage(SetMessage message) + { + // Start sending old messages right after the welcome message, as that is most reliable source + // of information that chat history was reset + if (message.getValue().equals(WELCOME_MESSAGE)) + { + QueuedMessage queuedMessage; + + while ((queuedMessage = messageQueue.poll()) != null) + { + chatMessageManager.queue(queuedMessage); + } + + return; + } + + if (ALLOWED_HISTORY.contains(message.getType())) + { + final QueuedMessage queuedMessage = QueuedMessage.builder() + .type(message.getType()) + .name(message.getName()) + .sender(message.getSender()) + .value(nbsp(message.getValue())) + .runeLiteFormattedMessage(nbsp(message.getMessageNode().getRuneLiteFormatMessage())) + .build(); + + if (!messageQueue.contains(queuedMessage)) + { + messageQueue.offer(queuedMessage); + } + } + } + + /** + * Small hack to prevent plugins checking for specific messages to match + * @param message message + * @return message with nbsp + */ + private static String nbsp(final String message) + { + if (message != null) + { + return message.replace(' ', '\u00A0'); + } + + return null; + } +} 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 c7cec907a8..56a27d00bf 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 @@ -151,7 +151,7 @@ public class ClanChatPlugin extends Plugin return; } - if (setMessage.getType() == ChatMessageType.CLANCHAT) + if (setMessage.getType() == ChatMessageType.CLANCHAT && client.getClanChatCount() > 0) { insertClanRankIcon(setMessage); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java index 7b45e08371..1aea6c2525 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java @@ -52,6 +52,7 @@ import net.runelite.client.chat.ChatColor; import net.runelite.client.chat.ChatColorType; 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.game.ItemManager; import net.runelite.client.plugins.Plugin; @@ -376,8 +377,10 @@ public class ExaminePlugin extends Plugin .append("ea)"); } - chatMessageManager.queue(ChatMessageType.EXAMINE_ITEM, message.build()); - client.refreshChat(); + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.EXAMINE_ITEM) + .runeLiteFormattedMessage(message.build()) + .build()); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java index 3743050207..4c3cfe81aa 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java @@ -50,6 +50,7 @@ import net.runelite.client.chat.ChatColor; import net.runelite.client.chat.ChatColorType; 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.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; @@ -239,7 +240,10 @@ public class RaidsPlugin extends Plugin .append("%)") .build(); - chatMessageManager.queue(ChatMessageType.CLANCHAT_INFO, chatMessage); + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.CLANCHAT_INFO) + .runeLiteFormattedMessage(chatMessage) + .build()); } } } 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 73a909e4f1..93d07ca64e 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java @@ -122,9 +122,9 @@ public abstract class RSClientMixin implements RSClient @Inject @Override - public void sendGameMessage(ChatMessageType type, String message) + public void addChatMessage(ChatMessageType type, String name, String message, String sender) { - sendGameMessage(type.getType(), "", message); + addChatMessage(type.getType(), name, message, sender); } @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 4402dd7eb1..4325635af4 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 @@ -204,8 +204,8 @@ public interface RSClient extends RSGameEngine, Client @Import("worldList") RSWorld[] getWorldList(); - @Import("sendGameMessage") - void sendGameMessage(int var1, String var2, String var3); + @Import("addChatMessage") + void addChatMessage(int type, String name, String message, String sender); @Override @Import("getObjectDefinition")