Repository: runelite/runelite
Pull request #17818 (2024-05-08 14:29:24)

achievement diary: fix prospector task
> Changed the Diary task in Motherlode Mine to require just the Helmet, instead of the entire outfit.

Post-fix:
![image](https://github.com/runelite/runelite/assets/7929021/aead5b97-22d4-4f69-8820-25838204b63a)



Patch:
diff --git a/runelite-api/src/main/java/net/runelite/api/ItemID.java b/runelite-api/src/main/java/net/runelite/api/ItemID.java
index daecbfb1b..e24e92634 100644
--- a/runelite-api/src/main/java/net/runelite/api/ItemID.java
+++ b/runelite-api/src/main/java/net/runelite/api/ItemID.java
@@ -13986,5 +13986,13 @@ public final class ItemID
 	public static final int SMALL_FUR_POUCH_OPEN = 29466;
 	public static final int MEDIUM_FUR_POUCH_OPEN = 29468;
 	public static final int LARGE_FUR_POUCH_OPEN = 29470;
+	public static final int PROSPECTOR_HELMET_29472 = 29472;
+	public static final int PROSPECTOR_JACKET_29474 = 29474;
+	public static final int PROSPECTOR_LEGS_29476 = 29476;
+	public static final int PROSPECTOR_BOOTS_29478 = 29478;
+	public static final int AGILITY_ARENA_TICKET_29480 = 29480;
+	public static final int BRIMHAVEN_VOUCHER = 29482;
+	public static final int AGILITY_XP = 29484;
+	public static final int GRACEFUL_RECOLOUR = 29485;
 	/* This file is automatically generated. Do not edit. */
 }
diff --git a/runelite-api/src/main/java/net/runelite/api/NullItemID.java b/runelite-api/src/main/java/net/runelite/api/NullItemID.java
index 4b666c304..f32d4e70b 100644
--- a/runelite-api/src/main/java/net/runelite/api/NullItemID.java
+++ b/runelite-api/src/main/java/net/runelite/api/NullItemID.java
@@ -15275,5 +15275,11 @@ public final class NullItemID
 	public static final int NULL_29467 = 29467;
 	public static final int NULL_29469 = 29469;
 	public static final int NULL_29471 = 29471;
+	public static final int NULL_29473 = 29473;
+	public static final int NULL_29475 = 29475;
+	public static final int NULL_29477 = 29477;
+	public static final int NULL_29479 = 29479;
+	public static final int NULL_29481 = 29481;
+	public static final int NULL_29483 = 29483;
 	/* This file is automatically generated. Do not edit. */
 }
diff --git a/runelite-api/src/main/java/net/runelite/api/NullObjectID.java b/runelite-api/src/main/java/net/runelite/api/NullObjectID.java
index b2c3c967b..6be44d9c2 100644
--- a/runelite-api/src/main/java/net/runelite/api/NullObjectID.java
+++ b/runelite-api/src/main/java/net/runelite/api/NullObjectID.java
@@ -13547,7 +13547,6 @@ public final class NullObjectID
 	public static final int NULL_28577 = 28577;
 	public static final int NULL_28578 = 28578;
 	public static final int NULL_28589 = 28589;
-	public static final int NULL_28590 = 28590;
 	public static final int NULL_28591 = 28591;
 	public static final int NULL_28593 = 28593;
 	public static final int NULL_28598 = 28598;
@@ -27596,5 +27595,13 @@ public final class NullObjectID
 	public static final int NULL_53231 = 53231;
 	public static final int NULL_53232 = 53232;
 	public static final int NULL_53233 = 53233;
+	public static final int NULL_53235 = 53235;
+	public static final int NULL_53239 = 53239;
+	public static final int NULL_53243 = 53243;
+	public static final int NULL_53244 = 53244;
+	public static final int NULL_53245 = 53245;
+	public static final int NULL_53246 = 53246;
+	public static final int NULL_53253 = 53253;
+	public static final int NULL_53257 = 53257;
 	/* This file is automatically generated. Do not edit. */
 }
diff --git a/runelite-api/src/main/java/net/runelite/api/ObjectID.java b/runelite-api/src/main/java/net/runelite/api/ObjectID.java
index 108182aaa..bbe47932e 100644
--- a/runelite-api/src/main/java/net/runelite/api/ObjectID.java
+++ b/runelite-api/src/main/java/net/runelite/api/ObjectID.java
@@ -12676,10 +12676,6 @@ public final class ObjectID
 	public static final int CUPBOARD_23687 = 23687;
 	public static final int CUPBOARD_23688 = 23688;
 	public static final int CUPBOARD_23689 = 23689;
-	public static final int CUPBOARD_23690 = 23690;
-	public static final int CUPBOARD_23691 = 23691;
-	public static final int CUPBOARD_23692 = 23692;
-	public static final int CUPBOARD_23693 = 23693;
 	public static final int CUBE_PILE = 23694;
 	public static final int CYLINDER_PILE = 23695;
 	public static final int ICOSAHEDRON_PILE = 23696;
@@ -15032,6 +15028,7 @@ public final class ObjectID
 	public static final int POT_OF_DYNAMITE_28586 = 28586;
 	public static final int SHATTERED_ROCKFACE = 28587;
 	public static final int SHATTERED_ROCKFACE_28588 = 28588;
+	public static final int WATER_WHEEL_28590 = 28590;
 	public static final int ORE_SACK = 28592;
 	public static final int BANK_CHEST_28594 = 28594;
 	public static final int BANK_CHEST_28595 = 28595;
@@ -25621,5 +25618,24 @@ public final class ObjectID
 	public static final int ZOMBIE_PIRATES_LOCKER = 53222;
 	public static final int AGILITY_DISPENSER = 53224;
 	public static final int SPIKES_53225 = 53225;
+	public static final int ROCKS_53234 = 53234;
+	public static final int ROCKS_53236 = 53236;
+	public static final int STEPPING_STONE_53237 = 53237;
+	public static final int ROCKS_53238 = 53238;
+	public static final int ROCKS_53240 = 53240;
+	public static final int STEPPING_STONE_53241 = 53241;
+	public static final int CREVICE_53242 = 53242;
+	public static final int LADDER_53247 = 53247;
+	public static final int LADDER_53248 = 53248;
+	public static final int LADDER_53249 = 53249;
+	public static final int TUNNEL_53250 = 53250;
+	public static final int TUNNEL_53251 = 53251;
+	public static final int ROCKS_53252 = 53252;
+	public static final int ROCKS_53254 = 53254;
+	public static final int CLIMBING_ROCKS_53255 = 53255;
+	public static final int DRY_STONE_WALL_53256 = 53256;
+	public static final int CREVICE_53258 = 53258;
+	public static final int CREVICE_53259 = 53259;
+	public static final int BANK_CHEST_53260 = 53260;
 	/* This file is automatically generated. Do not edit. */
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/config/Keybind.java b/runelite-client/src/main/java/net/runelite/client/config/Keybind.java
index 4ba1ada45..f48067047 100644
--- a/runelite-client/src/main/java/net/runelite/client/config/Keybind.java
+++ b/runelite-client/src/main/java/net/runelite/client/config/Keybind.java
@@ -131,9 +131,8 @@ public class Keybind
 			{
 				return this.keyCode == keyCode;
 			}
-			else
+			else if (mf != null)
 			{
-				assert mf != null;
 				return this.keyCode == keyCode && (this.modifiers & modifiers) == this.modifiers && ((mf & this.modifiers) == mf);
 			}
 		}
diff --git a/runelite-client/src/main/java/net/runelite/client/events/ConfigChanged.java b/runelite-client/src/main/java/net/runelite/client/events/ConfigChanged.java
index db79034c2..9f7762d88 100644
--- a/runelite-client/src/main/java/net/runelite/client/events/ConfigChanged.java
+++ b/runelite-client/src/main/java/net/runelite/client/events/ConfigChanged.java
@@ -60,5 +60,6 @@ public class ConfigChanged
 	/**
 	 * The new value of the entry, null if the entry has been unset.
 	 */
