chat commands: add pets command

Co-authored-by: Illya Myshakov <imyshako@uwaterloo.ca>
This commit is contained in:
Adam
2021-07-16 16:43:17 -04:00
committed by Adam
parent 3115142853
commit 21a7bf4906
9 changed files with 611 additions and 2 deletions

View File

@@ -179,6 +179,18 @@ public interface ChatCommandsConfig extends Config
@ConfigItem(
position = 13,
keyName = "pets",
name = "Pets Command",
description = "Configures whether the player pet list command is enabled<br> !pets<br>" +
" Note: Update your pet list by looking at the All Pets tab in the Collection Log"
)
default boolean pets()
{
return true;
}
@ConfigItem(
position = 20,
keyName = "clearSingleWord",
name = "Clear Single Word",
description = "Enable hot key to clear single word at a time"
@@ -189,7 +201,7 @@ public interface ChatCommandsConfig extends Config
}
@ConfigItem(
position = 14,
position = 21,
keyName = "clearEntireChatBox",
name = "Clear Chat Box",
description = "Enable hotkey to clear entire chat box"

View File

@@ -28,14 +28,23 @@ package net.runelite.client.plugins.chatcommands;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import com.google.inject.Provides;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.inject.Inject;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
@@ -43,6 +52,7 @@ import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.Experience;
import net.runelite.api.IconID;
import net.runelite.api.IndexedSprite;
import net.runelite.api.ItemComposition;
import net.runelite.api.MessageNode;
import net.runelite.api.Player;
@@ -57,9 +67,11 @@ 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.ADVENTURE_LOG_ID;
import static net.runelite.api.widgets.WidgetID.COLLECTION_LOG_ID;
import static net.runelite.api.widgets.WidgetID.GENERIC_SCROLL_GROUP_ID;
import static net.runelite.api.widgets.WidgetID.KILL_LOGS_GROUP_ID;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.chat.ChatColorType;
import net.runelite.client.chat.ChatCommandManager;
import net.runelite.client.chat.ChatMessageBuilder;
@@ -72,6 +84,7 @@ import net.runelite.client.game.ItemManager;
import net.runelite.client.input.KeyManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.QuantityFormatter;
import net.runelite.client.util.Text;
import net.runelite.http.api.chat.ChatClient;
@@ -112,6 +125,7 @@ public class ChatCommandsPlugin extends Plugin
"(?:<br>Overall time: <col=ff0000>(?<otime>[0-9:]+(?:\\.[0-9]+)?)</col>(?: \\(new personal best\\)|. Personal best: (?<opb>[0-9:]+(?:\\.[0-9]+)?)))?");
private static final Pattern HS_KC_FLOOR_PATTERN = Pattern.compile("You have completed Floor (\\d) of the Hallowed Sepulchre! Total completions: <col=ff0000>([0-9,]+)</col>\\.");
private static final Pattern HS_KC_GHC_PATTERN = Pattern.compile("You have opened the Grand Hallowed Coffin <col=ff0000>([0-9,]+)</col> times?!");
private static final Pattern COLLECTION_LOG_ITEM_PATTERN = Pattern.compile("New item added to your collection log: (.*)");
private static final String TOTAL_LEVEL_COMMAND_STRING = "!total";
private static final String PRICE_COMMAND_STRING = "!price";
@@ -128,9 +142,11 @@ public class ChatCommandsPlugin extends Plugin
private static final String DUEL_ARENA_COMMAND = "!duels";
private static final String LEAGUE_POINTS_COMMAND = "!lp";
private static final String SOUL_WARS_ZEAL_COMMAND = "!sw";
private static final String PET_LIST_COMMAND = "!pets";
@VisibleForTesting
static final int ADV_LOG_EXPLOITS_TEXT_INDEX = 1;
static final int COL_LOG_ENTRY_HEADER_TITLE_INDEX = 0;
private static final Map<String, String> KILLCOUNT_RENAMES = ImmutableMap.of(
"Barrows chest", "Barrows Chests"
@@ -139,15 +155,20 @@ public class ChatCommandsPlugin extends Plugin
private boolean bossLogLoaded;
private boolean advLogLoaded;
private boolean scrollInterfaceLoaded;
private boolean collectionLogLoaded;
private String pohOwner;
private HiscoreEndpoint hiscoreEndpoint; // hiscore endpoint for current player
private String lastBossKill;
private int lastBossTime = -1;
private double lastPb = -1;
private int modIconIdx = -1;
@Inject
private Client client;
@Inject
private ClientThread clientThread;
@Inject
private ChatCommandsConfig config;
@@ -181,6 +202,9 @@ public class ChatCommandsPlugin extends Plugin
@Inject
private RuneLiteConfig runeLiteConfig;
@Inject
private Gson gson;
@Override
public void startUp()
{
@@ -201,6 +225,9 @@ public class ChatCommandsPlugin extends Plugin
chatCommandManager.registerCommandAsync(GC_COMMAND_STRING, this::gambleCountLookup, this::gambleCountSubmit);
chatCommandManager.registerCommandAsync(DUEL_ARENA_COMMAND, this::duelArenaLookup, this::duelArenaSubmit);
chatCommandManager.registerCommandAsync(SOUL_WARS_ZEAL_COMMAND, this::soulWarsZealLookup);
chatCommandManager.registerCommandAsync(PET_LIST_COMMAND, this::petListLookup, this::petListSubmit);
clientThread.invoke(this::loadPetIcons);
}
@Override
@@ -226,6 +253,7 @@ public class ChatCommandsPlugin extends Plugin
chatCommandManager.unregisterCommand(GC_COMMAND_STRING);
chatCommandManager.unregisterCommand(DUEL_ARENA_COMMAND);
chatCommandManager.unregisterCommand(SOUL_WARS_ZEAL_COMMAND);
chatCommandManager.unregisterCommand(PET_LIST_COMMAND);
}
@Provides
@@ -272,6 +300,69 @@ public class ChatCommandsPlugin extends Plugin
return personalBest == null ? 0 : personalBest;
}
private void loadPetIcons()
{
final IndexedSprite[] modIcons = client.getModIcons();
if (modIconIdx != -1 || modIcons == null)
{
return;
}
final Pet[] pets = Pet.values();
final IndexedSprite[] newModIcons = Arrays.copyOf(modIcons, modIcons.length + pets.length);
modIconIdx = modIcons.length;
for (int i = 0; i < pets.length; i++)
{
final Pet pet = pets[i];
final BufferedImage image = ImageUtil.resizeImage(itemManager.getImage(pet.getIconID()), 18, 16);
final IndexedSprite sprite = ImageUtil.getImageIndexedSprite(image, client);
newModIcons[modIconIdx + i] = sprite;
}
client.setModIcons(newModIcons);
}
/**
* Sets the list of owned pets for the local player
*
* @param petList The total list of owned pets for the local player
*/
private void setPetList(List<Pet> petList)
{
if (petList == null)
{
return;
}
configManager.setRSProfileConfiguration("chatcommands", "pets",
gson.toJson(petList));
}
/**
* Looks up the list of owned pets for the local player
*/
private List<Pet> getPetList()
{
String petListJson = configManager.getRSProfileConfiguration("chatcommands", "pets",
String.class);
List<Pet> petList;
try
{
// CHECKSTYLE:OFF
petList = gson.fromJson(petListJson, new TypeToken<List<Pet>>(){}.getType());
// CHECKSTYLE:ON
}
catch (JsonSyntaxException ex)
{
return Collections.emptyList();
}
return petList != null ? petList : Collections.emptyList();
}
@Subscribe
public void onChatMessage(ChatMessage chatMessage)
{
@@ -431,6 +522,24 @@ public class ChatCommandsPlugin extends Plugin
lastBossKill = null;
lastBossTime = -1;
}
matcher = COLLECTION_LOG_ITEM_PATTERN.matcher(message);
if (matcher.find())
{
String item = matcher.group(1);
Pet pet = Pet.findPet(item);
if (pet != null)
{
List<Pet> petList = new ArrayList<>(getPetList());
if (!petList.contains(pet))
{
log.debug("New pet added: {}", pet);
petList.add(pet);
setPetList(petList);
}
}
}
}
@VisibleForTesting
@@ -490,6 +599,39 @@ public class ChatCommandsPlugin extends Plugin
}
}
if (collectionLogLoaded && (pohOwner == null || pohOwner.equals(client.getLocalPlayer().getName())))
{
collectionLogLoaded = false;
Widget collectionLogEntryHeader = client.getWidget(WidgetInfo.COLLECTION_LOG_ENTRY_HEADER);
if (collectionLogEntryHeader != null && collectionLogEntryHeader.getChildren() != null)
{
Widget entryTitle = collectionLogEntryHeader.getChild(COL_LOG_ENTRY_HEADER_TITLE_INDEX);
// Make sure that the player is looking in the All Pets tab of the collection log
if (entryTitle.getText().equals("All Pets"))
{
Widget collectionLogEntryItems = client.getWidget(WidgetInfo.COLLECTION_LOG_ENTRY_ITEMS);
if (collectionLogEntryItems != null && collectionLogEntryItems.getChildren() != null)
{
List<Pet> petList = new ArrayList<>();
for (Widget child : collectionLogEntryItems.getChildren())
{
if (child.getOpacity() == 0)
{
Pet pet = Pet.findPet(Text.removeTags(child.getName()));
if (pet != null)
{
petList.add(pet);
}
}
}
setPetList(petList);
}
}
}
}
if (bossLogLoaded && (pohOwner == null || pohOwner.equals(client.getLocalPlayer().getName())))
{
bossLogLoaded = false;
@@ -566,6 +708,9 @@ public class ChatCommandsPlugin extends Plugin
case ADVENTURE_LOG_ID:
advLogLoaded = true;
break;
case COLLECTION_LOG_ID:
collectionLogLoaded = true;
break;
case KILL_LOGS_GROUP_ID:
bossLogLoaded = true;
break;
@@ -583,6 +728,10 @@ public class ChatCommandsPlugin extends Plugin
case LOADING:
case HOPPING:
pohOwner = null;
break;
case LOGGED_IN:
loadPetIcons();
break;
}
}
@@ -999,6 +1148,103 @@ public class ChatCommandsPlugin extends Plugin
return true;
}
/**
* Looks up the pet list for the player who triggered !pets
*
* @param chatMessage The chat message containing the command.
* @param message The chat message in string format
* <p>
*/
private void petListLookup(ChatMessage chatMessage, String message)
{
if (!config.pets())
{
return;
}
ChatMessageType type = chatMessage.getType();
final String player;
if (type.equals(ChatMessageType.PRIVATECHATOUT))
{
player = client.getLocalPlayer().getName();
}
else
{
player = Text.sanitize(chatMessage.getName());
}
Set<Integer> playerPetList;
try
{
playerPetList = chatClient.getPetList(player);
}
catch (IOException ex)
{
log.debug("unable to lookup pet list", ex);
if (player.equals(client.getLocalPlayer().getName()))
{
String response = "Open the 'All Pets' tab in the Collection Log to update your pet list";
log.debug("Setting response {}", response);
final MessageNode messageNode = chatMessage.getMessageNode();
messageNode.setValue(response);
client.refreshChat();
}
return;
}
ChatMessageBuilder responseBuilder = new ChatMessageBuilder().append("Pets: ")
.append("(" + playerPetList.size() + ")");
// Append pets that the player owns
Pet[] pets = Pet.values();
for (Pet pet : pets)
{
if (playerPetList.contains(pet.getIconID()))
{
responseBuilder.append(" ").img(modIconIdx + pet.ordinal());
}
}
String response = responseBuilder.build();
log.debug("Setting response {}", response);
final MessageNode messageNode = chatMessage.getMessageNode();
messageNode.setValue(response);
client.refreshChat();
}
/**
* Submits the pet list for the local player
*
* @param chatInput The chat message containing the command.
* @param value The chat message
*/
private boolean petListSubmit(ChatInput chatInput, String value)
{
final String playerName = client.getLocalPlayer().getName();
executor.execute(() ->
{
try
{
List<Integer> petList = getPetList().stream().map(Pet::getIconID).collect(Collectors.toList());
chatClient.submitPetList(playerName, petList);
}
catch (Exception ex)
{
log.warn("unable to submit pet list", ex);
}
finally
{
chatInput.resume();
}
});
return true;
}
/**
* Looks up the item price and changes the original message to the
* response.

View File

@@ -0,0 +1,98 @@
/*
* Copyright (c) 2021, Illya Myshakov <https://github.com/IllyaMyshakov>
* 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.chatcommands;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.runelite.api.ItemID;
@AllArgsConstructor
@Getter
enum Pet
{
ABYSSAL_ORPHAN("Abyssal orphan", ItemID.ABYSSAL_ORPHAN),
IKKLE_HYDRA("Ikkle hydra", ItemID.IKKLE_HYDRA),
CALLISTO_CUB("Callisto cub", ItemID.CALLISTO_CUB),
HELLPUPPY("Hellpuppy", ItemID.HELLPUPPY),
PET_CHAOS_ELEMENTAL("Pet chaos elemental", ItemID.PET_CHAOS_ELEMENTAL),
PET_ZILYANA("Pet zilyana", ItemID.PET_ZILYANA),
PET_DARK_CORE("Pet dark core", ItemID.PET_DARK_CORE),
PET_DAGANNOTH_PRIME("Pet dagannoth prime", ItemID.PET_DAGANNOTH_PRIME),
PET_DAGANNOTH_SUPREME("Pet dagannoth supreme", ItemID.PET_DAGANNOTH_SUPREME),
PET_DAGANNOTH_REX("Pet dagannoth rex", ItemID.PET_DAGANNOTH_REX),
TZREKJAD("Tzrek-jad", ItemID.TZREKJAD),
PET_GENERAL_GRAARDOR("Pet general graardor", ItemID.PET_GENERAL_GRAARDOR),
BABY_MOLE("Baby mole", ItemID.BABY_MOLE),
NOON("Noon", ItemID.NOON),
JALNIBREK("Jal-nib-rek", ItemID.JALNIBREK),
KALPHITE_PRINCESS("Kalphite princess", ItemID.KALPHITE_PRINCESS),
PRINCE_BLACK_DRAGON("Prince black dragon", ItemID.PRINCE_BLACK_DRAGON),
PET_KRAKEN("Pet kraken", ItemID.PET_KRAKEN),
PET_KREEARRA("Pet kree'arra", ItemID.PET_KREEARRA),
PET_KRIL_TSUTSAROTH("Pet k'ril tsutsaroth", ItemID.PET_KRIL_TSUTSAROTH),
SCORPIAS_OFFSPRING("Scorpia's offspring", ItemID.SCORPIAS_OFFSPRING),
SKOTOS("Skotos", ItemID.SKOTOS),
PET_SMOKE_DEVIL("Pet smoke devil", ItemID.PET_SMOKE_DEVIL),
VENENATIS_SPIDERLING("Venenatis spiderling", ItemID.VENENATIS_SPIDERLING),
VETION_JR("Vet'ion jr.", ItemID.VETION_JR),
VORKI("Vorki", ItemID.VORKI),
PHOENIX("Phoenix", ItemID.PHOENIX),
PET_SNAKELING("Pet snakeling", ItemID.PET_SNAKELING),
OLMLET("Olmlet", ItemID.OLMLET),
LIL_ZIK("Lil' zik", ItemID.LIL_ZIK),
BLOODHOUND("Bloodhound", ItemID.BLOODHOUND),
PET_PENANCE_QUEEN("Pet penance queen", ItemID.PET_PENANCE_QUEEN),
HERON("Heron", ItemID.HERON),
ROCK_GOLEM("Rock golem", ItemID.ROCK_GOLEM),
BEAVER("Beaver", ItemID.BEAVER),
BABY_CHINCHOMPA("Baby chinchompa", ItemID.BABY_CHINCHOMPA),
GIANT_SQUIRREL("Giant squirrel", ItemID.GIANT_SQUIRREL),
TANGLEROOT("Tangleroot", ItemID.TANGLEROOT),
ROCKY("Rocky", ItemID.ROCKY),
RIFT_GUARDIAN("Rift guardian", ItemID.RIFT_GUARDIAN),
HERBI("Herbi", ItemID.HERBI),
CHOMPY_CHICK("Chompy chick", ItemID.CHOMPY_CHICK),
SRARACHA("Sraracha", ItemID.SRARACHA),
SMOLCANO("Smolcano", ItemID.SMOLCANO),
YOUNGLLEF("Youngllef", ItemID.YOUNGLLEF),
LITTLE_NIGHTMARE("Little nightmare", ItemID.LITTLE_NIGHTMARE),
LIL_CREATOR("Lil' creator", ItemID.LIL_CREATOR),
TINY_TEMPOR("Tiny tempor", ItemID.TINY_TEMPOR);
private final String name;
private final Integer iconID;
static Pet findPet(String petName)
{
for (Pet pet : values())
{
if (pet.name.equals(petName))
{
return pet;
}
}
return null;
}
}