Merge pull request #605 from deathbeam/chat-history

Add chat history plugin
This commit is contained in:
Adam
2018-02-20 18:45:58 -05:00
committed by GitHub
12 changed files with 202 additions and 62 deletions

View File

@@ -40,7 +40,7 @@ public interface Client extends GameEngine
int getRealSkillLevel(Skill skill); int getRealSkillLevel(Skill skill);
void sendGameMessage(ChatMessageType type, String message); void addChatMessage(ChatMessageType type, String name, String message, String sender);
GameState getGameState(); GameState getGameState();

View File

@@ -33,7 +33,7 @@ import net.runelite.api.ChatMessageType;
public class ChatMessage public class ChatMessage
{ {
private ChatMessageType type; private ChatMessageType type;
private String sender; private String name;
private String message; private String message;
private String clan; private String sender;
} }

View File

@@ -204,7 +204,7 @@ public class Hooks
eventBus.post(menuEntry); 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()) if (log.isDebugEnabled())
{ {
@@ -212,7 +212,7 @@ public class Hooks
} }
ChatMessageType chatMessageType = ChatMessageType.of(type); ChatMessageType chatMessageType = ChatMessageType.of(type);
ChatMessage chatMessage = new ChatMessage(chatMessageType, sender, message, clan); ChatMessage chatMessage = new ChatMessage(chatMessageType, name, message, sender);
eventBus.post(chatMessage); eventBus.post(chatMessage);
} }

View File

@@ -24,11 +24,12 @@
*/ */
package net.runelite.client.chat; package net.runelite.client.chat;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Queue; import java.util.Queue;
@@ -36,7 +37,6 @@ import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Provider; import javax.inject.Provider;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -100,83 +100,91 @@ public class ChatMessageManager
return this; 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() public void process()
{ {
for (Iterator<QueuedMessage> it = queuedMessages.iterator(); it.hasNext();) if (!queuedMessages.isEmpty())
{ {
QueuedMessage message = it.next(); queuedMessages.forEach(this::add);
add(message.getType(), message.getMessage()); queuedMessages.clear();
it.remove();
} }
} }
public void add(final ChatMessageType type, final String mesage) private void add(QueuedMessage message)
{ {
final Client client = clientProvider.get(); 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[] lines = chatLineBuffer.getLines();
final MessageNode line = lines[0]; 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(); if (Strings.isNullOrEmpty(target.getRuneLiteFormatMessage()))
final Set<ChatColor> 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())
{ {
target.setRuneLiteFormatMessage(message);
target.setValue(message);
return; return;
} }
final Client client = clientProvider.get();
final boolean transparent = client.isResized() && client.getSetting(Varbits.TRANSPARANT_CHATBOX) != 0;
final Set<ChatColor> 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<ChatColor> chatColors = colorCache.get(messageType);
final AtomicReference<String> resultMessage = new AtomicReference<>(message); final AtomicReference<String> resultMessage = new AtomicReference<>(message);
// Replace custom formatting with actual colors // Replace custom formatting with actual colors
chatColors.stream() chatColors.stream()
.filter(chatColor -> chatColor.isTransparent() == .filter(chatColor -> chatColor.isTransparent() == transparent)
(client.isResized() && client.getSetting(Varbits.TRANSPARANT_CHATBOX) != 0))
.forEach(chatColor -> .forEach(chatColor ->
resultMessage.getAndUpdate(oldMessage -> oldMessage.replaceAll( resultMessage.getAndUpdate(oldMessage -> oldMessage.replaceAll(
"<col" + chatColor.getType().name() + ">", "<col" + chatColor.getType().name() + ">",
"<col=" + Integer.toHexString(chatColor.getColor().getRGB() & 0xFFFFFF) + ">"))); "<col=" + Integer.toHexString(chatColor.getColor().getRGB() & 0xFFFFFF) + ">")));
target.setRuneLiteFormatMessage(message); return resultMessage.get();
target.setValue(resultMessage.get());
} }
public void refreshAll() public void refreshAll()
{ {
if (!config.chatCommandsRecolorEnabled())
{
return;
}
final Client client = clientProvider.get(); final Client client = clientProvider.get();
executor.submit(() -> executor.submit(() ->
{ {
final Set<MessageNode> chatLines = client.getChatLineMap().values().stream() client.getChatLineMap().values().stream()
.filter(Objects::nonNull) .filter(Objects::nonNull)
.flatMap(clb -> Arrays.stream(clb.getLines())) .flatMap(clb -> Arrays.stream(clb.getLines()))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.filter(mn -> mn.getRuneLiteFormatMessage() != null) .forEach(this::update);
.collect(Collectors.toSet());
chatLines.forEach(chatLine -> update(chatLine.getType(), chatLine.getRuneLiteFormatMessage(), chatLine)); client.refreshChat();
if (!chatLines.isEmpty())
{
client.refreshChat();
}
}); });
} }
} }

View File