+	@Nullable
 	private String newValue;
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemStack.java b/runelite-client/src/main/java/net/runelite/client/game/ItemStack.java
index 0e708a583..8258d5d64 100644
--- a/runelite-client/src/main/java/net/runelite/client/game/ItemStack.java
+++ b/runelite-client/src/main/java/net/runelite/client/game/ItemStack.java
@@ -32,5 +32,23 @@ public class ItemStack
 {
 	private final int id;
 	private final int quantity;
-	private final LocalPoint location;
+
+	public ItemStack(int id, int quantity)
+	{
+		this.id = id;
+		this.quantity = quantity;
+	}
+
+	@Deprecated
+	public ItemStack(int id, int quantity, LocalPoint location)
+	{
+		this.id = id;
+		this.quantity = quantity;
+	}
+
+	@Deprecated
+	public LocalPoint getLocation()
+	{
+		return null;
+	}
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/game/LootManager.java b/runelite-client/src/main/java/net/runelite/client/game/LootManager.java
index d282e58cb..9937ce630 100644
--- a/runelite-client/src/main/java/net/runelite/client/game/LootManager.java
+++ b/runelite-client/src/main/java/net/runelite/client/game/LootManager.java
@@ -35,6 +35,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 import javax.inject.Inject;
 import javax.inject.Singleton;
 import lombok.extern.slf4j.Slf4j;
@@ -53,7 +54,6 @@ import net.runelite.api.coords.WorldPoint;
 import net.runelite.api.events.AnimationChanged;
 import net.runelite.api.events.GameTick;
 import net.runelite.api.events.ItemDespawned;
-import net.runelite.api.events.ItemQuantityChanged;
 import net.runelite.api.events.ItemSpawned;
 import net.runelite.api.events.NpcChanged;
 import net.runelite.api.events.NpcDespawned;
@@ -74,7 +74,7 @@ public class LootManager
 	private final EventBus eventBus;
 	private final Client client;
 	private final NpcUtil npcUtil;
-	private final ListMultimap<Integer, ItemStack> itemSpawns = ArrayListMultimap.create();
+	private final ListMultimap<Integer, TileItem> itemSpawns = ArrayListMultimap.create();
 	private final Set<WorldPoint> killPoints = new HashSet<>();
 	private WorldPoint playerLocationLastTick;
 	private WorldPoint krakenPlayerLocation;
@@ -161,7 +161,9 @@ public class LootManager
 		final int x = location.getSceneX();
 		final int y = location.getSceneY();
 		final int packed = x << 8 | y;
-		final Collection<ItemStack> items = itemSpawns.get(packed);
+		final Collection<ItemStack> items = itemSpawns.get(packed).stream()
+			.map(i -> new ItemStack(i.getId(), i.getQuantity()))
+			.collect(Collectors.toList());
 
 		if (items.isEmpty())
 		{
@@ -179,7 +181,7 @@ public class LootManager
 		final Tile tile = itemSpawned.getTile();
 		final LocalPoint location = tile.getLocalLocation();
 		final int packed = location.getSceneX() << 8 | location.getSceneY();
-		itemSpawns.put(packed, new ItemStack(item.getId(), item.getQuantity(), location));
+		itemSpawns.put(packed, item);
 		log.debug("Item spawn {} ({}) location {}", item.getId(), item.getQuantity(), location);
 	}
 
@@ -191,23 +193,6 @@ public class LootManager
 		log.debug("Item despawn {} ({}) location {}", item.getId(), item.getQuantity(), location);
 	}
 
-	@Subscribe
-	public void onItemQuantityChanged(ItemQuantityChanged itemQuantityChanged)
-	{
-		final TileItem item = itemQuantityChanged.getItem();
-		final Tile tile = itemQuantityChanged.getTile();
-		final LocalPoint location = tile.getLocalLocation();
-		final int packed = location.getSceneX() << 8 | location.getSceneY();
-		final int diff = itemQuantityChanged.getNewQuantity() - itemQuantityChanged.getOldQuantity();
-
-		if (diff <= 0)
-		{
-			return;
-		}
-
-		itemSpawns.put(packed, new ItemStack(item.getId(), diff, location));
-	}
-
 	@Subscribe
 	public void onAnimationChanged(AnimationChanged e)
 	{
@@ -334,8 +319,8 @@ public class LootManager
 					}
 
 					final int packed = (x + i) << 8 | (y + j);
-					final Collection<ItemStack> items = itemSpawns.get(packed);
-					allItems.addAll(items);
+					final Collection<TileItem> items = itemSpawns.get(packed);
+					items.forEach(item -> allItems.add(new ItemStack(item.getId(), item.getQuantity())));
 				}
 			}
 		}
@@ -356,7 +341,7 @@ public class LootManager
 			case NpcID.ZULRAH:      // Green
 			case NpcID.ZULRAH_2043: // Red
 			case NpcID.ZULRAH_2044: // Blue
-				for (Map.Entry<Integer, ItemStack> entry : itemSpawns.entries())
+				for (Map.Entry<Integer, TileItem> entry : itemSpawns.entries())
 				{
 					if (entry.getValue().getId() == ItemID.ZULRAHS_SCALES)
 					{
@@ -503,8 +488,10 @@ public class LootManager
 		final int sceneX = localPoint.getSceneX();
 		final int sceneY = localPoint.getSceneY();
 		final int packed = sceneX << 8 | sceneY;
-		final List<ItemStack> itemStacks = itemSpawns.get(packed);
-		return Collections.unmodifiableList(itemStacks);
+		final List<TileItem> itemStacks = itemSpawns.get(packed);
+		return itemStacks.stream()
+			.map(ti -> new ItemStack(ti.getId(), ti.getQuantity()))
+			.collect(Collectors.toList());
 	}
 
 	private void clearDelayedLootNpc()
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/achievementdiary/diaries/FaladorDiaryRequirement.java b/runelite-client/src/main/java/net/runelite/client/plugins/achievementdiary/diaries/FaladorDiaryRequirement.java
index dcc738872..5db2a74bb 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/achievementdiary/diaries/FaladorDiaryRequirement.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/achievementdiary/diaries/FaladorDiaryRequirement.java
@@ -90,7 +90,7 @@ public class FaladorDiaryRequirement extends GenericDiaryRequirement
 			new SkillRequirement(Skill.SLAYER, 72));
 		add("Complete a lap of the Falador rooftop agility course.",
 			new SkillRequirement(Skill.AGILITY, 50));
-		add("Enter the mining guild wearing full prospector.",
+		add("Enter the mining guild wearing a Prospector helmet.",
 			new SkillRequirement(Skill.MINING, 60));
 		add("Kill the Blue Dragon under the Heroes' Guild.",
 			new QuestRequirement(Quest.HEROES_QUEST));
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityPlugin.java
index c8a6ea1ba..fc36eeb23 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityPlugin.java
@@ -220,13 +220,13 @@ public class AgilityPlugin extends Plugin
 		int skillGained = agilityXp - lastAgilityXp;
 		lastAgilityXp = agilityXp;
 
+		log.debug("Gained {} xp at {}", skillGained, client.getLocalPlayer().getWorldLocation());
+
 		// Get course
 		Courses course = Courses.getCourse(client.getLocalPlayer().getWorldLocation().getRegionID());
 		if (course == null
 			|| !config.showLapCount()
-			|| (course.getCourseEndWorldPoints().length == 0
-			? Math.abs(course.getLastObstacleXp() - skillGained) > 1
-			: Arrays.stream(course.getCourseEndWorldPoints()).noneMatch(wp -> wp.equals(client.getLocalPlayer().getWorldLocation()))))
+			|| Arrays.stream(course.getCourseEndWorldPoints()).noneMatch(wp -> wp.equals(client.getLocalPlayer().getWorldLocation())))
 		{
 			return;
 		}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/agility/Courses.java b/runelite-client/src/main/java/net/runelite/client/plugins/agility/Courses.java
index ae3b9996a..221a40858 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/agility/Courses.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/agility/Courses.java
@@ -31,34 +31,31 @@ import net.runelite.api.coords.WorldPoint;
 
 enum Courses
 {
-	GNOME(86.5, 46, 9781),
-	SHAYZIEN_BASIC(133.2, 92, 6200),
-	DRAYNOR(120.0, 79, 12338),
-	AL_KHARID(180.0, 0, 13105, new WorldPoint(3299, 3194, 0)),
-	PYRAMID(722.0, 0, 13356, new WorldPoint(3364, 2830, 0)),
-	VARROCK(238.0, 125, 12853),
-	PENGUIN(540.0, 65, 10559),
-	BARBARIAN(139.5, 60, 10039),
-	CANIFIS(240.0, 175, 13878),
-	APE_ATOLL(580.0, 300, 11050),
-	SHAYZIEN_ADVANCED(474.3, 382, 5944),
-	FALADOR(440, 180, 12084),
-	WILDERNESS(571.0, 499, 11837),
-	WEREWOLF(730.0, 380, 14234),
-	SEERS(570.0, 435, 10806),
-	POLLNIVNEACH(890.0, 540, 13358),
-	RELLEKA(780.0, 475, 10553),
-	PRIFDDINAS(1337.0, 1037, 12895),
-	ARDOUGNE(793.0, 529, 10547);
+	GNOME(110.5, 9781, new WorldPoint(2484, 3437, 0), new WorldPoint(2487, 3437, 0)),
+	SHAYZIEN_BASIC(153.5, 6200, new WorldPoint(1554, 3640, 0)),
+	DRAYNOR(120.0, 12338, new WorldPoint(3103, 3261, 0)),
+	AL_KHARID(216.0, 13105, new WorldPoint(3299, 3194, 0)),
+	PYRAMID(722.0, 13356, new WorldPoint(3364, 2830, 0)),
+	VARROCK(270.0, 12853, new WorldPoint(3236, 3417, 0)),
+	PENGUIN(540.0, 10559, new WorldPoint(2652, 4039, 1)),
+	BARBARIAN(153.2, 10039, new WorldPoint(2543, 3553, 0)),
+	CANIFIS(240.0, 13878, new WorldPoint(3510, 3485, 0)),
+	APE_ATOLL(580.0, 11050, new WorldPoint(2770, 2747, 0)),
+	SHAYZIEN_ADVANCED(507.5, 5944, new WorldPoint(1522, 3625, 0)),
+	FALADOR(586, 12084, new WorldPoint(3029, 3332, 0)),
+	WILDERNESS(571.4, 11837, new WorldPoint(2994, 3933, 0)),
+	WEREWOLF(730.0, 14234, new WorldPoint(3528, 9873, 0)),
+	SEERS(570.0, 10806, new WorldPoint(2704, 3464, 0)),
+	POLLNIVNEACH(1016.0, 13358, new WorldPoint(3363, 2998, 0)),
+	RELLEKA(920.0, 10553, new WorldPoint(2653, 3676, 0)),
+	PRIFDDINAS(1337.0, 12895, new WorldPoint(3240, 6109, 0)),
+	ARDOUGNE(889.0, 10547, new WorldPoint(2668, 3297, 0));
 
 	private final static Map<Integer, Courses> coursesByRegion;
 
 	@Getter
 	private final double totalXp;
 
-	@Getter
-	private final int lastObstacleXp;
-
 	@Getter
 	private final int regionId;
 
@@ -77,10 +74,9 @@ enum Courses
 		coursesByRegion = builder.build();
 	}
 
