Repository: runelite/runelite (java)

Issue:
[Loot Tracker] Other Actions in Grubby Chest area adds to Grubby Chest Count
### Type

Incorrect behavior

### Operating System

Windows

### OS Version

Windows 10

### Bug description

Steps to reproduce:

1. "Open Grubby Chest" while holding a Grubby Key or Right click option "Check Grubby Chest"
2. Attempt various actions (eat food, fletch, certain teleports, etc.)

Unexpected effect: The Loot Tracker counter for Grubby Chest increases without opening the chest

Tested:

- Triggering: House tab, Standard Spellbook teleports, Ectophial, Fletching Willow logs, Bury Bones, Eat Mackerel
- Not triggering: Khardest Memoirs teleport out, Xeric's Talisman Teleport out, decanting potions.

Not tested: Not already having a Grubby Chest loot tracked. 

Additional Notes:
Changed to safe mode and tested again, behaviour still occurs

### Screenshots or videos

![image](https://github.com/runelite/runelite/assets/40974473/b4f89c51-9429-4955-af1d-eb1c67383575)


### RuneLite version

1.10.12.2
Rev 216
2.6.3

### Logs

[client.log](https://github.com/runelite/runelite/files/12657894/client.log)
[launcher.log](https://github.com/runelite/runelite/files/12657900/launcher.log)


Pull request description:
loot tracker: fix grubby chest false positive loot
This PR fixes a possibility of items being mistakenly registered as loot from the Grubby chest, when checking the loot count ("kc") of the chest.

Instead of using a message shared when both looting and checking the chest (as it is currently), a loot-specific message is used to assume tracking. The message has two variations; one for common loot, and one for unique loot. Though they share a similar beginning, I chose to use a pattern (instead of using a string and checking the start of the message) because the shared string felt too generic and I'm not sure if e.g. opening other types of containers (e.g. clue scroll casket¹/random event casket/other) yield a message with similar beginning. This felt the safest.

¹ I recall there is at least 1 clue scroll step in the dungeon, by the red dragons.

The standard loot message:
> You find treasure and supplies within the chest.

The rare loot message:
> You find treasure, supplies, and a weirdly coloured egg sac within the chest.

<details>
  <summary>Debug output from looting the chest</summary>

```log
2025-02-25 15:53:09 CET [Client] DEBUG injected-client - Chat message type SPAM: You unlock the chest with your key.
2025-02-25 15:53:10 CET [Client] DEBUG injected-client - Chat message type GAMEMESSAGE: You have opened the Grubby Chest 19 times.
2025-02-25 15:53:10 CET [Client] DEBUG net.runelite.client.game.LootManager - Item spawn 7056 (1) location LocalPoint(x=6720, y=6848, worldView=-1)
2025-02-25 15:53:10 CET [Client] DEBUG net.runelite.client.game.LootManager - Item spawn 7056 (1) location LocalPoint(x=6720, y=6848, worldView=-1)
2025-02-25 15:53:10 CET [Client] DEBUG net.runelite.client.game.LootManager - Item spawn 7056 (1) location LocalPoint(x=6720, y=6848, worldView=-1)
2025-02-25 15:53:10 CET [Client] DEBUG net.runelite.client.game.LootManager - Item spawn 7056 (1) location LocalPoint(x=6720, y=6848, worldView=-1)
2025-02-25 15:53:10 CET [Client] DEBUG net.runelite.client.game.LootManager - Item spawn 385 (1) location LocalPoint(x=6720, y=6848, worldView=-1)
2025-02-25 15:53:10 CET [Client] DEBUG net.runelite.client.game.LootManager - Item spawn 385 (1) location LocalPoint(x=6720, y=6848, worldView=-1)
2025-02-25 15:53:10 CET [Client] DEBUG net.runelite.client.game.LootManager - Item spawn 385 (1) location LocalPoint(x=6720, y=6848, worldView=-1)
2025-02-25 15:53:10 CET [Client] DEBUG net.runelite.client.game.LootManager - Item spawn 385 (1) location LocalPoint(x=6720, y=6848, worldView=-1)
2025-02-25 15:53:10 CET [Client] DEBUG injected-client - Chat message type MESBOX: You find treasure and supplies within the chest.
2025-02-25 15:53:10 CET [Client] DEBUG n.r.c.p.l.LootTrackerPlugin - Chest loot matched 'You find treasure and supplies within the chest.' region 7323
2025-02-25 15:53:10 CET [Client] DEBUG n.r.c.p.l.LootTrackerPlugin - Inv change: [ItemStack(id=147, quantity=1), ItemStack(id=165, quantity=1), ItemStack(id=989, quantity=1), ItemStack(id=159, quantity=1)] Ground items: [ItemStack(id=7056, quantity=1), ItemStack(id=7056, quantity=1), ItemStack(id=7056, quantity=1), ItemStack(id=7056, quantity=1), ItemStack(id=385, quantity=1), ItemStack(id=385, quantity=1), ItemStack(id=385, quantity=1), ItemStack(id=385, quantity=1)]
2025-02-25 15:53:16 CET [Client] DEBUG injected-client - Chat message type SPAM: You have combined the liquid into four doses.
2025-02-25 15:53:17 CET [Client] DEBUG injected-client - Chat message type SPAM: You have combined the liquid into four doses.
2025-02-25 15:53:19 CET [Client] DEBUG injected-client - Chat message type SPAM: You have combined the liquid into four doses.
```
  
</details>



Fixes #17049 

Patch:
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollOverlay.java
index 8242c4fcc..0634810ca 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollOverlay.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollOverlay.java
@@ -69,6 +69,7 @@ public class ClueScrollOverlay extends OverlayPanel
 		item(ItemID.SEERS_HEADBAND_HARD),
 		item(ItemID.SEERS_HEADBAND_ELITE),
 		item(ItemID.WINT_TORCH),
+		item(ItemID.WINT_TORCH_OFFHAND),
 		item(ItemID.SKILLCAPE_MAX),
 		item(ItemID.SKILLCAPE_MAX_WORN),
 		item(ItemID.ABYSSAL_LANTERN_NORMAL),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/SkillChallengeClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/SkillChallengeClue.java
index 101e1fb8f..8ba1ffcc4 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/SkillChallengeClue.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/SkillChallengeClue.java
@@ -153,7 +153,8 @@ public class SkillChallengeClue extends ClueScroll implements NpcClueScroll, Nam
 
 	private static final AnyRequirementCollection ANY_HAMMER = any("Hammer",
 		item(ItemID.HAMMER),
-		item(ItemID.IMCANDO_HAMMER)
+		item(ItemID.IMCANDO_HAMMER),
+		item(ItemID.IMCANDO_HAMMER_OFFHAND)
 	);
 
 	private static final AllRequirementsCollection A_FULL_GRACEFUL_SET = all("A full Graceful set",
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 95734fdcb..91e058d29 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
@@ -171,7 +171,7 @@ public class LootTrackerPlugin extends Plugin
 	// Used by Stone Chest, Isle of Souls chest, Dark Chest
 	private static final String OTHER_CHEST_LOOTED_MESSAGE = "You steal some loot from the chest.";
 	private static final String DORGESH_KAAN_CHEST_LOOTED_MESSAGE = "You find treasure inside!";
-	private static final String GRUBBY_CHEST_LOOTED_MESSAGE = "You have opened the Grubby Chest";
+	private static final Pattern GRUBBY_CHEST_LOOTED_MESSAGE = Pattern.compile("You find treasure(?: and supplies|, supplies, and a weirdly coloured egg sac) within the chest.");
 	private static final String ANCIENT_CHEST_LOOTED_MESSAGE = "You open the chest and find";
 	private static final Pattern HAM_CHEST_LOOTED_PATTERN = Pattern.compile("Your (?<key>[a-z]+) key breaks in the lock.*");
 	private static final int HAM_STOREROOM_REGION = 10321;
@@ -1013,7 +1013,7 @@ public class LootTrackerPlugin extends Plugin
 		final String message = event.getMessage();
 
 		if (message.equals(CHEST_LOOTED_MESSAGE) || message.equals(OTHER_CHEST_LOOTED_MESSAGE)
-			|| message.equals(DORGESH_KAAN_CHEST_LOOTED_MESSAGE) || message.startsWith(GRUBBY_CHEST_LOOTED_MESSAGE)
+			|| message.equals(DORGESH_KAAN_CHEST_LOOTED_MESSAGE) || GRUBBY_CHEST_LOOTED_MESSAGE.matcher(message).matches()
 			|| message.startsWith(ANCIENT_CHEST_LOOTED_MESSAGE)
 			|| LARRAN_LOOTED_PATTERN.matcher(message).matches() || ROGUES_CHEST_PATTERN.matcher(message).matches())
 		{


Test patch:
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 c01e7bdaf..7f5e846db 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
@@ -355,6 +355,11 @@ public class LootTrackerPluginTest
 	@Test
 	public void testGrubbyChest()
 	{
+		// The order of events is as follows:
+		// 1. The key is removed from the players inventory
+		// 2. The MESBOX chat message prompt is delivered
+		// 3. The loot is added to the inventory/ground
+
 		Player player = mock(Player.class);
 		when(player.getWorldLocation()).thenReturn(new WorldPoint(7323 >> 2, (7323 & 0xff) << 6, 0));
 		when(client.getLocalPlayer()).thenReturn(player);
@@ -365,29 +370,44 @@ public class LootTrackerPluginTest
 		ItemContainer itemContainer = mock(ItemContainer.class);
 		when(itemContainer.getItems()).thenReturn(new Item[]{
 			new Item(ItemID.TWISTED_BOW, 1),
-			new Item(ItemID.HOSDUN_GRUBBY_KEY, 1)
+			new Item(ItemID.HOSDUN_GRUBBY_KEY, 2)
 		});
 		when(client.getItemContainer(InventoryID.INV)).thenReturn(itemContainer);
 
-		ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.SPAM, "", "You unlock the chest with your key.", "", 0);
+		lootTrackerPluginSpy.onItemContainerChanged(new ItemContainerChanged(InventoryID.INV, itemContainer));
+
+		// Default loot message
+		ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.MESBOX, "", "You find treasure and supplies within the chest.", "", 0);
 		lootTrackerPluginSpy.onChatMessage(chatMessage);
 
 		when(itemContainer.getItems()).thenReturn(new Item[]{
-			new Item(ItemID.TWISTED_BOW, 1)
+			new Item(ItemID.TWISTED_BOW, 1),
+			new Item(ItemID.HOSDUN_GRUBBY_KEY, 1),
+			new Item(ItemID.SHARK, 42)
 		});
 		lootTrackerPluginSpy.onItemContainerChanged(new ItemContainerChanged(InventoryID.INV, itemContainer));
 
-		chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "You have opened the Grubby Chest 2 times.", "", 0);
+		verify(lootTrackerPluginSpy).addLoot("Grubby Chest", -1, LootRecordType.EVENT, null, Arrays.asList(
+			new ItemStack(ItemID.SHARK, 42)
+		));
+
+		// Unique loot message
+		chatMessage = new ChatMessage(null, ChatMessageType.MESBOX, "", "You find treasure, supplies, and a weirdly coloured egg sac within the chest.", "", 0);
 		lootTrackerPluginSpy.onChatMessage(chatMessage);
 
 		when(itemContainer.getItems()).thenReturn(new Item[]{
 			new Item(ItemID.TWISTED_BOW, 1),
-			new Item(ItemID.SHARK, 42)
+			new Item(ItemID.SHARK, 42),
+			new Item(ItemID._2DOSEPOTIONOFSARADOMIN, 3),
+			new Item(ItemID.BR_2DOSE2RESTORE, 1),
+			new Item(ItemID.HOSDUN_ORANGE_EGG_SAC, 1),
 		});
 		lootTrackerPluginSpy.onItemContainerChanged(new ItemContainerChanged(InventoryID.INV, itemContainer));
 
 		verify(lootTrackerPluginSpy).addLoot("Grubby Chest", -1, LootRecordType.EVENT, null, Arrays.asList(
-			new ItemStack(ItemID.SHARK, 42)
+			new ItemStack(ItemID._2DOSEPOTIONOFSARADOMIN, 3),
+			new ItemStack(ItemID.BR_2DOSE2RESTORE, 1),
+			new ItemStack(ItemID.HOSDUN_ORANGE_EGG_SAC, 1)
 		));
 	}