@@ -24,14 +24,17 @@
*/ */
package net.runelite.client.chat; package net.runelite.client.chat;
import lombok.AllArgsConstructor; import lombok.Builder;
import lombok.Data; import lombok.Data;
import net.runelite.api.ChatMessageType; import net.runelite.api.ChatMessageType;
@Data @Data
@AllArgsConstructor @Builder
class QueuedMessage public class QueuedMessage
{ {
private final ChatMessageType type; private final ChatMessageType type;
private final String message; private final String value;
private String name;
private String sender;
private String runeLiteFormattedMessage;
} }

View File

@@ -38,14 +38,14 @@ import net.runelite.api.Client;
import net.runelite.api.GameState; import net.runelite.api.GameState;
import net.runelite.api.ItemComposition; import net.runelite.api.ItemComposition;
import net.runelite.api.MessageNode; 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.ChatColor;
import net.runelite.client.chat.ChatColorType; import net.runelite.client.chat.ChatColorType;
import net.runelite.client.chat.ChatMessageBuilder; import net.runelite.client.chat.ChatMessageBuilder;
import net.runelite.client.chat.ChatMessageManager; import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.config.ConfigManager; 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.game.ItemManager;
import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.PluginDescriptor;
@@ -185,7 +185,7 @@ public class ChatCommandsPlugin extends Plugin
log.debug("Running price lookup for {}", search); 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) 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 messageNode The chat message containing the command.
* @param search The item given with 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; SearchResult result;
@@ -263,7 +263,8 @@ public class ChatCommandsPlugin extends Plugin
String response = builder.build(); String response = builder.build();
log.debug("Setting response {}", response); log.debug("Setting response {}", response);
chatMessageManager.update(type, response, messageNode); messageNode.setRuneLiteFormatMessage(response);
chatMessageManager.update(messageNode);
client.refreshChat(); client.refreshChat();
} }
} }
@@ -325,7 +326,9 @@ public class ChatCommandsPlugin extends Plugin
.build(); .build();
log.debug("Setting response {}", response); log.debug("Setting response {}", response);
chatMessageManager.update(type, response, setMessage.getMessageNode()); final MessageNode messageNode = setMessage.getMessageNode();
messageNode.setRuneLiteFormatMessage(response);
chatMessageManager.update(messageNode);
client.refreshChat(); client.refreshChat();
} }
catch (IOException ex) catch (IOException ex)

View File

@@ -0,0 +1,119 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* 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<ChatMessageType> 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<QueuedMessage> 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;
}
}

View File

@@ -151,7 +151,7 @@ public class ClanChatPlugin extends Plugin
return; return;
} }
if (setMessage.getType() == ChatMessageType.CLANCHAT) if (setMessage.getType() == ChatMessageType.CLANCHAT && client.getClanChatCount() > 0)
{ {
insertClanRankIcon(setMessage); insertClanRankIcon(setMessage);
} }

View File

@@ -52,6 +52,7 @@ import net.runelite.client.chat.ChatColor;
import net.runelite.client.chat.ChatColorType; import net.runelite.client.chat.ChatColorType;
import net.runelite.client.chat.ChatMessageBuilder; import net.runelite.client.chat.ChatMessageBuilder;
import net.runelite.client.chat.ChatMessageManager; import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.chat.QueuedMessage;
import net.runelite.client.config.ConfigManager; import net.runelite.client.config.ConfigManager;
import net.runelite.client.game.ItemManager; import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.Plugin;
@@ -376,8 +377,10 @@ public class ExaminePlugin extends Plugin
.append("ea)"); .append("ea)");
} }
chatMessageManager.queue(ChatMessageType.EXAMINE_ITEM, message.build()); chatMessageManager.queue(QueuedMessage.builder()
client.refreshChat(); .type(ChatMessageType.EXAMINE_ITEM)
.runeLiteFormattedMessage(message.build())
.build());
} }
} }

View File

@@ -50,6 +50,7 @@ import net.runelite.client.chat.ChatColor;
import net.runelite.client.chat.ChatColorType; import net.runelite.client.chat.ChatColorType;
import net.runelite.client.chat.ChatMessageBuilder; import net.runelite.client.chat.ChatMessageBuilder;
import net.runelite.client.chat.ChatMessageManager; import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.chat.QueuedMessage;
import net.runelite.client.config.ConfigManager; import net.runelite.client.config.ConfigManager;
import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.PluginDescriptor;
@@ -239,7 +240,10 @@ public class RaidsPlugin extends Plugin
.append("%)") .append("%)")
.build(); .build();
chatMessageManager.queue(ChatMessageType.CLANCHAT_INFO, chatMessage); chatMessageManager.queue(QueuedMessage.builder()
.type(ChatMessageType.CLANCHAT_INFO)
.runeLiteFormattedMessage(chatMessage)
.build());
} }
} }
} }

View File

@@ -122,9 +122,9 @@ public abstract class RSClientMixin implements RSClient
@Inject @Inject
@Override @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 @Inject

View File

@@ -204,8 +204,8 @@ public interface RSClient extends RSGameEngine, Client
@Import("worldList") @Import("worldList")
RSWorld[] getWorldList(); RSWorld[] getWorldList();
@Import("sendGameMessage") @Import("addChatMessage")
void sendGameMessage(int var1, String var2, String var3); void addChatMessage(int type, String name, String message, String sender);
@Override @Override
@Import("getObjectDefinition") @Import("getObjectDefinition")