-	Courses(double totalXp, int lastObstacleXp, int regionId, WorldPoint... courseEndWorldPoints)
+	Courses(double totalXp, int regionId, WorldPoint... courseEndWorldPoints)
 	{
 		this.totalXp = totalXp;
-		this.lastObstacleXp = lastObstacleXp;
 		this.regionId = regionId;
 		this.courseEndWorldPoints = courseEndWorldPoints;
 	}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/Boss.java b/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/Boss.java
index f47b75856..524de80f3 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/Boss.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/Boss.java
@@ -44,7 +44,7 @@ enum Boss
 	ARTIO(NpcID.ARTIO, 11, RSTimeUnit.GAME_TICKS, ItemID.CALLISTO_CUB),
 	CHAOS_ELEMENTAL(NpcID.CHAOS_ELEMENTAL, 14, RSTimeUnit.GAME_TICKS, ItemID.PET_CHAOS_ELEMENTAL),
 	CHAOS_FANATIC(NpcID.CHAOS_FANATIC, 14, RSTimeUnit.GAME_TICKS, ItemID.ANCIENT_STAFF),
-	CRAZY_ARCHAEOLOGIST(NpcID.CRAZY_ARCHAEOLOGIST, 30, ChronoUnit.SECONDS, ItemID.FEDORA),
+	CRAZY_ARCHAEOLOGIST(NpcID.CRAZY_ARCHAEOLOGIST, 9, ChronoUnit.SECONDS, ItemID.FEDORA),
 	KING_BLACK_DRAGON(NpcID.KING_BLACK_DRAGON, 9, ChronoUnit.SECONDS, ItemID.PRINCE_BLACK_DRAGON),
 	SCORPIA(NpcID.SCORPIA, 14, RSTimeUnit.GAME_TICKS, ItemID.SCORPIAS_OFFSPRING),
 	SCURRIUS(NpcID.SCURRIUS, 29, RSTimeUnit.GAME_TICKS, ItemID.SCURRY),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java
index c62e666c9..f6ed3e993 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java
@@ -237,7 +237,7 @@ public class CoordinateClue extends ClueScroll implements LocationClueScroll
 		.put(new WorldPoint(3085, 3569, 0), new CoordinateClueInfo("Wilderness. Obelisk of Air.", BRASSICAN_MAGE))
 		.put(new WorldPoint(2934, 2727, 0), new CoordinateClueInfo("Eastern shore of Crash Island.", BRASSICAN_MAGE))
 		.put(new WorldPoint(1451, 3695, 0), new CoordinateClueInfo("West side of Lizardman Canyon with Lizardman shaman.", ANCIENT_WIZARDS))
-		.put(new WorldPoint(2538, 3739, 0), new CoordinateClueInfo("Waterbirth Island. Bring a pet rock and rune thrownaxe.", BRASSICAN_MAGE))
+		.put(new WorldPoint(2538, 3739, 0), new CoordinateClueInfo("Waterbirth Island. Bring a pet rock and rune thrownaxe OR have 85 agility.", BRASSICAN_MAGE))
 		.put(new WorldPoint(1248, 3751, 0), new CoordinateClueInfo("In the north wing of the Farming Guild.", BRASSICAN_MAGE))
 		.put(new WorldPoint(1698, 3792, 0), new CoordinateClueInfo("Arceuus church.", ANCIENT_WIZARDS))
 		.put(new WorldPoint(2951, 3820, 0), new CoordinateClueInfo("Wilderness. Chaos Temple (level 38).", ANCIENT_WIZARDS))
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java
index d1272f3da..57b45e17e 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java
@@ -1070,7 +1070,7 @@ public class CrypticClue extends ClueScroll implements NpcClueScroll, ObjectClue
 		CrypticClue.builder()
 			.text("A ring of water surrounds 4 powerful rings, dig above the ladder located there.")
 			.location(new WorldPoint(1910, 4367, 0))
-			.solution("Dig by the ladder leading to the Dagannoth Kings room in the Waterbirth Island Dungeon. Bring a pet rock and rune thrownaxe.")
+			.solution("Dig by the ladder leading to the Dagannoth Kings room in the Waterbirth Island Dungeon. Bring a pet rock and rune thrownaxe OR have 85 agility.")
 			.build(),
 		CrypticClue.builder()
 			.text("This place sure is a mess.")
@@ -1108,7 +1108,7 @@ public class CrypticClue extends ClueScroll implements NpcClueScroll, ObjectClue
 		CrypticClue.builder()
 			.text("If you're feeling brave, dig beneath the dragon's eye.")
 			.location(new WorldPoint(2410, 4714, 0))
-			.solution("Dig below the mossy rock under the Viyeldi caves (Legend's Quest). Items needed: Pickaxe, unpowered orb, lockpick, spade, any charge orb spell, and either 79 agility or an axe and machete.")
+			.solution("Dig below the mossy rock under the Viyeldi caves (Legend's Quest). Items needed: Pickaxe, unpowered orb, lockpick, spade, any charge orb spell, and either 79 agility or an axe and machete. With 96 agility no items are needed.")
 			.build(),
 		CrypticClue.builder()
 			.text("Search the tents in the Imperial Guard camp in Burthorpe for some boxes.")
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java
index 38366542f..0684df67d 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java
@@ -39,6 +39,7 @@ import java.util.HashMap;
 import java.util.Map;
 import javax.inject.Inject;
 import net.runelite.api.Client;
+import net.runelite.api.ItemID;
 import net.runelite.api.Perspective;
 import net.runelite.api.Player;
 import net.runelite.api.Point;
@@ -225,34 +226,38 @@ public class GroundItemsOverlay extends Overlay
 					.append(')');
 			}
 
-			if (config.priceDisplayMode() == PriceDisplayMode.BOTH)
+			if (item.getId() != ItemID.COINS_995)
 			{
-				if (item.getGePrice() > 0)
+				PriceDisplayMode displayMode = config.priceDisplayMode();
+				if (displayMode == PriceDisplayMode.BOTH)
 				{
-					itemStringBuilder.append(" (GE: ")
-						.append(QuantityFormatter.quantityToStackSize(item.getGePrice()))
-						.append(" gp)");
-				}
+					if (item.getGePrice() > 0)
+					{
+						itemStringBuilder.append(" (GE: ")
+							.append(QuantityFormatter.quantityToStackSize(item.getGePrice()))
+							.append(" gp)");
+					}
 
-				if (item.getHaPrice() > 0)
-				{
-					itemStringBuilder.append(" (HA: ")
-						.append(QuantityFormatter.quantityToStackSize(item.getHaPrice()))
-						.append(" gp)");
+					if (item.getHaPrice() > 0)
+					{
+						itemStringBuilder.append(" (HA: ")
+							.append(QuantityFormatter.quantityToStackSize(item.getHaPrice()))
+							.append(" gp)");
+					}
 				}
-			}
-			else if (config.priceDisplayMode() != PriceDisplayMode.OFF)
-			{
-				final int price = config.priceDisplayMode() == PriceDisplayMode.GE
-					? item.getGePrice()
-					: item.getHaPrice();
-
-				if (price > 0)
+				else if (displayMode != PriceDisplayMode.OFF)
 				{
-					itemStringBuilder
-						.append(" (")
-						.append(QuantityFormatter.quantityToStackSize(price))
-						.append(" gp)");
+					final int price = displayMode == PriceDisplayMode.GE
+						? item.getGePrice()
+						: item.getHaPrice();
+
+					if (price > 0)
+					{
+						itemStringBuilder
+							.append(" (")
+							.append(QuantityFormatter.quantityToStackSize(price))
+							.append(" gp)");
+					}
 				}
 			}
 
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java
index 253058dbf..62ddd9b9d 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java
@@ -79,10 +79,7 @@ import net.runelite.client.callback.ClientThread;
 import net.runelite.client.config.ConfigManager;
 import net.runelite.client.eventbus.Subscribe;
 import net.runelite.client.events.ConfigChanged;
-import net.runelite.client.events.NpcLootReceived;
-import net.runelite.client.events.PlayerLootReceived;
 import net.runelite.client.game.ItemManager;
-import net.runelite.client.game.ItemStack;
 import net.runelite.client.input.KeyManager;
 import net.runelite.client.input.MouseManager;
 import net.runelite.client.plugins.Plugin;
@@ -261,7 +258,7 @@ public class GroundItemsPlugin extends Plugin
 			collectedGroundItems.put(tile.getWorldLocation(), item.getId(), groundItem);
 		}
 
-		if (!config.onlyShowOwnItems())
+		if (groundItem.isMine() || !config.onlyShowOwnItems())
 		{
 			notifyHighlightedItem(groundItem);
 		}
@@ -332,20 +329,6 @@ public class GroundItemsPlugin extends Plugin
 		handleLootbeam(tile.getWorldLocation());
 	}
 
