From 65d63cd7548370a36afcc0cd45de0d99e34f3965 Mon Sep 17 00:00:00 2001 From: Deon Zhao Date: Tue, 24 Mar 2020 17:56:48 -0700 Subject: [PATCH] loottracker: Track herbiboar loot with an open herb sack When looting the herbiboar with an open herb sack, instead of receiving the herbs to your inventory, the herbs are deposited to the sack directly and the player receives chat messages indicating the herbs they received. This commit adds support for reading those chat messages when looting the herbiboar. This change incidentally fixes an issue which arose when the open herb sack was introduced to the game where any inventory change after looting the herbiboar would be tracked as herbiboar loot, such as drinking a stamina potion. Closes #10655 Fixes #10718 --- .../loottracker/LootTrackerPlugin.java | 39 ++++++++- .../loottracker/LootTrackerPluginTest.java | 85 ++++++++++++++++++- 2 files changed, 121 insertions(+), 3 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java index ae0237939a..afad2d6af8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java @@ -63,6 +63,7 @@ import net.runelite.api.GameState; import net.runelite.api.InventoryID; import net.runelite.api.ItemComposition; import net.runelite.api.ItemContainer; +import net.runelite.api.MessageNode; import net.runelite.api.NPC; import net.runelite.api.Player; import net.runelite.api.SpriteID; @@ -116,8 +117,10 @@ public class LootTrackerPlugin extends Plugin private static final int THEATRE_OF_BLOOD_REGION = 12867; // Herbiboar loot handling - private static final String HERBIBOAR_LOOTED_MESSAGE = "You harvest herbs from the herbiboar, whereupon it escapes."; + @VisibleForTesting + static final String HERBIBOAR_LOOTED_MESSAGE = "You harvest herbs from the herbiboar, whereupon it escapes."; private static final String HERBIBOAR_EVENT = "Herbiboar"; + private static final Pattern HERBIBOAR_HERB_SACK_PATTERN = Pattern.compile(".+(Grimy .+?) herb.+"); // Hespori loot handling private static final String HESPORI_LOOTED_MESSAGE = "You have successfully cleared this patch for new crops."; @@ -500,10 +503,14 @@ public class LootTrackerPlugin extends Plugin if (message.equals(HERBIBOAR_LOOTED_MESSAGE)) { + if (processHerbiboarHerbSackLoot(event.getTimestamp())) + { + return; + } + eventType = HERBIBOAR_EVENT; lootRecordType = LootRecordType.EVENT; takeInventorySnapshot(); - return; } @@ -677,6 +684,34 @@ public class LootTrackerPlugin extends Plugin } } + private boolean processHerbiboarHerbSackLoot(int timestamp) + { + List herbs = new ArrayList<>(); + + for (MessageNode messageNode : client.getMessages()) + { + if (messageNode.getTimestamp() != timestamp + || messageNode.getType() != ChatMessageType.SPAM) + { + continue; + } + + Matcher matcher = HERBIBOAR_HERB_SACK_PATTERN.matcher(messageNode.getValue()); + if (matcher.matches()) + { + herbs.add(new ItemStack(itemManager.search(matcher.group(1)).get(0).getId(), 1, client.getLocalPlayer().getLocalLocation())); + } + } + + if (herbs.isEmpty()) + { + return false; + } + + addLoot(HERBIBOAR_EVENT, -1, LootRecordType.EVENT, herbs); + return true; + } + void toggleItem(String name, boolean ignore) { final Set ignoredItemSet = new HashSet<>(ignoredItems); diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java index 4cb4e18a8f..2f30c315fc 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java @@ -24,32 +24,66 @@ */ package net.runelite.client.plugins.loottracker; +import com.google.common.collect.ImmutableMap; import com.google.inject.Guice; import com.google.inject.testing.fieldbinder.Bind; import com.google.inject.testing.fieldbinder.BoundFieldModule; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import javax.inject.Inject; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; +import net.runelite.api.ItemID; +import net.runelite.api.IterableHashTable; +import net.runelite.api.MessageNode; import net.runelite.api.Player; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.ChatMessage; import net.runelite.client.account.SessionManager; +import net.runelite.client.game.ItemManager; +import net.runelite.client.game.ItemStack; import net.runelite.client.game.SpriteManager; import net.runelite.client.ui.overlay.infobox.InfoBoxManager; +import net.runelite.http.api.item.ItemPrice; import net.runelite.http.api.loottracker.LootRecordType; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import org.mockito.Mock; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class LootTrackerPluginTest { + private static final Map HERB_IDS_TO_NAMES = ImmutableMap.builder() + .put(ItemID.GRIMY_GUAM_LEAF, "Grimy guam leaf") + .put(ItemID.GRIMY_MARRENTILL, "Grimy marrentill") + .put(ItemID.GRIMY_TARROMIN, "Grimy tarromin") + .put(ItemID.GRIMY_HARRALANDER, "Grimy harralander") + .put(ItemID.GRIMY_RANARR_WEED, "Grimy ranarr weed") + .put(ItemID.GRIMY_IRIT_LEAF, "Grimy irit leaf") + .put(ItemID.GRIMY_AVANTOE, "Grimy avantoe") + .put(ItemID.GRIMY_KWUARM, "Grimy kwuarm") + .put(ItemID.GRIMY_SNAPDRAGON, "Grimy snapdragon") + .put(ItemID.GRIMY_CADANTINE, "Grimy cadantine") + .put(ItemID.GRIMY_LANTADYME, "Grimy lantadyme") + .put(ItemID.GRIMY_DWARF_WEED, "Grimy dwarf weed") + .put(ItemID.GRIMY_TORSTOL, "Grimy torstol") + .build(); + @Mock @Bind private ScheduledExecutorService scheduledExecutorService; @@ -77,6 +111,10 @@ public class LootTrackerPluginTest @Bind private SessionManager sessionManager; + @Mock + @Bind + private ItemManager itemManager; + @Before public void setUp() { @@ -106,4 +144,49 @@ public class LootTrackerPluginTest assertEquals("Clue Scroll (Master)", lootTrackerPlugin.eventType); assertEquals(LootRecordType.EVENT, lootTrackerPlugin.lootRecordType); } -} \ No newline at end of file + + @Test + public void testHerbiboarHerbSack() + { + for (Map.Entry herb : HERB_IDS_TO_NAMES.entrySet()) + { + final int id = herb.getKey(); + final String name = herb.getValue(); + final String herbMessage = String.format("You put the %s herb into your herb sack.", name); + final String herbFullMessage = String.format("Your herb sack is too full to hold the %s herb.", name); + + final ItemPrice herbPrice = new ItemPrice(); + herbPrice.setId(id); + herbPrice.setName(name); + when(itemManager.search(name)).thenReturn(Collections.singletonList(herbPrice)); + + MessageNode node = mock(MessageNode.class); + when(node.getType()).thenReturn(ChatMessageType.SPAM); + when(node.getValue()).thenReturn(herbMessage); + + MessageNode nodeFull = mock(MessageNode.class); + when(nodeFull.getType()).thenReturn(ChatMessageType.SPAM); + when(nodeFull.getValue()).thenReturn(herbFullMessage); + + IterableHashTable messageTable = mock(IterableHashTable.class); + Iterator mockIterator = mock(Iterator.class); + when(mockIterator.hasNext()).thenReturn(true, true, false); + when(mockIterator.next()).thenReturn(node).thenReturn(nodeFull); + when(messageTable.iterator()).thenReturn(mockIterator); + when(client.getMessages()).thenReturn(messageTable); + + LootTrackerPlugin lootTrackerPluginSpy = spy(this.lootTrackerPlugin); + doNothing().when(lootTrackerPluginSpy).addLoot(any(), anyInt(), any(), any(Collection.class)); + + ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", LootTrackerPlugin.HERBIBOAR_LOOTED_MESSAGE, "", 0); + lootTrackerPluginSpy.onChatMessage(chatMessage); + + verify(lootTrackerPluginSpy).addLoot("Herbiboar", -1, LootRecordType.EVENT, Arrays.asList( + new ItemStack(id, 1, null), + new ItemStack(id, 1, null) + )); + // Check the event type is null, which means the plugin isn't waiting on an inventory change event + assertNull(lootTrackerPlugin.eventType); + } + } +}