client: add chat command manager

This commit is contained in:
Adam
2019-01-05 18:02:55 -05:00
committed by GitHub
parent ce18860d10
commit b2c15a8c34
6 changed files with 363 additions and 159 deletions

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* 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.chat;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.runelite.api.events.SetMessage;
import net.runelite.client.events.ChatInput;
@AllArgsConstructor
@Getter
class ChatCommand
{
private final String name;
private boolean async;
private final BiConsumer<SetMessage, String> execute;
private final BiPredicate<ChatInput, String> input;
}

View File

@@ -0,0 +1,197 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* 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.chat;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.events.SetMessage;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ChatInput;
import net.runelite.client.events.ChatboxInput;
import net.runelite.client.events.PrivateMessageInput;
@Singleton
@Slf4j
public class ChatCommandManager implements ChatboxInputListener
{
private final Map<String, ChatCommand> commands = new HashMap<>();
private final Client client;
private final ScheduledExecutorService scheduledExecutorService;
@Inject
private ChatCommandManager(EventBus eventBus, CommandManager commandManager, Client client, ScheduledExecutorService scheduledExecutorService)
{
this.client = client;
this.scheduledExecutorService = scheduledExecutorService;
eventBus.register(this);
commandManager.register(this);
}
public void registerCommand(String command, BiConsumer<SetMessage, String> execute)
{
registerCommand(command, execute, null);
}
public void registerCommand(String command, BiConsumer<SetMessage, String> execute, BiPredicate<ChatInput, String> input)
{
commands.put(command.toLowerCase(), new ChatCommand(command, false, execute, input));
}
public void registerCommandAsync(String command, BiConsumer<SetMessage, String> execute)
{
registerCommandAsync(command, execute, null);
}
public void registerCommandAsync(String command, BiConsumer<SetMessage, String> execute, BiPredicate<ChatInput, String> input)
{
commands.put(command.toLowerCase(), new ChatCommand(command, true, execute, input));
}
public void unregisterCommand(String command)
{
commands.remove(command.toLowerCase());
}
@Subscribe
public void onSetMessage(SetMessage setMessage)
{
if (client.getGameState() != GameState.LOGGED_IN)
{
return;
}
switch (setMessage.getType())
{
case PUBLIC:
case PUBLIC_MOD:
case CLANCHAT:
case PRIVATE_MESSAGE_RECEIVED:
case PRIVATE_MESSAGE_SENT:
break;
default:
return;
}
String message = setMessage.getValue();
String command = extractCommand(message);
if (command == null)
{
return;
}
ChatCommand chatCommand = commands.get(command.toLowerCase());
if (chatCommand == null)
{
return;
}
if (chatCommand.isAsync())
{
scheduledExecutorService.execute(() -> chatCommand.getExecute().accept(setMessage, message));
}
else
{
chatCommand.getExecute().accept(setMessage, message);
}
}
@Override
public boolean onChatboxInput(ChatboxInput chatboxInput)
{
String message = chatboxInput.getValue();
if (message.startsWith("/"))
{
message = message.substring(1); // clan chat input
}
String command = extractCommand(message);
if (command == null)
{
return false;
}
ChatCommand chatCommand = commands.get(command.toLowerCase());
if (chatCommand == null)
{
return false;
}
BiPredicate<ChatInput, String> input = chatCommand.getInput();
if (input == null)
{
return false;
}
return input.test(chatboxInput, message);
}
@Override
public boolean onPrivateMessageInput(PrivateMessageInput privateMessageInput)
{
final String message = privateMessageInput.getMessage();
String command = extractCommand(message);
if (command == null)
{
return false;
}
ChatCommand chatCommand = commands.get(command.toLowerCase());
if (chatCommand == null)
{
return false;
}
BiPredicate<ChatInput, String> input = chatCommand.getInput();
if (input == null)
{
return false;
}
return input.test(privateMessageInput, message);
}
private static String extractCommand(String message)
{
int idx = message.indexOf(' ');
if (idx == -1)
{
return message;
}
return message.substring(0, idx);
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.events;
public abstract class ChatInput
{
public abstract void resume();
}

View File

@@ -27,10 +27,8 @@ package net.runelite.client.events;
import lombok.Data;
@Data
public abstract class ChatboxInput
public abstract class ChatboxInput extends ChatInput
{
private final String value;
private final int chatType;
public abstract void resume();
}

View File

@@ -27,10 +27,8 @@ package net.runelite.client.events;
import lombok.Data;
@Data
public abstract class PrivateMessageInput
public abstract class PrivateMessageInput extends ChatInput
{
private final String target;
private final String message;
public abstract void resume();
}

View File

@@ -37,27 +37,25 @@ import lombok.extern.slf4j.Slf4j;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.Experience;
import net.runelite.api.GameState;
import net.runelite.api.IconID;
import net.runelite.api.ItemComposition;
import net.runelite.api.MessageNode;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.SetMessage;
import net.runelite.api.events.VarbitChanged;
import net.runelite.api.events.WidgetLoaded;
import net.runelite.api.vars.AccountType;
import net.runelite.api.widgets.Widget;
import static net.runelite.api.widgets.WidgetID.KILL_LOGS_GROUP_ID;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.chat.ChatColorType;
import net.runelite.client.chat.ChatCommandManager;
import net.runelite.client.chat.ChatMessageBuilder;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.chat.ChatboxInputListener;
import net.runelite.client.chat.CommandManager;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ChatboxInput;
import net.runelite.client.events.PrivateMessageInput;
import net.runelite.client.events.ChatInput;
import net.runelite.client.game.ItemManager;
import net.runelite.client.input.KeyManager;
import net.runelite.client.plugins.Plugin;
@@ -78,7 +76,7 @@ import net.runelite.http.api.kc.KillCountClient;
tags = {"grand", "exchange", "level", "prices"}
)
@Slf4j
public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener
public class ChatCommandsPlugin extends Plugin
{
private static final float HIGH_ALCHEMY_CONSTANT = 0.6f;
private static final Pattern KILLCOUNT_PATTERN = Pattern.compile("Your (.+) kill count is: <col=ff0000>(\\d+)</col>.");
@@ -96,6 +94,7 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener
private final KillCountClient killCountClient = new KillCountClient();
private boolean logKills;
private HiscoreEndpoint hiscoreEndpoint; // hiscore endpoint for current player
@Inject
private Client client;
@@ -112,6 +111,9 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener
@Inject
private ChatMessageManager chatMessageManager;
@Inject
private ChatCommandManager chatCommandManager;
@Inject
private ScheduledExecutorService executor;
@@ -121,21 +123,30 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener
@Inject
private ChatKeyboardListener chatKeyboardListener;
@Inject
private CommandManager commandManager;
@Override
public void startUp()
{
keyManager.registerKeyListener(chatKeyboardListener);
commandManager.register(this);
chatCommandManager.registerCommandAsync(TOTAL_LEVEL_COMMAND_STRING, this::playerSkillLookup);
chatCommandManager.registerCommandAsync(CMB_COMMAND_STRING, this::combatLevelLookup);
chatCommandManager.registerCommand(PRICE_COMMAND_STRING, this::itemPriceLookup);
chatCommandManager.registerCommandAsync(LEVEL_COMMAND_STRING, this::playerSkillLookup);
chatCommandManager.registerCommandAsync(CLUES_COMMAND_STRING, this::clueLookup);
chatCommandManager.registerCommandAsync(KILLCOUNT_COMMAND_STRING, this::killCountLookup, this::killCountSubmit);
}
@Override
public void shutDown()
{
keyManager.unregisterKeyListener(chatKeyboardListener);
commandManager.unregister(this);
chatCommandManager.unregisterCommand(TOTAL_LEVEL_COMMAND_STRING);
chatCommandManager.unregisterCommand(CMB_COMMAND_STRING);
chatCommandManager.unregisterCommand(PRICE_COMMAND_STRING);
chatCommandManager.unregisterCommand(LEVEL_COMMAND_STRING);
chatCommandManager.unregisterCommand(CLUES_COMMAND_STRING);
chatCommandManager.unregisterCommand(KILLCOUNT_COMMAND_STRING);
}
@Provides
@@ -157,84 +168,6 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener
return killCount == null ? 0 : killCount;
}
/**
* Checks if the chat message is a command.
*
* @param setMessage The chat message.
*/
@Subscribe
public void onSetMessage(SetMessage setMessage)
{
if (client.getGameState() != GameState.LOGGED_IN)
{
return;
}
switch (setMessage.getType())
{
case PUBLIC:
case PUBLIC_MOD:
case CLANCHAT:
case PRIVATE_MESSAGE_RECEIVED:
case PRIVATE_MESSAGE_SENT:
break;
default:
return;
}
String message = setMessage.getValue();
MessageNode messageNode = setMessage.getMessageNode();
HiscoreEndpoint localEndpoint = getHiscoreEndpointType();
// clear RuneLite formatted message as the message node is
// being reused
messageNode.setRuneLiteFormatMessage(null);
if (config.lvl() && message.toLowerCase().equals(TOTAL_LEVEL_COMMAND_STRING))
{
log.debug("Running total level lookup");
executor.submit(() -> playerSkillLookup(setMessage, localEndpoint, "total"));
}
else if (config.lvl() && message.toLowerCase().equals(CMB_COMMAND_STRING))
{
log.debug("Running combat level lookup");
executor.submit(() -> combatLevelLookup(setMessage.getType(), setMessage));
}
else if (config.price() && message.toLowerCase().startsWith(PRICE_COMMAND_STRING + " "))
{
String search = message.substring(PRICE_COMMAND_STRING.length() + 1);
log.debug("Running price lookup for {}", search);
itemPriceLookup(setMessage.getMessageNode(), search);
}
else if (config.lvl() && message.toLowerCase().startsWith(LEVEL_COMMAND_STRING + " "))
{
String search = message.substring(LEVEL_COMMAND_STRING.length() + 1);
log.debug("Running level lookup for {}", search);
executor.submit(() -> playerSkillLookup(setMessage, localEndpoint, search));
}
else if (config.clue() && message.toLowerCase().equals(CLUES_COMMAND_STRING))
{
log.debug("Running lookup for overall clues");
executor.submit(() -> playerClueLookup(setMessage, localEndpoint, "total"));
}
else if (config.clue() && message.toLowerCase().startsWith(CLUES_COMMAND_STRING + " "))
{
String search = message.substring(CLUES_COMMAND_STRING.length() + 1);
log.debug("Running clue lookup for {}", search);
executor.submit(() -> playerClueLookup(setMessage, localEndpoint, search));
}
else if (config.killcount() && message.toLowerCase().startsWith(KILLCOUNT_COMMAND_STRING + " "))
{
String search = message.substring(KILLCOUNT_COMMAND_STRING.length() + 1);
log.debug("Running killcount lookup for {}", search);
executor.submit(() -> killCountLookup(setMessage.getType(), setMessage, search));
}
}
@Subscribe
public void onChatMessage(ChatMessage chatMessage)
{
@@ -329,15 +262,14 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener
logKills = true;
}
@Override
public boolean onChatboxInput(ChatboxInput chatboxInput)
@Subscribe
public void onVarbitChanged(VarbitChanged varbitChanged)
{
final String value = chatboxInput.getValue();
if (!config.killcount() || !value.startsWith("!kc ") && !value.startsWith("/!kc "))
{
return false;
}
hiscoreEndpoint = getLocalHiscoreEndpointType();
}
private boolean killCountSubmit(ChatInput chatInput, String value)
{
int idx = value.indexOf(' ');
final String boss = longBossName(value.substring(idx + 1));
@@ -361,54 +293,23 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener
}
finally
{
chatboxInput.resume();
chatInput.resume();
}
});
return true;
}
@Override
public boolean onPrivateMessageInput(PrivateMessageInput privateMessageInput)
private void killCountLookup(SetMessage setMessage, String message)
{
final String message = privateMessageInput.getMessage();
if (!config.killcount() || !message.startsWith("!kc "))
if (!config.killcount())
{
return false;
return;
}
int idx = message.indexOf(' ');
final String boss = longBossName(message.substring(idx + 1));
ChatMessageType type = setMessage.getType();
String search = message.substring(KILLCOUNT_COMMAND_STRING.length() + 1);
final int kc = getKc(boss);
if (kc <= 0)
{
return false;
}
final String playerName = client.getLocalPlayer().getName();
executor.execute(() ->
{
try
{
killCountClient.submit(playerName, boss, kc);
}
catch (Exception ex)
{
log.warn("unable to submit killcount", ex);
}
finally
{
privateMessageInput.resume();
}
});
return true;
}
private void killCountLookup(ChatMessageType type, SetMessage setMessage, String search)
{
final String player;
if (type.equals(ChatMessageType.PRIVATE_MESSAGE_SENT))
{
@@ -452,11 +353,19 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener
* Looks up the item price and changes the original message to the
* response.
*
* @param messageNode The chat message containing the command.
* @param search The item given with the command.
* @param setMessage The chat message containing the command.
* @param message The chat message
*/
private void itemPriceLookup(MessageNode messageNode, String search)
private void itemPriceLookup(SetMessage setMessage, String message)
{
if (!config.price())
{
return;
}
MessageNode messageNode = setMessage.getMessageNode();
String search = message.substring(PRICE_COMMAND_STRING.length() + 1);
List<ItemPrice> results = itemManager.search(search);
if (!results.isEmpty())
@@ -501,11 +410,25 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener
* response.
*
* @param setMessage The chat message containing the command.
* @param local HiscoreEndpoint for local player, needs to be sent in advance to avoid threading bugs
* @param search The item given with the command.
* @param message The chat message
*/
private void playerSkillLookup(SetMessage setMessage, HiscoreEndpoint local, String search)
private void playerSkillLookup(SetMessage setMessage, String message)
{
if (!config.lvl())
{
return;
}
String search;
if (message.equalsIgnoreCase(TOTAL_LEVEL_COMMAND_STRING))
{
search = "total";
}
else
{
search = message.substring(LEVEL_COMMAND_STRING.length() + 1);
}
search = SkillAbbreviations.getFullName(search);
final HiscoreSkill skill;
try
@@ -517,7 +440,7 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener
return;
}
final HiscoreLookup lookup = getCorrectLookupFor(setMessage, local);
final HiscoreLookup lookup = getCorrectLookupFor(setMessage);
try
{
@@ -558,8 +481,15 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener
}
}
private void combatLevelLookup(ChatMessageType type, SetMessage setMessage)
private void combatLevelLookup(SetMessage setMessage, String message)
{
if (!config.lvl())
{
return;
}
ChatMessageType type = setMessage.getType();
String player;
if (type == ChatMessageType.PRIVATE_MESSAGE_SENT)
{
@@ -636,18 +566,28 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener
}
}
/**
* Looks up the quantities of clues completed
* for the requested clue-level (no arg if requesting total)
* easy, medium, hard, elite, master
*/
private void playerClueLookup(SetMessage setMessage, HiscoreEndpoint local, String search)
private void clueLookup(SetMessage setMessage, String message)
{
final HiscoreLookup lookup = getCorrectLookupFor(setMessage, local);
if (!config.clue())
{
return;
}
String search;
if (message.equalsIgnoreCase(CLUES_COMMAND_STRING))
{
search = "total";
}
else
{
search = message.substring(CLUES_COMMAND_STRING.length() + 1);
}
try
{
final Skill hiscoreSkill;
final HiscoreLookup lookup = getCorrectLookupFor(setMessage);
final HiscoreResult result = hiscoreClient.lookup(lookup.getName(), lookup.getEndpoint());
if (result == null)
@@ -720,10 +660,9 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener
* Gets correct lookup data for message
*
* @param setMessage chat message
* @param local HiscoreEndpoint for local player, needs to be sent in advance to avoid threading bugs
* @return hiscore lookup data
*/
private HiscoreLookup getCorrectLookupFor(final SetMessage setMessage, final HiscoreEndpoint local)
private HiscoreLookup getCorrectLookupFor(final SetMessage setMessage)
{
final String player;
final HiscoreEndpoint ironmanStatus;
@@ -731,7 +670,7 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener
if (setMessage.getType().equals(ChatMessageType.PRIVATE_MESSAGE_SENT))
{
player = client.getLocalPlayer().getName();
ironmanStatus = local;
ironmanStatus = hiscoreEndpoint;
}
else
{
@@ -740,7 +679,7 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener
if (player.equals(client.getLocalPlayer().getName()))
{
// Get ironman status from for the local player
ironmanStatus = getHiscoreEndpointType();
ironmanStatus = hiscoreEndpoint;
}
else
{
@@ -799,7 +738,7 @@ public class ChatCommandsPlugin extends Plugin implements ChatboxInputListener
*
* @return hiscore endpoint
*/
private HiscoreEndpoint getHiscoreEndpointType()
private HiscoreEndpoint getLocalHiscoreEndpointType()
{
return toEndPoint(client.getAccountType());
}