-	@Subscribe
-	public void onNpcLootReceived(NpcLootReceived npcLootReceived)
-	{
-		Collection<ItemStack> items = npcLootReceived.getItems();
-		lootReceived(items);
-	}
-
-	@Subscribe
-	public void onPlayerLootReceived(PlayerLootReceived playerLootReceived)
-	{
-		Collection<ItemStack> items = playerLootReceived.getItems();
-		lootReceived(items);
-	}
-
 	@Subscribe
 	public void onClientTick(ClientTick event)
 	{
@@ -395,29 +378,6 @@ public class GroundItemsPlugin extends Plugin
 		}).toArray(MenuEntry[]::new));
 	}
 
-	private void lootReceived(Collection<ItemStack> items)
-	{
-		for (ItemStack itemStack : items)
-		{
-			WorldPoint location = WorldPoint.fromLocal(client, itemStack.getLocation());
-			GroundItem groundItem = collectedGroundItems.get(location, itemStack.getId());
-			if (groundItem != null)
-			{
-				if (config.onlyShowOwnItems())
-				{
-					notifyHighlightedItem(groundItem);
-				}
-			}
-		}
-
-		// Since the loot can potentially be over multiple tiles, make sure to process lootbeams on all those tiles
-		items.stream()
-			.map(ItemStack::getLocation)
-			.map(l -> WorldPoint.fromLocal(client, l))
-			.distinct()
-			.forEach(this::handleLootbeam);
-	}
-
 	private GroundItem buildGroundItem(final Tile tile, final TileItem item)
 	{
 		// Collect the data for the item
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesPlugin.java
index f624f4d10..f64bb624b 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesPlugin.java
@@ -58,7 +58,7 @@ import net.runelite.client.util.ImageUtil;
 @PluginDescriptor(
 	name = "Interface Styles",
 	description = "Change the interface style to the 2005/2010 interface",
-	tags = {"2005", "2010", "skin", "theme", "ui"},
+	tags = {"2005", "2010", "skin", "theme", "ui", "hp", "bar"},
 	enabledByDefault = false
 )
 public class InterfaceStylesPlugin extends Plugin
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 2d95de301..ecaa869a2 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
@@ -31,7 +31,7 @@ import net.runelite.client.game.ItemStack;
 import net.runelite.http.api.loottracker.LootRecordType;
 
 /**
- * Event published by the loottracker plugin when new loot is received
+ * Event published by the loot tracker when new loot is received
  */
 @Data
 @AllArgsConstructor
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 3bf802401..aa373ab98 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
@@ -395,7 +395,7 @@ public class LootTrackerPlugin extends Plugin
 			}
 			if (quantity > 0)
 			{
-				list.add(new ItemStack(item.getId(), item.getQuantity() + quantity, item.getLocation()));
+				list.add(new ItemStack(item.getId(), item.getQuantity() + quantity));
 			}
 			else
 			{
@@ -744,7 +744,7 @@ public class LootTrackerPlugin extends Plugin
 
 			// convert to item stack
 			var items = diff.entrySet().stream()
-				.map(e -> new ItemStack(e.getElement(), e.getCount(), client.getLocalPlayer().getLocalLocation())) // made up location
+				.map(e -> new ItemStack(e.getElement(), e.getCount()))
 				.collect(Collectors.toList());
 			addLoot(groundSnapshotName, groundSnapshotCombatLevel, LootRecordType.NPC, null, items);
 
@@ -797,6 +797,13 @@ public class LootTrackerPlugin extends Plugin
 		}
 	}
 
+	@Subscribe
+	public void onPluginLootReceived(PluginLootReceived event)
+	{
+		log.debug("Plugin loot received from {}: {}", event.getSource().getName(), event.getItems());
+		addLoot(event.getName(), event.getCombatLevel(), event.getType(), event.getItems(), event.getItems());
+	}
+
 	@Subscribe
 	public void onPlayerLootReceived(final PlayerLootReceived playerLootReceived)
 	{
@@ -920,7 +927,7 @@ public class LootTrackerPlugin extends Plugin
 		// Convert container items to array of ItemStack
 		final Collection<ItemStack> items = Arrays.stream(container.getItems())
 			.filter(item -> item.getId() > 0)
-			.map(item -> new ItemStack(item.getId(), item.getQuantity(), client.getLocalPlayer().getLocalLocation()))
+			.map(item -> new ItemStack(item.getId(), item.getQuantity()))
 			.collect(Collectors.toList());
 
 		if (config.showRaidsLootValue() && (event.equals(THEATRE_OF_BLOOD) || event.equals(CHAMBERS_OF_XERIC) || event.equals(TOMBS_OF_AMASCUT)))
@@ -1153,7 +1160,7 @@ public class LootTrackerPlugin extends Plugin
 		final Multiset<Integer> diffr = Multisets.difference(inventorySnapshot, currentInventory);
 
 		final List<ItemStack> items = diff.entrySet().stream()
-			.map(e -> new ItemStack(e.getElement(), e.getCount(), client.getLocalPlayer().getLocalLocation()))
+			.map(e -> new ItemStack(e.getElement(), e.getCount()))
 			.collect(Collectors.toList());
 
 		log.debug("Inv change: {} Ground items: {}", items, groundItems);
@@ -1419,7 +1426,7 @@ public class LootTrackerPlugin extends Plugin
 			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()));
