diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 05cef0b42b..75e56a54aa 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -25,9 +25,9 @@ object ProjectVersions { const val launcherVersion = "2.2.0" - const val rlVersion = "1.8.13" + const val rlVersion = "1.8.13.1" - const val openosrsVersion = "4.20.3" + const val openosrsVersion = "4.20.4" const val rsversion = 203 const val cacheversion = 165 diff --git a/runelite-api/src/main/java/net/runelite/api/InventoryID.java b/runelite-api/src/main/java/net/runelite/api/InventoryID.java index 95f41af65a..37642dbbd4 100644 --- a/runelite-api/src/main/java/net/runelite/api/InventoryID.java +++ b/runelite-api/src/main/java/net/runelite/api/InventoryID.java @@ -96,7 +96,11 @@ public enum InventoryID /** * Player inventory when accessing group ironman shared storage */ - GROUP_STORAGE_INV(660); + GROUP_STORAGE_INV(660), + /** + * Wilderness loot chest + */ + WILDERNESS_LOOT_CHEST(797); private final int id; diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java index 9cc1b33962..617f5be107 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java @@ -187,6 +187,7 @@ public final class WidgetID public static final int GROUP_IRON_GROUP_ID = 726; public static final int GROUP_STORAGE_INVENTORY_GROUP_ID = 725; public static final int GROUP_STORAGE_GROUP_ID = 724; + public static final int WILDERNESS_LOOT_CHEST = 742; static class WorldMap { diff --git a/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java b/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java index 91d7c99683..c212b8fee5 100644 --- a/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java +++ b/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java @@ -164,9 +164,16 @@ public class ChatMessageManager stringStack[size - 4] = ColorUtil.wrapWithColorTag(channel, channelColor); } + String prefix = ""; + if (chatMessageType == ChatMessageType.CLAN_GIM_CHAT || chatMessageType == ChatMessageType.CLAN_GIM_MESSAGE) + { + message = message.substring(1); // remove | + prefix = "|"; + } + if (messageNode.getRuneLiteFormatMessage() != null) { - stringStack[size - 2] = message = formatRuneLiteMessage(messageNode.getRuneLiteFormatMessage(), + message = formatRuneLiteMessage(messageNode.getRuneLiteFormatMessage(), chatMessageType, splitpmbox); } @@ -178,20 +185,15 @@ public class ChatMessageManager continue; } - String prefix = ""; - if (chatMessageType == ChatMessageType.CLAN_GIM_CHAT || chatMessageType == ChatMessageType.CLAN_GIM_MESSAGE) - { - message = message.substring(1); // remove | - prefix = "|"; - } - // Replace tags in the message with the new color so embedded won't reset the color final Color color = chatColor.getColor(); - stringStack[size - 2] = prefix + ColorUtil.wrapWithColorTag( + message = ColorUtil.wrapWithColorTag( message.replace(ColorUtil.CLOSING_COLOR_TAG, ColorUtil.colorTag(color)), color); break; } + + stringStack[size - 2] = prefix + message; } @Subscribe diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java index fdb07d1e6e..77efae681b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java @@ -353,8 +353,6 @@ public class GpuPlugin extends Plugin implements DrawCallbacks System.setProperty("jogl.debug", "true"); } - System.setProperty("jogamp.gluegen.UseNativeExeFile", "true"); - GLProfile.initSingleton(); invokeOnMainThread(() -> diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootReceived.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootReceived.java index bba5363988..2d95de301a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootReceived.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootReceived.java @@ -41,4 +41,5 @@ public class LootReceived private int combatLevel; private LootRecordType type; private Collection items; + private int amount; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java index 36d784c182..2706162318 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java @@ -242,8 +242,7 @@ class LootTrackerBox extends JPanel subTitleLabel.setToolTipText(QuantityFormatter.formatNumber(totalPrice / kills) + " gp (average)"); } - validate(); - repaint(); + revalidate(); } void collapse() @@ -368,7 +367,7 @@ class LootTrackerBox extends JPanel itemContainer.add(slotContainer); } - itemContainer.repaint(); + itemContainer.revalidate(); } private static String buildToolTip(LootTrackerItem item) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerConfig.java index 6e1b9f27ed..5c10067ccf 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerConfig.java @@ -81,6 +81,16 @@ public interface LootTrackerConfig extends Config return false; } + @ConfigItem( + keyName = "syncPanel", + name = "Remember loot", + description = "Saves loot between client sessions" + ) + default boolean syncPanel() + { + return true; + } + @ConfigItem( keyName = "ignoredEvents", name = "Ignored Loot Sources", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java index d08157ce99..9b5cb7dd9b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java @@ -323,7 +323,7 @@ class LootTrackerPanel extends PluginPanel boxes.removeIf(b -> b.matches(currentView, currentType)); updateOverall(); logsContainer.removeAll(); - logsContainer.repaint(); + logsContainer.revalidate(); // Delete all loot, or loot matching the current view if (currentView != null) @@ -368,7 +368,7 @@ class LootTrackerPanel extends PluginPanel * Creates a subtitle, adds a new entry and then passes off to the render methods, that will decide * how to display this new data. */ - void add(final String eventName, final LootRecordType type, final int actorLevel, LootTrackerItem[] items) + void add(final String eventName, final LootRecordType type, final int actorLevel, LootTrackerItem[] items, int kills) { final String subTitle; if (type == LootRecordType.PICKPOCKET) @@ -379,7 +379,7 @@ class LootTrackerPanel extends PluginPanel { subTitle = actorLevel > -1 ? "(lvl-" + actorLevel + ")" : ""; } - final LootTrackerRecord record = new LootTrackerRecord(eventName, subTitle, type, items, 1); + final LootTrackerRecord record = new LootTrackerRecord(eventName, subTitle, type, items, kills); sessionRecords.add(record); if (hideIgnoredItems && plugin.isEventIgnored(eventName)) @@ -505,7 +505,6 @@ class LootTrackerPanel extends PluginPanel boxes.forEach(LootTrackerBox::rebuild); updateOverall(); logsContainer.revalidate(); - logsContainer.repaint(); } /** @@ -606,7 +605,7 @@ class LootTrackerPanel extends PluginPanel boxes.remove(box); updateOverall(); logsContainer.remove(box); - logsContainer.repaint(); + logsContainer.revalidate(); // Without loot being grouped we have no way to identify single kills to be deleted if (groupLoot) 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 726e74d7ac..8c10a2dbd4 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 @@ -263,6 +263,23 @@ public class LootTrackerPlugin extends Plugin // Mahogany Homes private static final String MAHOGANY_CRATE_EVENT = "Supply crate (Mahogany Homes)"; + // Implings + private static final Set IMPLING_JARS = ImmutableSet.of( + ItemID.BABY_IMPLING_JAR, + ItemID.YOUNG_IMPLING_JAR, + ItemID.GOURMET_IMPLING_JAR, + ItemID.EARTH_IMPLING_JAR, + ItemID.ESSENCE_IMPLING_JAR, + ItemID.ECLECTIC_IMPLING_JAR, + ItemID.NATURE_IMPLING_JAR, + ItemID.MAGPIE_IMPLING_JAR, + ItemID.NINJA_IMPLING_JAR, + ItemID.CRYSTAL_IMPLING_JAR, + ItemID.DRAGON_IMPLING_JAR, + ItemID.LUCKY_IMPLING_JAR + ); + private static final String IMPLING_CATCH_MESSAGE = "You manage to catch the impling and acquire some loot."; + private static final Set VOWELS = ImmutableSet.of('a', 'e', 'i', 'o', 'u'); @Inject @@ -304,24 +321,23 @@ public class LootTrackerPlugin extends Plugin @Inject private Gson gson; + @Getter(AccessLevel.PACKAGE) + @Inject + private LootTrackerClient lootTrackerClient; + private LootTrackerPanel panel; private NavigationButton navButton; - @VisibleForTesting - String eventType; - @VisibleForTesting - LootRecordType lootRecordType; - private Object metadata; + private boolean chestLooted; private String lastPickpocketTarget; private List ignoredItems = new ArrayList<>(); private List ignoredEvents = new ArrayList<>(); + private InventoryID inventoryId; private Multiset inventorySnapshot; + private InvChangeCallback inventorySnapshotCb; - @Getter(AccessLevel.PACKAGE) - @Inject - private LootTrackerClient lootTrackerClient; private final List queuedLoots = new ArrayList<>(); private String profileKey; private Instant lastLootImport = Instant.now().minus(1, ChronoUnit.MINUTES); @@ -396,7 +412,6 @@ public class LootTrackerPlugin extends Plugin return; } - log.debug("Profile changed to {}", profileKey); switchProfile(profileKey); } @@ -411,6 +426,11 @@ public class LootTrackerPlugin extends Plugin log.debug("Switched to profile {}", profileKey); + if (!config.syncPanel()) + { + return; + } + int drops = 0; List loots = new ArrayList<>(); Instant old = Instant.now().minus(MAX_AGE); @@ -552,9 +572,14 @@ public class LootTrackerPlugin extends Plugin } void addLoot(@NonNull String name, int combatLevel, LootRecordType type, Object metadata, Collection items) + { + addLoot(name, combatLevel, type, metadata, items, 1); + } + + void addLoot(@NonNull String name, int combatLevel, LootRecordType type, Object metadata, Collection items, int amount) { final LootTrackerItem[] entries = buildEntries(stack(items)); - SwingUtilities.invokeLater(() -> panel.add(name, type, combatLevel, entries)); + SwingUtilities.invokeLater(() -> panel.add(name, type, combatLevel, entries, amount)); LootRecord lootRecord = new LootRecord(name, type, metadata, toGameItems(items), Instant.now(), getLootWorldId()); synchronized (queuedLoots) @@ -562,7 +587,7 @@ public class LootTrackerPlugin extends Plugin queuedLoots.add(lootRecord); } - eventBus.post(new LootReceived(name, combatLevel, type, items)); + eventBus.post(new LootReceived(name, combatLevel, type, items, amount)); } private Integer getLootWorldId() @@ -616,12 +641,14 @@ public class LootTrackerPlugin extends Plugin @Subscribe public void onWidgetLoaded(WidgetLoaded widgetLoaded) { + String event; + Object metadata = null; final ItemContainer container; switch (widgetLoaded.getGroupId()) { case (WidgetID.BARROWS_REWARD_GROUP_ID): - setEvent(LootRecordType.EVENT, "Barrows"); + event = "Barrows"; container = client.getItemContainer(InventoryID.BARROWS_REWARD); break; case (WidgetID.CHAMBERS_OF_XERIC_REWARD_GROUP_ID): @@ -629,7 +656,7 @@ public class LootTrackerPlugin extends Plugin { return; } - setEvent(LootRecordType.EVENT, "Chambers of Xeric"); + event = "Chambers of Xeric"; container = client.getItemContainer(InventoryID.CHAMBERS_OF_XERIC_CHEST); chestLooted = true; break; @@ -643,33 +670,33 @@ public class LootTrackerPlugin extends Plugin { return; } - setEvent(LootRecordType.EVENT, "Theatre of Blood"); + event = "Theatre of Blood"; container = client.getItemContainer(InventoryID.THEATRE_OF_BLOOD_CHEST); chestLooted = true; break; - case (WidgetID.CLUE_SCROLL_REWARD_GROUP_ID): - // event type should be set via ChatMessage for clue scrolls. - // Clue Scrolls use same InventoryID as Barrows - container = client.getItemContainer(InventoryID.BARROWS_REWARD); - - if (eventType == null) - { - log.debug("Clue scroll reward interface with no event!"); - return; - } - break; case (WidgetID.KINGDOM_GROUP_ID): - setEvent(LootRecordType.EVENT, "Kingdom of Miscellania"); + event = "Kingdom of Miscellania"; container = client.getItemContainer(InventoryID.KINGDOM_OF_MISCELLANIA); break; case (WidgetID.FISHING_TRAWLER_REWARD_GROUP_ID): - setEvent(LootRecordType.EVENT, "Fishing Trawler", client.getBoostedSkillLevel(Skill.FISHING)); + event = "Fishing Trawler"; + metadata = client.getBoostedSkillLevel(Skill.FISHING); container = client.getItemContainer(InventoryID.FISHING_TRAWLER_REWARD); break; case (WidgetID.DRIFT_NET_FISHING_REWARD_GROUP_ID): - setEvent(LootRecordType.EVENT, "Drift Net", client.getBoostedSkillLevel(Skill.FISHING)); + event = "Drift Net"; + metadata = client.getBoostedSkillLevel(Skill.FISHING); container = client.getItemContainer(InventoryID.DRIFT_NET_FISHING_REWARD); break; + case WidgetID.WILDERNESS_LOOT_CHEST: + if (chestLooted) + { + return; + } + event = "Loot Chest"; + container = client.getItemContainer(InventoryID.WILDERNESS_LOOT_CHEST); + chestLooted = true; + break; default: return; } @@ -685,13 +712,13 @@ public class LootTrackerPlugin extends Plugin .map(item -> new ItemStack(item.getId(), item.getQuantity(), client.getLocalPlayer().getLocalLocation())) .collect(Collectors.toList()); - if (config.showRaidsLootValue() && (eventType.equals("Theatre of Blood") || eventType.equals("Chambers of Xeric"))) + if (config.showRaidsLootValue() && (event.equals("Theatre of Blood") || event.equals("Chambers of Xeric"))) { long totalValue = items.stream() .filter(item -> item.getId() > -1) - .mapToLong(item -> (long) (config.priceType() == LootTrackerPriceType.GRAND_EXCHANGE ? - itemManager.getItemPrice(item.getId()) * item.getQuantity() : - itemManager.getItemComposition(item.getId()).getHaPrice() * item.getQuantity())) + .mapToLong(item -> config.priceType() == LootTrackerPriceType.GRAND_EXCHANGE ? + (long) itemManager.getItemPrice(item.getId()) * item.getQuantity() : + (long) itemManager.getItemComposition(item.getId()).getHaPrice() * item.getQuantity()) .sum(); String chatMessage = new ChatMessageBuilder() @@ -711,11 +738,11 @@ public class LootTrackerPlugin extends Plugin if (items.isEmpty()) { - log.debug("No items to find for Event: {} | Container: {}", eventType, container); + log.debug("No items to find for Event: {} | Container: {}", event, container); return; } - addLoot(eventType, -1, lootRecordType, metadata, items); + addLoot(event, -1, LootRecordType.EVENT, metadata, items); } @Subscribe @@ -738,17 +765,14 @@ public class LootTrackerPlugin extends Plugin return; } - setEvent(LootRecordType.EVENT, CHEST_EVENT_TYPES.get(regionID)); - takeInventorySnapshot(); - + onInvChange(collectInvAndGroundItems(LootRecordType.EVENT, CHEST_EVENT_TYPES.get(regionID))); return; } if (message.equals(COFFIN_LOOTED_MESSAGE) && isPlayerWithinMapRegion(HALLOWED_SEPULCHRE_MAP_REGIONS)) { - setEvent(LootRecordType.EVENT, HALLOWED_SEPULCHRE_COFFIN_EVENT); - takeInventorySnapshot(); + onInvChange(collectInvAndGroundItems(LootRecordType.EVENT, HALLOWED_SEPULCHRE_COFFIN_EVENT)); return; } @@ -759,16 +783,14 @@ public class LootTrackerPlugin extends Plugin return; } - setEvent(LootRecordType.EVENT, HERBIBOAR_EVENT, client.getBoostedSkillLevel(Skill.HERBLORE)); - takeInventorySnapshot(); + onInvChange(collectInvAndGroundItems(LootRecordType.EVENT, HERBIBOAR_EVENT, client.getBoostedSkillLevel(Skill.HERBLORE))); return; } final int regionID = client.getLocalPlayer().getWorldLocation().getRegionID(); if (HESPORI_REGION == regionID && message.equals(HESPORI_LOOTED_MESSAGE)) { - setEvent(LootRecordType.EVENT, HESPORI_EVENT); - takeInventorySnapshot(); + onInvChange(collectInvAndGroundItems(LootRecordType.EVENT, HESPORI_EVENT)); return; } @@ -776,8 +798,7 @@ public class LootTrackerPlugin extends Plugin if (hamStoreroomMatcher.matches() && regionID == HAM_STOREROOM_REGION) { String keyType = hamStoreroomMatcher.group("key"); - setEvent(LootRecordType.EVENT, String.format("H.A.M. chest (%s)", keyType)); - takeInventorySnapshot(); + onInvChange(collectInvAndGroundItems(LootRecordType.EVENT, String.format("H.A.M. chest (%s)", keyType))); return; } @@ -790,14 +811,10 @@ public class LootTrackerPlugin extends Plugin // Occasional edge case where the pickpocket message doesn't list the correct name of the NPC (e.g. H.A.M. Members) if (PICKPOCKET_DISAMBIGUATION_MAP.get(lastPickpocketTarget).contains(pickpocketTarget)) { - setEvent(LootRecordType.PICKPOCKET, lastPickpocketTarget); - } - else - { - setEvent(LootRecordType.PICKPOCKET, pickpocketTarget); + pickpocketTarget = lastPickpocketTarget; } - takeInventorySnapshot(); + onInvChange(collectInvAndGroundItems(LootRecordType.PICKPOCKET, pickpocketTarget)); return; } @@ -806,27 +823,36 @@ public class LootTrackerPlugin extends Plugin if (m.find()) { final String type = m.group(1).toLowerCase(); + String eventType; switch (type) { case "beginner": - setEvent(LootRecordType.EVENT, "Clue Scroll (Beginner)"); - return; + eventType = "Clue Scroll (Beginner)"; + break; case "easy": - setEvent(LootRecordType.EVENT, "Clue Scroll (Easy)"); - return; + eventType = "Clue Scroll (Easy)"; + break; case "medium": - setEvent(LootRecordType.EVENT, "Clue Scroll (Medium)"); - return; + eventType = "Clue Scroll (Medium)"; + break; case "hard": - setEvent(LootRecordType.EVENT, "Clue Scroll (Hard)"); - return; + eventType = "Clue Scroll (Hard)"; + break; case "elite": - setEvent(LootRecordType.EVENT, "Clue Scroll (Elite)"); - return; + eventType = "Clue Scroll (Elite)"; + break; case "master": - setEvent(LootRecordType.EVENT, "Clue Scroll (Master)"); + eventType = "Clue Scroll (Master)"; + break; + default: + log.debug("Unrecognized clue type: {}", type); return; } + + // Clue Scrolls use same InventoryID as Barrows + onInvChange(InventoryID.BARROWS_REWARD, collectInvItems(LootRecordType.EVENT, eventType)); + + return; } if (SHADE_CHEST_NO_KEY_PATTERN.matcher(message).matches()) @@ -848,56 +874,64 @@ public class LootTrackerPlugin extends Plugin return; } - setEvent(LootRecordType.EVENT, type, client.getBoostedSkillLevel(Skill.HUNTER)); - takeInventorySnapshot(); + onInvChange(collectInvAndGroundItems(LootRecordType.EVENT, type, client.getBoostedSkillLevel(Skill.HUNTER))); + return; } if (regionID == TEMPOROSS_REGION && message.startsWith(TEMPOROSS_LOOT_STRING)) { - setEvent(LootRecordType.EVENT, TEMPOROSS_EVENT, client.getBoostedSkillLevel(Skill.FISHING)); - takeInventorySnapshot(); + onInvChange(collectInvItems(LootRecordType.EVENT, TEMPOROSS_EVENT, client.getBoostedSkillLevel(Skill.FISHING))); + return; + } + + if (message.equals(IMPLING_CATCH_MESSAGE)) + { + onInvChange(collectInvItems(LootRecordType.EVENT, client.getLocalPlayer().getInteracting().getName())); + return; } } @Subscribe public void onItemContainerChanged(ItemContainerChanged event) { - if (event.getContainerId() != InventoryID.INVENTORY.getId() - || eventType == null) + // when the wilderness chest empties, clear chest loot flag for the next key + if (event.getContainerId() == InventoryID.WILDERNESS_LOOT_CHEST.getId() + && Arrays.stream(event.getItemContainer().getItems()).noneMatch(i -> i.getId() > -1)) + { + log.debug("Resetting chest loot flag"); + chestLooted = false; + } + + if (inventoryId == null || event.getContainerId() != inventoryId.getId()) { return; } - if (CHEST_EVENT_TYPES.containsValue(eventType) - || SHADE_CHEST_OBJECTS.containsValue(eventType) - || HALLOWED_SEPULCHRE_COFFIN_EVENT.equals(eventType) - || HALLOWED_SACK_EVENT.equals(eventType) - || HERBIBOAR_EVENT.equals(eventType) - || HESPORI_EVENT.equals(eventType) - || WINTERTODT_SUPPLY_CRATE_EVENT.equals(eventType) - || eventType.endsWith("Bird House") - || eventType.startsWith("H.A.M. chest") - || lootRecordType == LootRecordType.PICKPOCKET - || eventType.endsWith("lockbox")) - { - WorldPoint playerLocation = client.getLocalPlayer().getWorldLocation(); - Collection groundItems = lootManager.getItemSpawns(playerLocation); + final ItemContainer inventoryContainer = event.getItemContainer(); + Multiset currentInventory = HashMultiset.create(); + Arrays.stream(inventoryContainer.getItems()) + .forEach(item -> currentInventory.add(item.getId(), item.getQuantity())); - processInventoryLoot(eventType, lootRecordType, metadata, event.getItemContainer(), groundItems); - resetEvent(); - } - // Events that do not produce ground items - else if (SEEDPACK_EVENT.equals(eventType) - || CASKET_EVENT.equals(eventType) - || BIRDNEST_EVENT.equals(eventType) - || SPOILS_OF_WAR_EVENT.equals(eventType) - || TEMPOROSS_EVENT.equals(eventType) - || TEMPOROSS_CASKET_EVENT.equals(eventType) - || MAHOGANY_CRATE_EVENT.equals(eventType)) + WorldPoint playerLocation = client.getLocalPlayer().getWorldLocation(); + final Collection groundItems = lootManager.getItemSpawns(playerLocation); + + final Multiset diff = Multisets.difference(currentInventory, inventorySnapshot); + final Multiset diffr = Multisets.difference(inventorySnapshot, currentInventory); + + final List items = diff.entrySet().stream() + .map(e -> new ItemStack(e.getElement(), e.getCount(), client.getLocalPlayer().getLocalLocation())) + .collect(Collectors.toList()); + + log.debug("Inv change: {} Ground items: {}", items, groundItems); + + if (inventorySnapshotCb != null) { - processInventoryLoot(eventType, lootRecordType, metadata, event.getItemContainer(), Collections.emptyList()); - resetEvent(); + inventorySnapshotCb.accept(items, groundItems, diffr); } + + inventoryId = null; + inventorySnapshot = null; + inventorySnapshotCb = null; } @Subscribe @@ -911,58 +945,60 @@ public class LootTrackerPlugin extends Plugin } else if (isObjectOp(event.getMenuAction()) && event.getMenuOption().equals("Open") && SHADE_CHEST_OBJECTS.containsKey(event.getId())) { - setEvent(LootRecordType.EVENT, SHADE_CHEST_OBJECTS.get(event.getId())); - takeInventorySnapshot(); + onInvChange(collectInvAndGroundItems(LootRecordType.EVENT, SHADE_CHEST_OBJECTS.get(event.getId()))); } else if (isItemOp(event.getMenuAction())) { if (event.getMenuOption().equals("Take") && event.getId() == ItemID.SEED_PACK) { - setEvent(LootRecordType.EVENT, SEEDPACK_EVENT); - takeInventorySnapshot(); + onInvChange(collectInvItems(LootRecordType.EVENT, SEEDPACK_EVENT)); } else if (event.getMenuOption().equals("Search") && BIRDNEST_IDS.contains(event.getId())) { - setEvent(LootRecordType.EVENT, BIRDNEST_EVENT, event.getId()); - takeInventorySnapshot(); + onInvChange(collectInvItems(LootRecordType.EVENT, BIRDNEST_EVENT)); } else if (event.getMenuOption().equals("Open")) { switch (event.getId()) { case ItemID.CASKET: - setEvent(LootRecordType.EVENT, CASKET_EVENT); - takeInventorySnapshot(); + onInvChange(collectInvItems(LootRecordType.EVENT, CASKET_EVENT)); break; case ItemID.SUPPLY_CRATE: case ItemID.EXTRA_SUPPLY_CRATE: - setEvent(LootRecordType.EVENT, WINTERTODT_SUPPLY_CRATE_EVENT); - takeInventorySnapshot(); + onInvChange(collectInvAndGroundItems(LootRecordType.EVENT, WINTERTODT_SUPPLY_CRATE_EVENT)); break; case ItemID.SPOILS_OF_WAR: - setEvent(LootRecordType.EVENT, SPOILS_OF_WAR_EVENT); - takeInventorySnapshot(); + onInvChange(collectInvItems(LootRecordType.EVENT, SPOILS_OF_WAR_EVENT)); break; case ItemID.CASKET_25590: - setEvent(LootRecordType.EVENT, TEMPOROSS_CASKET_EVENT); - takeInventorySnapshot(); + onInvChange(collectInvAndGroundItems(LootRecordType.EVENT, TEMPOROSS_CASKET_EVENT)); break; case ItemID.SIMPLE_LOCKBOX_25647: case ItemID.ELABORATE_LOCKBOX_25649: case ItemID.ORNATE_LOCKBOX_25651: - setEvent(LootRecordType.EVENT, itemManager.getItemComposition(event.getId()).getName()); - takeInventorySnapshot(); + onInvChange(collectInvAndGroundItems(LootRecordType.EVENT, itemManager.getItemComposition(event.getId()).getName())); break; case ItemID.SUPPLY_CRATE_24884: - setEvent(LootRecordType.EVENT, MAHOGANY_CRATE_EVENT, client.getBoostedSkillLevel(Skill.CONSTRUCTION)); - takeInventorySnapshot(); + onInvChange(collectInvItems(LootRecordType.EVENT, MAHOGANY_CRATE_EVENT, client.getBoostedSkillLevel(Skill.CONSTRUCTION))); break; case ItemID.HALLOWED_SACK: - setEvent(LootRecordType.EVENT, HALLOWED_SACK_EVENT); - takeInventorySnapshot(); + onInvChange(collectInvAndGroundItems(LootRecordType.EVENT, HALLOWED_SACK_EVENT)); break; } } + else if (event.getMenuOption().equals("Loot") && IMPLING_JARS.contains(event.getId())) + { + onInvChange(((invItems, groundItems, removedItems) -> + { + int cnt = removedItems.count(event.getId()); + if (cnt > 0) + { + String name = itemManager.getItemComposition(event.getId()).getName(); + addLoot(name, -1, LootRecordType.EVENT, null, invItems, cnt); + } + })); + } } } @@ -1057,59 +1093,65 @@ public class LootTrackerPlugin extends Plugin } } - private void setEvent(LootRecordType lootRecordType, String eventType, Object metadata) - { - this.lootRecordType = lootRecordType; - this.eventType = eventType; - this.metadata = metadata; - } - - private void setEvent(LootRecordType lootRecordType, String eventType) - { - setEvent(lootRecordType, eventType, null); - } - private void resetEvent() { - lootRecordType = null; - eventType = null; - metadata = null; + inventoryId = null; + inventorySnapshot = null; + inventorySnapshotCb = null; } - private void takeInventorySnapshot() + @FunctionalInterface + interface InvChangeCallback { - final ItemContainer itemContainer = client.getItemContainer(InventoryID.INVENTORY); + void accept(Collection invItems, Collection groundItems, Multiset removedItems); + } + + private InvChangeCallback collectInvItems(LootRecordType type, String event) + { + return collectInvItems(type, event, null); + } + + private InvChangeCallback collectInvItems(LootRecordType type, String event, Object metadata) + { + return (invItems, groundItems, removedItems) -> + addLoot(event, -1, type, metadata, invItems); + } + + private InvChangeCallback collectInvAndGroundItems(LootRecordType type, String event) + { + return collectInvAndGroundItems(type, event, null); + } + + private InvChangeCallback collectInvAndGroundItems(LootRecordType type, String event, Object metadata) + { + return (invItems, groundItems, removedItems) -> + { + List combined = new ArrayList<>(); + combined.addAll(invItems); + combined.addAll(groundItems); + addLoot(event, -1, type, metadata, combined); + }; + } + + private void onInvChange(InvChangeCallback cb) + { + onInvChange(InventoryID.INVENTORY, cb); + } + + private void onInvChange(InventoryID inv, InvChangeCallback cb) + { + inventoryId = inv; + inventorySnapshot = HashMultiset.create(); + inventorySnapshotCb = cb; + + final ItemContainer itemContainer = client.getItemContainer(inv); if (itemContainer != null) { - inventorySnapshot = HashMultiset.create(); Arrays.stream(itemContainer.getItems()) .forEach(item -> inventorySnapshot.add(item.getId(), item.getQuantity())); } } - private void processInventoryLoot(String event, LootRecordType lootRecordType, Object metadata, ItemContainer inventoryContainer, Collection groundItems) - { - if (inventorySnapshot != null) - { - Multiset currentInventory = HashMultiset.create(); - Arrays.stream(inventoryContainer.getItems()) - .forEach(item -> currentInventory.add(item.getId(), item.getQuantity())); - - groundItems.stream() - .forEach(item -> currentInventory.add(item.getId(), item.getQuantity())); - - final Multiset diff = Multisets.difference(currentInventory, inventorySnapshot); - - List items = diff.entrySet().stream() - .map(e -> new ItemStack(e.getElement(), e.getCount(), client.getLocalPlayer().getLocalLocation())) - .collect(Collectors.toList()); - - addLoot(event, -1, lootRecordType, metadata, items); - - inventorySnapshot = null; - } - } - private boolean processHerbiboarHerbSackLoot(int timestamp) { List herbs = new ArrayList<>(); diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/roofremoval/overrides.jsonc b/runelite-client/src/main/resources/net/runelite/client/plugins/roofremoval/overrides.jsonc index 04433ef5a0..777e425099 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/roofremoval/overrides.jsonc +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/roofremoval/overrides.jsonc @@ -3358,5 +3358,57 @@ "z1": 0, "z2": 3 } + ], + "7243": [ // Cabin Fever ships + { + "rx1": 21, + "ry1": 23, + "rx2": 25, + "ry2": 46, + "z1": 0, + "z2": 0 + }, + { + "rx1": 30, + "ry1": 19, + "rx2": 34, + "ry2": 42, + "z1": 0, + "z2": 0 + } + ], + "12336": [ // Tutorial Island + { // bank front + "rx1": 46, + "ry1": 46, + "rx2": 53, + "ry2": 46, + "z1": 0, + "z2": 0 + }, + { // bank back + "rx1": 47, + "ry1": 54, + "rx2": 48, + "ry2": 54, + "z1": 0, + "z2": 0 + }, + { // monastery + "rx1": 57, + "ry1": 34, + "rx2": 57, + "ry2": 35, + "z1": 0, + "z2": 0 + }, + { // monastery z1 + "rx1": 43, + "ry1": 33, + "rx2": 52, + "ry2": 36, + "z1": 1, + "z2": 1 + } ] } diff --git a/runelite-client/src/test/java/net/runelite/client/chat/ChatMessageManagerTest.java b/runelite-client/src/test/java/net/runelite/client/chat/ChatMessageManagerTest.java index 5f5d2d59a9..e2958e8ba9 100644 --- a/runelite-client/src/test/java/net/runelite/client/chat/ChatMessageManagerTest.java +++ b/runelite-client/src/test/java/net/runelite/client/chat/ChatMessageManagerTest.java @@ -69,7 +69,7 @@ public class ChatMessageManagerTest Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); } - private void setupVm(ChatMessageType type, String name, String message) + private MessageNode setupVm(ChatMessageType type, String name, String message) { MessageNode messageNode = mock(MessageNode.class); when(messageNode.getType()).thenReturn(type); @@ -92,6 +92,8 @@ public class ChatMessageManagerTest when(client.getStringStackSize()).thenReturn(sstack.length); when(client.getIntStack()).thenReturn(istack); when(client.getIntStackSize()).thenReturn(istack.length); + + return messageNode; } @Test @@ -187,4 +189,24 @@ public class ChatMessageManagerTest assertEquals("Total points: 42, Personal points: 43 (44%)", formattedMessage); } + + @Test + public void testGim() + { + when(chatColorConfig.opaqueClanChatInfo()).thenReturn(Color.RED); + when(chatColorConfig.opaqueClanChatInfoHighlight()).thenReturn(Color.BLUE); + + // rebuild color cache + ConfigChanged configChanged = new ConfigChanged(); + configChanged.setGroup("textrecolor"); + chatMessageManager.onConfigChanged(configChanged); + + MessageNode messageNode = setupVm(ChatMessageType.CLAN_GIM_MESSAGE, "", "rsn received a drop: 8 x Bronze bolts (16 coins)."); + when(messageNode.getRuneLiteFormatMessage()).thenReturn("rsn received a drop: 8 x Bronze bolts (16 coins)."); + + chatMessageManager.colorChatMessage(); + + // | + assertEquals("|rsn received a drop: 8 x Bronze bolts (16 coins).", sstack[2]); + } } \ No newline at end of file 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 654e99f2c9..1897d2813a 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 @@ -32,6 +32,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import javax.inject.Inject; @@ -59,11 +60,9 @@ import net.runelite.client.config.ConfigManager; 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; @@ -113,10 +112,6 @@ public class LootTrackerPluginTest @Bind private SpriteManager spriteManager; - @Mock - @Bind - private InfoBoxManager infoBoxManager; - @Inject private LootTrackerPlugin lootTrackerPlugin; @@ -152,6 +147,21 @@ public class LootTrackerPluginTest Player player = mock(Player.class); when(player.getWorldLocation()).thenReturn(new WorldPoint(0, 0, 0)); when(client.getLocalPlayer()).thenReturn(player); + + lootTrackerPlugin = spy(lootTrackerPlugin); + doNothing().when(lootTrackerPlugin).addLoot(any(), anyInt(), any(), any(), any(Collection.class)); + } + + private void sendInvChange(InventoryID inv, Collection items) + { + ItemContainer itemContainer = mock(ItemContainer.class); + when(itemContainer.getItems()).thenReturn(items.stream() + .map(is -> new Item(is.getId(), is.getQuantity())) + .toArray(Item[]::new)); + when(client.getItemContainer(inv)).thenReturn(itemContainer); + + ItemContainerChanged event = new ItemContainerChanged(inv.getId(), itemContainer); + lootTrackerPlugin.onItemContainerChanged(event); } @Test @@ -160,8 +170,12 @@ public class LootTrackerPluginTest ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.SPAM, "", "You pick the hero's pocket.", "", 0); lootTrackerPlugin.onChatMessage(chatMessage); - assertEquals("Hero", lootTrackerPlugin.eventType); - assertEquals(LootRecordType.PICKPOCKET, lootTrackerPlugin.lootRecordType); + List items = Collections.singletonList( + new ItemStack(ItemID.COINS_995, 1, null) + ); + sendInvChange(InventoryID.INVENTORY, items); + + verify(lootTrackerPlugin).addLoot("Hero", -1, LootRecordType.PICKPOCKET, null, items); } @Test @@ -170,15 +184,12 @@ public class LootTrackerPluginTest ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "You have completed 1 master Treasure Trail.", "", 0); lootTrackerPlugin.onChatMessage(chatMessage); - assertEquals("Clue Scroll (Master)", lootTrackerPlugin.eventType); - assertEquals(LootRecordType.EVENT, lootTrackerPlugin.lootRecordType); - } + List items = Collections.singletonList( + new ItemStack(ItemID.COINS_995, 1, null) + ); + sendInvChange(InventoryID.BARROWS_REWARD, items); - private static ItemComposition mockItem(String name) - { - ItemComposition itemComposition = mock(ItemComposition.class); - when(itemComposition.getName()).thenReturn(name); - return itemComposition; + verify(lootTrackerPlugin).addLoot("Clue Scroll (Master)", -1, LootRecordType.EVENT, null, items); } @Test @@ -213,18 +224,13 @@ public class LootTrackerPluginTest when(messageTable.iterator()).thenReturn(mockIterator); when(client.getMessages()).thenReturn(messageTable); - LootTrackerPlugin lootTrackerPluginSpy = spy(this.lootTrackerPlugin); - doNothing().when(lootTrackerPluginSpy).addLoot(any(), anyInt(), any(), any(), any(Collection.class)); - ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", LootTrackerPlugin.HERBIBOAR_LOOTED_MESSAGE, "", 0); - lootTrackerPluginSpy.onChatMessage(chatMessage); + lootTrackerPlugin.onChatMessage(chatMessage); - verify(lootTrackerPluginSpy).addLoot("Herbiboar", -1, LootRecordType.EVENT, 42, Arrays.asList( + verify(lootTrackerPlugin).addLoot("Herbiboar", -1, LootRecordType.EVENT, 42, 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); } } @@ -234,10 +240,6 @@ public class LootTrackerPluginTest when(lootTrackerConfig.showRaidsLootValue()).thenReturn(true); when(lootTrackerConfig.priceType()).thenReturn(LootTrackerPriceType.GRAND_EXCHANGE); - LootTrackerPlugin spyPlugin = Mockito.spy(lootTrackerPlugin); - // Make sure we don't execute addLoot, so we don't have to mock LootTrackerPanel and everything else also - doNothing().when(spyPlugin).addLoot(anyString(), anyInt(), any(LootRecordType.class), isNull(), anyCollection()); - ItemContainer itemContainer = mock(ItemContainer.class); when(itemContainer.getItems()).thenReturn(new Item[]{ new Item(ItemID.TWISTED_BOW, 1), @@ -250,7 +252,7 @@ public class LootTrackerPluginTest WidgetLoaded widgetLoaded = new WidgetLoaded(); widgetLoaded.setGroupId(WidgetID.CHAMBERS_OF_XERIC_REWARD_GROUP_ID); - spyPlugin.onWidgetLoaded(widgetLoaded); + lootTrackerPlugin.onWidgetLoaded(widgetLoaded); ArgumentCaptor captor = ArgumentCaptor.forClass(QueuedMessage.class); verify(chatMessageManager).queue(captor.capture()); @@ -303,26 +305,35 @@ public class LootTrackerPluginTest @Test public void testBirdhouses() { + when(client.getBoostedSkillLevel(Skill.HUNTER)).thenReturn(42); + List items = Collections.singletonList( + new ItemStack(ItemID.BIRD_NEST, 42, null) + ); + // No bird nests ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.SPAM, "", "You dismantle and discard the trap, retrieving 10 dead birds, 30 feathers and 1140 Hunter XP.", "", 0); lootTrackerPlugin.onChatMessage(chatMessage); - assertEquals("Magic Bird House", lootTrackerPlugin.eventType); - assertEquals(LootRecordType.EVENT, lootTrackerPlugin.lootRecordType); + sendInvChange(InventoryID.INVENTORY, items); + verify(lootTrackerPlugin).addLoot("Magic Bird House", -1, LootRecordType.EVENT, 42, items); + + when(client.getItemContainer(InventoryID.INVENTORY)).thenReturn(null); // Single bird nest chatMessage = new ChatMessage(null, ChatMessageType.SPAM, "", "You dismantle and discard the trap, retrieving a nest, 10 dead birds, 50 feathers and 700 Hunter XP.", "", 0); lootTrackerPlugin.onChatMessage(chatMessage); - assertEquals("Teak Bird House", lootTrackerPlugin.eventType); - assertEquals(LootRecordType.EVENT, lootTrackerPlugin.lootRecordType); + sendInvChange(InventoryID.INVENTORY, items); + verify(lootTrackerPlugin).addLoot("Teak Bird House", -1, LootRecordType.EVENT, 42, items); + + when(client.getItemContainer(InventoryID.INVENTORY)).thenReturn(null); // Multiple nests chatMessage = new ChatMessage(null, ChatMessageType.SPAM, "", "You dismantle and discard the trap, retrieving 2 nests, 10 dead birds, 40 feathers and 280 Hunter XP.", "", 0); lootTrackerPlugin.onChatMessage(chatMessage); - assertEquals("Regular Bird House", lootTrackerPlugin.eventType); - assertEquals(LootRecordType.EVENT, lootTrackerPlugin.lootRecordType); + sendInvChange(InventoryID.INVENTORY, items); + verify(lootTrackerPlugin).addLoot("Regular Bird House", -1, LootRecordType.EVENT, 42, items); } @Test @@ -372,8 +383,7 @@ public class LootTrackerPluginTest when(client.getLocalPlayer().getWorldLocation()).thenReturn(new WorldPoint(3153, 2833, 0)); when(client.getBoostedSkillLevel(Skill.FISHING)).thenReturn(69); - LootTrackerPlugin lootTrackerPluginSpy = spy(this.lootTrackerPlugin); - doNothing().when(lootTrackerPluginSpy).addLoot(any(), anyInt(), any(), any(), any(Collection.class)); + doNothing().when(lootTrackerPlugin).addLoot(any(), anyInt(), any(), any(), any(Collection.class)); ItemContainer itemContainer = mock(ItemContainer.class); when(itemContainer.getItems()).thenReturn(new Item[]{ @@ -383,21 +393,21 @@ public class LootTrackerPluginTest when(client.getItemContainer(InventoryID.INVENTORY)).thenReturn(itemContainer); ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.SPAM, "", "You found some loot: 30 x Raw tuna", "", 0); - lootTrackerPluginSpy.onChatMessage(chatMessage); + lootTrackerPlugin.onChatMessage(chatMessage); when(itemContainer.getItems()).thenReturn(new Item[]{ new Item(ItemID.BUCKET_OF_WATER, 1), new Item(ItemID.ROPE, 1), new Item(ItemID.RAW_TUNA, 30) }); - lootTrackerPluginSpy.onItemContainerChanged(new ItemContainerChanged(InventoryID.INVENTORY.getId(), itemContainer)); + lootTrackerPlugin.onItemContainerChanged(new ItemContainerChanged(InventoryID.INVENTORY.getId(), itemContainer)); - verify(lootTrackerPluginSpy).addLoot("Reward pool (Tempoross)", -1, LootRecordType.EVENT, 69, Arrays.asList( + verify(lootTrackerPlugin).addLoot("Reward pool (Tempoross)", -1, LootRecordType.EVENT, 69, Arrays.asList( new ItemStack(ItemID.RAW_TUNA, 30, null) )); chatMessage = new ChatMessage(null, ChatMessageType.SPAM, "", "You found some loot: Tome of water (empty)", "", 0); - lootTrackerPluginSpy.onChatMessage(chatMessage); + lootTrackerPlugin.onChatMessage(chatMessage); when(itemContainer.getItems()).thenReturn(new Item[]{ new Item(ItemID.BUCKET_OF_WATER, 1), @@ -405,9 +415,9 @@ public class LootTrackerPluginTest new Item(ItemID.RAW_TUNA, 30), new Item(ItemID.TOME_OF_WATER_EMPTY, 1) }); - lootTrackerPluginSpy.onItemContainerChanged(new ItemContainerChanged(InventoryID.INVENTORY.getId(), itemContainer)); + lootTrackerPlugin.onItemContainerChanged(new ItemContainerChanged(InventoryID.INVENTORY.getId(), itemContainer)); - verify(lootTrackerPluginSpy).addLoot("Reward pool (Tempoross)", -1, LootRecordType.EVENT, 69, Arrays.asList( + verify(lootTrackerPlugin).addLoot("Reward pool (Tempoross)", -1, LootRecordType.EVENT, 69, Arrays.asList( new ItemStack(ItemID.TOME_OF_WATER_EMPTY, 1, null) )); }