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); + } + } +}