+				herbs.add(new ItemStack(itemManager.search(matcher.group(1)).get(0).getId(), 1));
 			}
 		}
 
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/PluginLootReceived.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/PluginLootReceived.java
new file mode 100644
index 000000000..0670363ac
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/PluginLootReceived.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2024, Adam <Adam@sigterm.info>
+ * 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.loottracker;
+
+import java.util.Collection;
+import javax.annotation.Nullable;
+import lombok.Builder;
+import lombok.NonNull;
+import lombok.Value;
+import net.runelite.client.game.ItemStack;
+import net.runelite.client.plugins.Plugin;
+import net.runelite.http.api.loottracker.LootRecordType;
+
+/**
+ * Event for plugins to publish new loot to the loot tracker
+ */
+@Builder
+@Value
+public class PluginLootReceived
+{
+	@NonNull
+	Plugin source;
+	@NonNull
+	String name;
+	@Builder.Default
+	int combatLevel = -1;
+	@NonNull
+	LootRecordType type;
+	@NonNull
+	Collection<ItemStack> items;
+	@Builder.Default
+	int amount = 1;
+	@Nullable
+	Object metadata;
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MiningOverlay.java
similarity index 90%
rename from runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeOverlay.java
rename to runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MiningOverlay.java
index d6053fc6d..6167ef654 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeOverlay.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MiningOverlay.java
@@ -55,7 +55,7 @@ import net.runelite.client.ui.overlay.OverlayPosition;
 import net.runelite.client.ui.overlay.components.LineComponent;
 import net.runelite.client.ui.overlay.components.TitleComponent;
 
-class MotherlodeOverlay extends OverlayPanel
+class MiningOverlay extends OverlayPanel
 {
 	private static final Set<Integer> MINING_ANIMATION_IDS = ImmutableSet.of(
 		MINING_MOTHERLODE_BRONZE, MINING_MOTHERLODE_IRON, MINING_MOTHERLODE_STEEL,
@@ -72,8 +72,10 @@ class MotherlodeOverlay extends OverlayPanel
 	private final MotherlodeSession motherlodeSession;
 	private final MotherlodeConfig config;
 
+	private Instant last = Instant.MIN;
+
 	@Inject
-	MotherlodeOverlay(Client client, MotherlodePlugin plugin, MotherlodeSession motherlodeSession, MotherlodeConfig config)
+	MiningOverlay(Client client, MotherlodePlugin plugin, MotherlodeSession motherlodeSession, MotherlodeConfig config)
 	{
 		super(plugin);
 		setPosition(OverlayPosition.TOP_LEFT);
@@ -109,7 +111,14 @@ class MotherlodeOverlay extends OverlayPanel
 
 		if (config.showMiningState())
 		{
+			Instant now = Instant.now();
 			if (MINING_ANIMATION_IDS.contains(client.getLocalPlayer().getAnimation()))
+			{
+				last = now;
+			}
+
+			// Anim regularly goes to -1 when mining, add a small delay after the last anim
+			if (last.isAfter(now.minusMillis(1500)))
 			{
 				panelComponent.getChildren().add(TitleComponent.builder()
 					.text("Mining")
@@ -130,10 +139,13 @@ class MotherlodeOverlay extends OverlayPanel
 			.right(Integer.toString(session.getTotalMined()))
 			.build());
 
-		panelComponent.getChildren().add(LineComponent.builder()
-			.left("Pay-dirt/hr:")
-			.right(session.getRecentMined() > 2 ? Integer.toString(session.getPerHour()) : "")
-			.build());
+		if (session.getRecentMined() > 2)
+		{
+			panelComponent.getChildren().add(LineComponent.builder()
+				.left("Pay-dirt/hr:")
+				.right(Integer.toString(session.getPerHour()))
+				.build());
+		}
 
 		return super.render(graphics);
 	}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeConfig.java
index 92b90498e..777b831eb 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeConfig.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeConfig.java
@@ -106,20 +106,20 @@ public interface MotherlodeConfig extends Config
 
 	@ConfigItem(
 		keyName = "showGemsFound",
-		name = "Show gems found",
-		description = "Shows gems found during current mining session"
+		name = "Track gems found",
+		description = "Tracks gems found from mining in the loot tracker"
 	)
-	default boolean showGemsFound()
+	default boolean trackGemsFound()
 	{
 		return true;
 	}
 
 	@ConfigItem(
 		keyName = "showOresFound",
-		name = "Show ores found",
-		description = "Shows the ores found during current mining session"
+		name = "Track ores found",
+		description = "Tracks ores found from mining in the loot tracker"
 	)
-	default boolean showOresFound()
+	default boolean trackOresFound()
 	{
 		return true;
 	}
@@ -133,14 +133,4 @@ public interface MotherlodeConfig extends Config
 	{
 		return true;
 	}
-
-	@ConfigItem(
-		keyName = "showLootIcons",
-		name = "Show ore icons",
-		description = "Display collected ores and gems as item images instead of text"
-	)
-	default boolean showLootIcons()
-	{
-		return false;
-	}
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeGemOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeGemOverlay.java
deleted file mode 100644
index 07c5395d9..000000000
--- a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeGemOverlay.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (c) 2018, Lars <lars.oernlo@gmail.com>
- * 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.motherlode;
-
-import java.awt.Dimension;
-import java.awt.Graphics2D;
-import java.time.Duration;
-import java.time.Instant;
-import javax.inject.Inject;
-import net.runelite.api.ItemID;
-import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG;
-import net.runelite.client.game.ItemManager;
-import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE;
-import net.runelite.client.ui.overlay.OverlayPanel;
-import net.runelite.client.ui.overlay.OverlayPosition;
-import net.runelite.client.ui.overlay.components.ComponentOrientation;
-import net.runelite.client.ui.overlay.components.ImageComponent;
-import net.runelite.client.ui.overlay.components.LineComponent;
-import net.runelite.client.ui.overlay.components.TitleComponent;
-
-public class MotherlodeGemOverlay extends OverlayPanel
-{
-	private final MotherlodePlugin plugin;
-	private final MotherlodeSession motherlodeSession;
-	private final MotherlodeConfig config;
-	private final ItemManager itemManager;
-
-	@Inject
-	MotherlodeGemOverlay(MotherlodePlugin plugin, MotherlodeSession motherlodeSession, MotherlodeConfig config, ItemManager itemManager)
-	{
-		super(plugin);
-		setPosition(OverlayPosition.TOP_LEFT);
-		this.plugin = plugin;
-		this.motherlodeSession = motherlodeSession;
-		this.config = config;
-		this.itemManager = itemManager;
-		addMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Gem overlay");
-	}
-
-	@Override
-	public Dimension render(Graphics2D graphics)
-	{
-		MotherlodeSession session = motherlodeSession;
-
-		if (session.getLastGemFound() == null || !plugin.isInMlm() || !config.showGemsFound())
-		{
-			return null;
-		}
-
-		Duration statTimeout = Duration.ofMinutes(config.statTimeout());
-		Duration sinceLastGem = Duration.between(session.getLastGemFound(), Instant.now());
-
-		if (sinceLastGem.compareTo(statTimeout) >= 0)
-		{
-			return null;
-		}
-
-		int diamondsFound = session.getDiamondsFound();
-		int rubiesFound = session.getRubiesFound();
-		int emeraldsFound = session.getEmeraldsFound();
-		int sapphiresFound = session.getSapphiresFound();
-
-		if (config.showLootIcons())
-		{
-			panelComponent.setOrientation(ComponentOrientation.HORIZONTAL);
-			if (diamondsFound > 0)
-			{
-				panelComponent.getChildren().add(new ImageComponent(itemManager.getImage(ItemID.UNCUT_DIAMOND, diamondsFound, true)));
-			}
-			if (rubiesFound > 0)
-			{
-				panelComponent.getChildren().add(new ImageComponent(itemManager.getImage(ItemID.UNCUT_RUBY, rubiesFound, true)));
-			}
-			if (emeraldsFound > 0)
-			{
-				panelComponent.getChildren().add(new ImageComponent(itemManager.getImage(ItemID.UNCUT_EMERALD, emeraldsFound, true)));
-			}
-			if (sapphiresFound > 0)
-			{
-				panelComponent.getChildren().add(new ImageComponent(itemManager.getImage(ItemID.UNCUT_SAPPHIRE, sapphiresFound, true)));
-			}
-		}
-		else
-		{
-			panelComponent.setOrientation(ComponentOrientation.VERTICAL);
-			panelComponent.getChildren().add(TitleComponent.builder().text("Gems found").build());
-			if (diamondsFound > 0)
-			{
-				panelComponent.getChildren().add(LineComponent.builder()
-					.left("Diamonds:")
-					.right(Integer.toString(diamondsFound))
-					.build());
-			}
-
-			if (rubiesFound > 0)
-			{
-				panelComponent.getChildren().add(LineComponent.builder()
-					.left("Rubies:")
-					.right(Integer.toString(rubiesFound))
-					.build());
-			}
-
-			if (emeraldsFound > 0)
-			{
-				panelComponent.getChildren().add(LineComponent.builder()
-					.left("Emeralds:")
-					.right(Integer.toString(emeraldsFound))
-					.build());
-			}
-
-			if (sapphiresFound > 0)
-			{
-				panelComponent.getChildren().add(LineComponent.builder()
-					.left("Sapphires:")
-					.right(Integer.toString(sapphiresFound))
-					.build());
-			}
-		}
-
-		return super.render(graphics);
-	}
-}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeOreOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeOreOverlay.java
deleted file mode 100644
index 4c7a2c50f..000000000
--- a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeOreOverlay.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (c) 2019, Sir Girion <https://github.com/sirgirion>
- * 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.motherlode;
-
-import java.awt.Dimension;
-import java.awt.Graphics2D;
-import javax.inject.Inject;
-import net.runelite.api.ItemID;
-import net.runelite.client.game.ItemManager;
-import net.runelite.client.ui.overlay.OverlayPanel;
-import net.runelite.client.ui.overlay.OverlayPosition;
-import net.runelite.client.ui.overlay.components.ComponentOrientation;
-import net.runelite.client.ui.overlay.components.ImageComponent;
-import net.runelite.client.ui.overlay.components.LineComponent;
-import net.runelite.client.ui.overlay.components.TitleComponent;
-
-public class MotherlodeOreOverlay extends OverlayPanel
-{
-	private final MotherlodePlugin plugin;
-	private final MotherlodeSession motherlodeSession;
-	private final MotherlodeConfig config;
-	private final ItemManager itemManager;
-
-	@Inject
-	MotherlodeOreOverlay(MotherlodePlugin plugin, MotherlodeSession motherlodeSession, MotherlodeConfig config, ItemManager itemManager)
-	{
-		setPosition(OverlayPosition.TOP_LEFT);
-		this.plugin = plugin;
-		this.motherlodeSession = motherlodeSession;
-		this.config = config;
-		this.itemManager = itemManager;
-	}
-
-	@Override
-	public Dimension render(Graphics2D graphics)
-	{
-		if (!plugin.isInMlm() || !config.showOresFound())
-		{
-			return null;
-		}
-
-		MotherlodeSession session = motherlodeSession;
-
-		int nuggetsFound = session.getNuggetsFound();
-		int coalFound = session.getCoalFound();
-		int goldFound = session.getGoldFound();
-		int mithrilFound = session.getMithrilFound();
-		int adamantiteFound = session.getAdamantiteFound();
-		int runiteFound = session.getRuniteFound();
-
-		// If no ores have even been collected, don't bother showing anything
-		if (nuggetsFound == 0 && coalFound == 0 && goldFound == 0 && mithrilFound == 0
-			&& adamantiteFound == 0 && runiteFound == 0)
-		{
-			return null;
-		}
-
-		if (config.showLootIcons())
-		{
-			panelComponent.setOrientation(ComponentOrientation.HORIZONTAL);
-			if (nuggetsFound > 0)
-			{
-				panelComponent.getChildren().add(new ImageComponent(itemManager.getImage(ItemID.GOLDEN_NUGGET, nuggetsFound, true)));
-			}
-			if (coalFound > 0)
-			{
-				panelComponent.getChildren().add(new ImageComponent(itemManager.getImage(ItemID.COAL, coalFound, true)));
-			}
-			if (goldFound > 0)
-			{
-				panelComponent.getChildren().add(new ImageComponent(itemManager.getImage(ItemID.GOLD_ORE, goldFound, true)));
-			}
-			if (mithrilFound > 0)
-			{
-				panelComponent.getChildren().add(new ImageComponent(itemManager.getImage(ItemID.MITHRIL_ORE, mithrilFound, true)));
-			}
-			if (adamantiteFound > 0)
-			{
-				panelComponent.getChildren().add(new ImageComponent(itemManager.getImage(ItemID.ADAMANTITE_ORE, adamantiteFound, true)));
-			}
-			if (runiteFound > 0)
-			{
-				panelComponent.getChildren().add(new ImageComponent(itemManager.getImage(ItemID.RUNITE_ORE, runiteFound, true)));
-			}
-		}
-		else
-		{
-			panelComponent.setOrientation(ComponentOrientation.VERTICAL);
-			panelComponent.getChildren().add(TitleComponent.builder().text("Ores found").build());
-			if (nuggetsFound > 0)
-			{
-				panelComponent.getChildren().add(LineComponent.builder()
-					.left("Nuggets:")
-					.right(Integer.toString(nuggetsFound))
-					.build());
-			}
-			if (coalFound > 0)
-			{
-				panelComponent.getChildren().add(LineComponent.builder()
-					.left("Coal:")
-					.right(Integer.toString(coalFound))
-					.build());
-			}
-			if (goldFound > 0)
-			{
-				panelComponent.getChildren().add(LineComponent.builder()
-					.left("Gold:")
-					.right(Integer.toString(goldFound))
-					.build());
-			}
-			if (mithrilFound > 0)
-			{
-				panelComponent.getChildren().add(LineComponent.builder()
-					.left("Mithril:")
-					.right(Integer.toString(mithrilFound))
-					.build());
-			}
-			if (adamantiteFound > 0)
-			{
-				panelComponent.getChildren().add(LineComponent.builder()
-					.left("Adamantite:")
-					.right(Integer.toString(adamantiteFound))
-					.build());
-			}
-			if (runiteFound > 0)
-			{
-				panelComponent.getChildren().add(LineComponent.builder()
-					.left("Runite:")
-					.right(Integer.toString(runiteFound))
-					.build());
-			}
-		}
-
-		return super.render(graphics);
-	}
-}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodePlugin.java
index 0618aacbf..8130c714b 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodePlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodePlugin.java
@@ -35,8 +35,10 @@ import java.time.Duration;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.stream.Collectors;
 import javax.inject.Inject;
 import lombok.AccessLevel;
 import lombok.Getter;
@@ -71,11 +73,15 @@ import net.runelite.api.widgets.ComponentID;
 import net.runelite.api.widgets.Widget;
 import net.runelite.client.callback.ClientThread;
 import net.runelite.client.config.ConfigManager;
+import net.runelite.client.eventbus.EventBus;
 import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.game.ItemStack;
 import net.runelite.client.plugins.Plugin;
 import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.plugins.loottracker.PluginLootReceived;
 import net.runelite.client.task.Schedule;
 import net.runelite.client.ui.overlay.OverlayManager;
+import net.runelite.http.api.loottracker.LootRecordType;
 
 @PluginDescriptor(
 	name = "Motherlode Mine",
@@ -102,7 +108,7 @@ public class MotherlodePlugin extends Plugin
 	private OverlayManager overlayManager;
 
 	@Inject
-	private MotherlodeOverlay overlay;
+	private MiningOverlay miningOverlay;
 
 	@Inject
 	private MotherlodeSceneOverlay sceneOverlay;
@@ -110,12 +116,6 @@ public class MotherlodePlugin extends Plugin
 	@Inject
 	private MotherlodeSackOverlay motherlodeSackOverlay;
 
-	@Inject
-	private MotherlodeGemOverlay motherlodeGemOverlay;
-
-	@Inject
-	private MotherlodeOreOverlay motherlodeOreOverlay;
-
 	@Inject
 	private MotherlodeConfig config;
 
@@ -125,6 +125,9 @@ public class MotherlodePlugin extends Plugin
 	@Inject
 	private ClientThread clientThread;
 
+	@Inject
+	private EventBus eventBus;
+
 	@Getter(AccessLevel.PACKAGE)
 	private boolean inMlm;
 
@@ -156,10 +159,8 @@ public class MotherlodePlugin extends Plugin
 	@Override
 	protected void startUp()
 	{
-		overlayManager.add(overlay);
+		overlayManager.add(miningOverlay);
 		overlayManager.add(sceneOverlay);
-		overlayManager.add(motherlodeGemOverlay);
-		overlayManager.add(motherlodeOreOverlay);
 		overlayManager.add(motherlodeSackOverlay);
 
 		inMlm = checkInMlm();
@@ -173,10 +174,8 @@ public class MotherlodePlugin extends Plugin
 	@Override
 	protected void shutDown()
 	{
-		overlayManager.remove(overlay);
+		overlayManager.remove(miningOverlay);
 		overlayManager.remove(sceneOverlay);
-		overlayManager.remove(motherlodeGemOverlay);
-		overlayManager.remove(motherlodeOreOverlay);
 		overlayManager.remove(motherlodeSackOverlay);
 		veins.clear();
 		rocks.clear();
@@ -238,23 +237,46 @@ public class MotherlodePlugin extends Plugin
 				break;
 
 			case "You just found a Diamond!":
-				session.incrementGemFound(ItemID.UNCUT_DIAMOND);
+				if (config.trackGemsFound())
+				{
+					broadcastLootItem(ItemID.UNCUT_DIAMOND);
+				}
 				break;
 
 			case "You just found a Ruby!":
-				session.incrementGemFound(ItemID.UNCUT_RUBY);
+				if (config.trackGemsFound())
+				{
+					broadcastLootItem(ItemID.UNCUT_RUBY);
+				}
 				break;
 
 			case "You just found an Emerald!":
-				session.incrementGemFound(ItemID.UNCUT_EMERALD);
+				if (config.trackGemsFound())
+				{
+					broadcastLootItem(ItemID.UNCUT_EMERALD);
+				}
 				break;
 
 			case "You just found a Sapphire!":
-				session.incrementGemFound(ItemID.UNCUT_SAPPHIRE);
+				if (config.trackGemsFound())
+				{
+					broadcastLootItem(ItemID.UNCUT_SAPPHIRE);
+				}
 				break;
 		}
 	}
 
+	private void broadcastLootItem(int itemId)
+	{
+		var lootEvent = PluginLootReceived.builder()
+			.source(this)
+			.name("Motherlode Mine")
+			.type(LootRecordType.EVENT)
+			.items(Collections.singleton(new ItemStack(itemId, 1, client.getLocalPlayer().getLocalLocation())))
+			.build();
+		eventBus.post(lootEvent);
+	}
+
 	@Schedule(
 		period = 1,
 		unit = ChronoUnit.SECONDS
@@ -357,7 +379,7 @@ public class MotherlodePlugin extends Plugin
 	{
 		final ItemContainer container = event.getItemContainer();
 
-		if (!inMlm || !shouldUpdateOres || inventorySnapshot == null || container != client.getItemContainer(InventoryID.INVENTORY))
+		if (!inMlm || !shouldUpdateOres || inventorySnapshot == null || container.getId() != InventoryID.INVENTORY.getId())
 		{
 			return;
 		}
@@ -371,8 +393,20 @@ public class MotherlodePlugin extends Plugin
 		// Take the difference
 		Multiset<Integer> delta = Multisets.difference(current, inventorySnapshot);
 
-		// Update the session
-		delta.forEachEntry(session::updateOreFound);
+		// Advertise the loot
+		var lootEvent = PluginLootReceived.builder()
+			.source(this)
+			.name("Motherlode Mine")
+			.type(LootRecordType.EVENT)
+			.items(delta.entrySet().stream()
+				.map(e -> new ItemStack(e.getElement(), e.getCount()))
+				.collect(Collectors.toList()))
+			.build();
+		if (config.trackOresFound())
+		{
+			eventBus.post(lootEvent);
+		}
+
 		inventorySnapshot = null;
 		shouldUpdateOres = false;
 	}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeSession.java b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeSession.java
index e4d00cfda..3f8133998 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeSession.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeSession.java
@@ -27,16 +27,15 @@ package net.runelite.client.plugins.motherlode;
 import java.time.Duration;
 import java.time.Instant;
 import javax.inject.Singleton;
-import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
-import net.runelite.api.ItemID;
 
+@Getter
 @Slf4j
 @Singleton
-public class MotherlodeSession
+class MotherlodeSession
 {
-	private static final Duration HOUR = Duration.ofHours(1);
+	private static final long HOUR = Duration.ofHours(1).toMillis();
 
 	private int perHour;
 
@@ -46,93 +45,6 @@ public class MotherlodeSession
 	private Instant recentPayDirtMined;
 	private int recentMined;
 
-	@Getter(AccessLevel.PACKAGE)
-	private Instant lastGemFound;
-
-	@Getter(AccessLevel.PACKAGE)
-	private int diamondsFound;
-
-	@Getter(AccessLevel.PACKAGE)
-	private int rubiesFound;
-
-	@Getter(AccessLevel.PACKAGE)
-	private int emeraldsFound;
-
-	@Getter(AccessLevel.PACKAGE)
-	private int sapphiresFound;
-
-	@Getter(AccessLevel.PACKAGE)
-	private int nuggetsFound;
-
-	@Getter(AccessLevel.PACKAGE)
-	private int coalFound;
-
-	@Getter(AccessLevel.PACKAGE)
-	private int goldFound;
-
-	@Getter(AccessLevel.PACKAGE)
-	private int mithrilFound;
-
-	@Getter(AccessLevel.PACKAGE)
-	private int adamantiteFound;
-
-	@Getter(AccessLevel.PACKAGE)
-	private int runiteFound;
-
-	void incrementGemFound(int gemID)
-	{
-		lastGemFound = Instant.now();
-
-		switch (gemID)
-		{
-			case ItemID.UNCUT_DIAMOND:
-				diamondsFound++;
-				break;
-
-			case ItemID.UNCUT_RUBY:
-				rubiesFound++;
-				break;
-
-			case ItemID.UNCUT_EMERALD:
-				emeraldsFound++;
-				break;
-
-			case ItemID.UNCUT_SAPPHIRE:
-				sapphiresFound++;
-				break;
-
-			default:
-				log.debug("Invalid gem type specified. The gem count will not be incremented.");
-		}
-	}
-
-	void updateOreFound(int item, int count)
-	{
-		switch (item)
-		{
-			case ItemID.GOLDEN_NUGGET:
-				nuggetsFound += count;
-				break;
-			case ItemID.COAL:
-				coalFound += count;
-				break;
-			case ItemID.GOLD_ORE:
-				goldFound += count;
-				break;
-			case ItemID.MITHRIL_ORE:
-				mithrilFound += count;
-				break;
-			case ItemID.ADAMANTITE_ORE:
-				adamantiteFound += count;
-				break;
-			case ItemID.RUNITE_ORE:
-				runiteFound += count;
-				break;
-			default:
-				log.debug("Invalid ore specified. The ore count will not be updated.");
-		}
-	}
-
 	public void incrementPayDirtMined()
 	{
 		Instant now = Instant.now();
@@ -149,7 +61,7 @@ public class MotherlodeSession
 		Duration timeSinceStart = Duration.between(recentPayDirtMined, now);
 		if (!timeSinceStart.isZero())
 		{
-			perHour = (int) ((double) recentMined * (double) HOUR.toMillis() / (double) timeSinceStart.toMillis());
+			perHour = (int) ((double) recentMined * (double) HOUR / (double) timeSinceStart.toMillis());
 		}
 	}
 
@@ -159,28 +71,4 @@ public class MotherlodeSession
 		recentMined = 0;
 	}
 
-	public int getPerHour()
-	{
-		return perHour;
-	}
-
-	public Instant getLastPayDirtMined()
-	{
-		return lastPayDirtMined;
-	}
-
-	public int getTotalMined()
-	{
-		return totalMined;
-	}
-
-	public Instant getRecentPayDirtMined()
-	{
-		return recentPayDirtMined;
-	}
-
-	public int getRecentMined()
-	{
-		return recentMined;
-	}
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mta/alchemy/AlchemyItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/mta/alchemy/AlchemyItem.java
index b059de024..026f5de5f 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/mta/alchemy/AlchemyItem.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mta/alchemy/AlchemyItem.java
@@ -27,7 +27,7 @@ package net.runelite.client.plugins.mta.alchemy;
 import lombok.Getter;
 import net.runelite.api.ItemID;
 
-public enum AlchemyItem
+enum AlchemyItem
 {
 	LEATHER_BOOTS("Leather Boots", ItemID.LEATHER_BOOTS_6893),
 	ADAMANT_KITESHIELD("Adamant Kiteshield", ItemID.ADAMANT_KITESHIELD_6894),
@@ -35,7 +35,6 @@ public enum AlchemyItem
 	EMERALD("Emerald", ItemID.EMERALD_6896),
 	RUNE_LONGSWORD("Rune Longsword", ItemID.RUNE_LONGSWORD_6897),
 	EMPTY("", -1),
-	POSSIBLY_EMPTY("", ItemID.CAKE_OF_GUIDANCE),
 	UNKNOWN("Unknown", ItemID.CAKE_OF_GUIDANCE);
 
 	@Getter
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mta/alchemy/AlchemyRoom.java b/runelite-client/src/main/java/net/runelite/client/plugins/mta/alchemy/AlchemyRoom.java
index 2c7059b9a..18c605739 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/mta/alchemy/AlchemyRoom.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mta/alchemy/AlchemyRoom.java
@@ -49,10 +49,6 @@ import static net.runelite.api.ObjectID.CUPBOARD_23686;
 import static net.runelite.api.ObjectID.CUPBOARD_23687;
 import static net.runelite.api.ObjectID.CUPBOARD_23688;
 import static net.runelite.api.ObjectID.CUPBOARD_23689;
-import static net.runelite.api.ObjectID.CUPBOARD_23690;
-import static net.runelite.api.ObjectID.CUPBOARD_23691;
-import static net.runelite.api.ObjectID.CUPBOARD_23692;
-import static net.runelite.api.ObjectID.CUPBOARD_23693;
 import net.runelite.api.Perspective;
 import net.runelite.api.Player;
 import net.runelite.api.Point;
@@ -77,7 +73,7 @@ public class AlchemyRoom extends MTARoom
 	private static final int MTA_ALCH_REGION = 13462;
 
 	private static final int IMAGE_Z_OFFSET = 150;
-	private static final int NUM_CUPBOARDS = 8;
+	private static final int NUM_CUPBOARDS = 6;
 	private static final int INFO_ITEM_START = 7;
 	private static final int INFO_POINT_START = 12;
 	private static final int INFO_LENGTH = 5;
@@ -131,13 +127,11 @@ public class AlchemyRoom extends MTARoom
 			Arrays.stream(cupboards)
 				.filter(Objects::nonNull)
 				.forEach(e -> e.alchemyItem = AlchemyItem.UNKNOWN);
-		}
 
-		Cupboard newSuggestion = getSuggestion();
-		if (suggestion == null || newSuggestion == null || suggestion.alchemyItem != newSuggestion.alchemyItem)
-		{
-			suggestion = newSuggestion;
+			client.clearHintArrow();
 		}
+
+		suggestion = getSuggestion();
 	}
 
 
@@ -185,16 +179,6 @@ public class AlchemyRoom extends MTARoom
 				cupboardId = 5;
 				break;
 
-			case CUPBOARD_23690:
-			case CUPBOARD_23691:
-				cupboardId = 6;
-				break;
-
-			case CUPBOARD_23692:
-			case CUPBOARD_23693:
-				cupboardId = 7;
-				break;
-
 			default:
 				return;
 
@@ -255,24 +239,10 @@ public class AlchemyRoom extends MTARoom
 			else if (message.equals(EMPTY))
 			{
 				Cupboard clicked = getClicked();
-
-				int idx = Arrays.asList(cupboards).indexOf(clicked);
-				for (int i = -2; i <= 2; ++i)
+				if (clicked.alchemyItem != AlchemyItem.EMPTY)
 				{
-					int j = (idx + i) % 8;
-					if (j < 0)
-					{
-						j = 8 + j;
-					}
-
-					Cupboard cupboard = cupboards[j];
-					if (cupboard != null && cupboard.alchemyItem == AlchemyItem.UNKNOWN)
-					{
-						cupboard.alchemyItem = AlchemyItem.POSSIBLY_EMPTY;
-					}
+					fill(clicked, AlchemyItem.EMPTY);
 				}
-
-				clicked.alchemyItem = AlchemyItem.EMPTY;
 			}
 		}
 	}
@@ -420,29 +390,7 @@ public class AlchemyRoom extends MTARoom
 			}
 		}
 
-		// otherwise find the closest cupboard which can not be empty
-		Cupboard nearest = null;
-		int distance = -1;
-
-		WorldPoint mine = client.getLocalPlayer().getWorldLocation();
-
-		for (Cupboard cupboard : cupboards)
-		{
-			if (cupboard == null || cupboard.alchemyItem == AlchemyItem.EMPTY || cupboard.alchemyItem == AlchemyItem.POSSIBLY_EMPTY)
-			{
-				continue;
-			}
-
-			int objectDistance = cupboard.gameObject.getWorldLocation().distanceTo(mine);
-
-			if (nearest == null || objectDistance < distance)
-			{
-				nearest = cupboard;
-				distance = objectDistance;
-			}
-		}
-
-		return nearest;
+		return null;
 	}
 
 
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mta/alchemy/AlchemyRoomTimer.java b/runelite-client/src/main/java/net/runelite/client/plugins/mta/alchemy/AlchemyRoomTimer.java
index a28dbedc9..d5c3cbf95 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/mta/alchemy/AlchemyRoomTimer.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mta/alchemy/AlchemyRoomTimer.java
@@ -32,7 +32,7 @@ import net.runelite.client.util.ImageUtil;
 
 public class AlchemyRoomTimer extends Timer
 {
-	private static final int RESET_PERIOD = 42;
+	private static final int RESET_PERIOD = 60;
 	private static BufferedImage image;
 
 	public AlchemyRoomTimer(Plugin plugin)
diff --git a/runelite-client/src/main/resources/item_variations.json b/runelite-client/src/main/resources/item_variations.json
index 2eeeffae4..dea486cfe 100644
--- a/runelite-client/src/main/resources/item_variations.json
+++ b/runelite-client/src/main/resources/item_variations.json
@@ -3149,6 +3149,10 @@
     2994,
     2995
   ],
+  "agility arena ticket": [
+    2996,
+    29480
+  ],
   "energy potion": [
     3008,
     3010,
@@ -8018,6 +8022,22 @@
     24851,
     25533
   ],
+  "prospector helmet": [
+    12013,
+    29472
+  ],
+  "prospector jacket": [
+    12014,
+    29474
+  ],
+  "prospector legs": [
+    12015,
+    29476
+  ],
+  "prospector boots": [
+    12016,
+    29478
+  ],
   "black wizard robe": [
     12449,
     12451


Test patch:
diff --git a/runelite-client/src/test/java/net/runelite/client/config/KeybindTest.java b/runelite-client/src/test/java/net/runelite/client/config/KeybindTest.java
index 14c1a34f2..27396fe4e 100644
--- a/runelite-client/src/test/java/net/runelite/client/config/KeybindTest.java
+++ b/runelite-client/src/test/java/net/runelite/client/config/KeybindTest.java
@@ -31,6 +31,7 @@ import static java.awt.event.KeyEvent.KEY_PRESSED;
 import static java.awt.event.KeyEvent.KEY_RELEASED;
 import static java.awt.event.KeyEvent.VK_CONTROL;
 import static java.awt.event.KeyEvent.VK_SHIFT;
+import static java.awt.event.KeyEvent.VK_UNDEFINED;
 import org.junit.Assert;
 import org.junit.Test;
 import org.mockito.Mockito;
@@ -53,6 +54,13 @@ public class KeybindTest
 		Assert.assertFalse(keybind.matches(createKeyEvent(KEY_RELEASED, 0, VK_SHIFT)));
 	}
 
+	@Test
+	public void testUnknownKey()
+	{
+		Keybind keybind = new Keybind(VK_UNDEFINED, CTRL_DOWN_MASK);
+		keybind.matches(createKeyEvent(KEY_RELEASED, 0, VK_UNDEFINED));
+	}
+
 	private KeyEvent createKeyEvent(int id, int modifiersEx, int extendedKeyCode)
 	{
 		KeyEvent mock = Mockito.mock(KeyEvent.class);
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 6fe32737b..4c7a08292 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
@@ -184,7 +184,7 @@ public class LootTrackerPluginTest
 		lootTrackerPlugin.onChatMessage(chatMessage);
 
 		List<ItemStack> items = Collections.singletonList(
-			new ItemStack(ItemID.COINS_995, 1, null)
+			new ItemStack(ItemID.COINS_995, 1)
 		);
 		sendInvChange(InventoryID.INVENTORY, items);
 
@@ -198,7 +198,7 @@ public class LootTrackerPluginTest
 		lootTrackerPlugin.onChatMessage(chatMessage);
 
 		List<ItemStack> items = Collections.singletonList(
-			new ItemStack(ItemID.COINS_995, 1, null)
+			new ItemStack(ItemID.COINS_995, 1)
 		);
 		sendInvChange(InventoryID.BARROWS_REWARD, items);
 
@@ -241,8 +241,8 @@ public class LootTrackerPluginTest
 			lootTrackerPlugin.onChatMessage(chatMessage);
 
 			verify(lootTrackerPlugin).addLoot("Herbiboar", -1, LootRecordType.EVENT, 42, Arrays.asList(
-				new ItemStack(id, 1, null),
-				new ItemStack(id, 1, null)
+				new ItemStack(id, 1),
+				new ItemStack(id, 1)
 			));
 		}
 	}
@@ -320,7 +320,7 @@ public class LootTrackerPluginTest
 	{
 		when(client.getBoostedSkillLevel(Skill.HUNTER)).thenReturn(42);
 		List<ItemStack> items = Collections.singletonList(
-			new ItemStack(ItemID.BIRD_NEST, 42, null)
+			new ItemStack(ItemID.BIRD_NEST, 42)
 		);
 
 		// No bird nests
@@ -384,7 +384,7 @@ public class LootTrackerPluginTest
 		lootTrackerPluginSpy.onItemContainerChanged(new ItemContainerChanged(InventoryID.INVENTORY.getId(), itemContainer));
 
 		verify(lootTrackerPluginSpy).addLoot("Grubby Chest", -1, LootRecordType.EVENT, null, Arrays.asList(
-			new ItemStack(ItemID.SHARK, 42, null)
+			new ItemStack(ItemID.SHARK, 42)
 		));
 	}
 
@@ -416,7 +416,7 @@ public class LootTrackerPluginTest
 		lootTrackerPlugin.onItemContainerChanged(new ItemContainerChanged(InventoryID.INVENTORY.getId(), itemContainer));
 
 		verify(lootTrackerPlugin).addLoot("Reward pool (Tempoross)", -1, LootRecordType.EVENT, 69, Arrays.asList(
-			new ItemStack(ItemID.RAW_TUNA, 30, null)
+			new ItemStack(ItemID.RAW_TUNA, 30)
 		));
 
 		chatMessage = new ChatMessage(null, ChatMessageType.SPAM, "", "You found some loot: <col=ef1020>Tome of water (empty)</col>", "", 0);
@@ -431,7 +431,7 @@ public class LootTrackerPluginTest
 		lootTrackerPlugin.onItemContainerChanged(new ItemContainerChanged(InventoryID.INVENTORY.getId(), itemContainer));
 
 		verify(lootTrackerPlugin).addLoot("Reward pool (Tempoross)", -1, LootRecordType.EVENT, 69, Arrays.asList(
-			new ItemStack(ItemID.TOME_OF_WATER_EMPTY, 1, null)
+			new ItemStack(ItemID.TOME_OF_WATER_EMPTY, 1)
 		));
 	}
 
@@ -460,7 +460,7 @@ public class LootTrackerPluginTest
 		spyPlugin.onWidgetLoaded(widgetLoaded);
 
 		verify(spyPlugin).addLoot("Theatre of Blood", -1, LootRecordType.EVENT, null, Collections.singletonList(
-			new ItemStack(ItemID.SCYTHE_OF_VITUR_UNCHARGED, 1, null)
+			new ItemStack(ItemID.SCYTHE_OF_VITUR_UNCHARGED, 1)
 		));
 	}
 
@@ -489,7 +489,7 @@ public class LootTrackerPluginTest
 		spyPlugin.onWidgetLoaded(widgetLoaded);
 
 		verify(spyPlugin).addLoot("Theatre of Blood", -1, LootRecordType.EVENT, null, Collections.singletonList(
-			new ItemStack(ItemID.SCYTHE_OF_VITUR_UNCHARGED, 1, null)
+			new ItemStack(ItemID.SCYTHE_OF_VITUR_UNCHARGED, 1)
 		));
 	}
 
@@ -518,7 +518,7 @@ public class LootTrackerPluginTest
 		spyPlugin.onWidgetLoaded(widgetLoaded);
 
 		verify(spyPlugin).addLoot("Theatre of Blood", -1, LootRecordType.EVENT, null, Collections.singletonList(
-			new ItemStack(ItemID.SCYTHE_OF_VITUR_UNCHARGED, 1, null)
+			new ItemStack(ItemID.SCYTHE_OF_VITUR_UNCHARGED, 1)
 		));
 
 		when(itemContainer.getItems()).thenReturn(new Item[]{
@@ -529,7 +529,7 @@ public class LootTrackerPluginTest
 		spyPlugin.onWidgetLoaded(widgetLoaded);
 
 		verify(spyPlugin).addLoot("Theatre of Blood", -1, LootRecordType.EVENT, null, Collections.singletonList(
-			new ItemStack(ItemID.SANGUINESTI_STAFF_UNCHARGED, 1, null)
+			new ItemStack(ItemID.SANGUINESTI_STAFF_UNCHARGED, 1)
 		));
 	}
 }
diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/motherlode/MotherlodePluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/motherlode/MotherlodePluginTest.java
index bf40320d1..d2d42a444 100644
--- a/runelite-client/src/test/java/net/runelite/client/plugins/motherlode/MotherlodePluginTest.java
+++ b/runelite-client/src/test/java/net/runelite/client/plugins/motherlode/MotherlodePluginTest.java
@@ -27,6 +27,7 @@ package net.runelite.client.plugins.motherlode;
 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.concurrent.ScheduledExecutorService;
 import javax.inject.Inject;
 import net.runelite.api.Client;
@@ -39,14 +40,18 @@ import net.runelite.api.Varbits;
 import net.runelite.api.events.GameStateChanged;
 import net.runelite.api.events.ItemContainerChanged;
 import net.runelite.api.events.VarbitChanged;
+import net.runelite.client.eventbus.EventBus;
+import net.runelite.client.game.ItemStack;
+import net.runelite.client.plugins.loottracker.PluginLootReceived;
 import net.runelite.client.ui.overlay.OverlayManager;
+import static org.junit.Assert.assertEquals;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 import org.mockito.junit.MockitoJUnitRunner;
 
@@ -68,14 +73,6 @@ public class MotherlodePluginTest
 	@Bind
 	private MotherlodeConfig motherlodeConfig;
 
-	@Mock
-	@Bind
-	private MotherlodeGemOverlay motherlodeGemOverlay;
-
-	@Mock
-	@Bind
-	private MotherlodeOreOverlay motherlodeOreOverlay;
-
 	@Mock
 	@Bind
 	private MotherlodeSceneOverlay motherlodeSceneOverlay;
@@ -92,6 +89,10 @@ public class MotherlodePluginTest
 	@Bind
 	private OverlayManager overlayManager;
 
+	@Mock
+	@Bind
+	private EventBus eventBus;
+
 	@Before
 	public void before()
 	{
@@ -149,16 +150,21 @@ public class MotherlodePluginTest
 		};
 		when(inventory.getItems())
 			.thenReturn(items);
-		when(client.getItemContainer(InventoryID.INVENTORY)).thenReturn(inventory);
+		when(inventory.getId()).thenReturn(InventoryID.INVENTORY.getId());
 
 		// Trigger comparison
+		when(motherlodeConfig.trackOresFound()).thenReturn(true);
 		motherlodePlugin.onItemContainerChanged(new ItemContainerChanged(InventoryID.INVENTORY.getId(), inventory));
 
-		verify(motherlodeSession).updateOreFound(ItemID.RUNITE_ORE, 1);
-		verify(motherlodeSession).updateOreFound(ItemID.GOLDEN_NUGGET, 4);
-		verify(motherlodeSession).updateOreFound(ItemID.COAL, 2);
-		verify(motherlodeSession).updateOreFound(ItemID.ADAMANTITE_ORE, 1);
-		verifyNoMoreInteractions(motherlodeSession);
+		ArgumentCaptor<PluginLootReceived> captor = ArgumentCaptor.forClass(PluginLootReceived.class);
+		verify(eventBus).post(captor.capture());
+		PluginLootReceived event = captor.getValue();
+		assertEquals(Arrays.asList(
+			new ItemStack(ItemID.ADAMANTITE_ORE, 1),
+			new ItemStack(ItemID.RUNITE_ORE, 1),
+			new ItemStack(ItemID.COAL, 2),
+			new ItemStack(ItemID.GOLDEN_NUGGET, 4)
+		), event.getItems());
 	}
 
 	private static Item item(int itemId, int quantity)