Repository: runelite/runelite
Pull request #18349 (2024-09-28 02:33:43)

worldmap: add spider cave teleport
Adds the spider cave teleport to the worldmap. Based on 50-60 teleports (thanks beta worlds!), it's a 3x3 this time.

![teleportlocations](https://github.com/user-attachments/assets/a105447a-40a2-4f6d-a46b-606645f3b984)

![worldmap](https://github.com/user-attachments/assets/f8b6cd4e-f070-4b10-b8db-b2878dd05ac6)



Patch:
diff --git a/cache/pom.xml b/cache/pom.xml
index b08a0522f..34f2e8dc4 100644
--- a/cache/pom.xml
+++ b/cache/pom.xml
@@ -29,7 +29,7 @@
 	<parent>
 		<groupId>net.runelite</groupId>
 		<artifactId>runelite-parent</artifactId>
-		<version>1.10.39-SNAPSHOT</version>
+		<version>1.10.41-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>cache</artifactId>
diff --git a/pom.xml b/pom.xml
index 8daa0dee1..362a39015 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
 
 	<groupId>net.runelite</groupId>
 	<artifactId>runelite-parent</artifactId>
-	<version>1.10.39-SNAPSHOT</version>
+	<version>1.10.41-SNAPSHOT</version>
 	<packaging>pom</packaging>
 
 	<name>RuneLite</name>
diff --git a/runelite-api/pom.xml b/runelite-api/pom.xml
index 12db42e20..ef55a7ec1 100644
--- a/runelite-api/pom.xml
+++ b/runelite-api/pom.xml
@@ -29,7 +29,7 @@
 	<parent>
 		<groupId>net.runelite</groupId>
 		<artifactId>runelite-parent</artifactId>
-		<version>1.10.39-SNAPSHOT</version>
+		<version>1.10.41-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>runelite-api</artifactId>
diff --git a/runelite-api/src/main/interfaces/interfaces.toml b/runelite-api/src/main/interfaces/interfaces.toml
index 04952b5af..2dfa7c09d 100644
--- a/runelite-api/src/main/interfaces/interfaces.toml
+++ b/runelite-api/src/main/interfaces/interfaces.toml
@@ -69,12 +69,13 @@ deposit_equipment=46
 potion_store=48
 incinerator=49
 incinerator_confirm=50
-equipment_parent=74
-equipment_set_bonus=115
-settings_button=119
-equipment_button=120
-popup=122
-equipment_stat_bonus=127
+potionstore_content=52
+equipment_parent=77
+equipment_set_bonus=118
+settings_button=122
+equipment_button=123
+popup=125
+equipment_stat_bonus=131
 
 [bank_inventory]
 id=15
@@ -626,6 +627,7 @@ container=0
 
 [prayer]
 id=541
+parent=0
 
 [private_chat]
 id=163
diff --git a/runelite-api/src/main/java/net/runelite/api/AnimationID.java b/runelite-api/src/main/java/net/runelite/api/AnimationID.java
index 153e82525..7eb514c70 100644
--- a/runelite-api/src/main/java/net/runelite/api/AnimationID.java
+++ b/runelite-api/src/main/java/net/runelite/api/AnimationID.java
@@ -236,6 +236,10 @@ public final class AnimationID
 	public static final int DENSE_ESSENCE_CHIPPING = 7201;
 	public static final int DENSE_ESSENCE_CHISELING = 7202;
 	public static final int HERBLORE_POTIONMAKING = 363; //used for both herb and secondary
+	public static final int HERBLORE_MIXOLOGY_CONCENTRATE = 11644;
+	public static final int HERBLORE_MIXOLOGY_CRYSTALIZE = 11639;
+	public static final int HERBLORE_MIXOLOGY_HOMOGENIZE = 11634;
+	public static final int HERBLORE_MIXOLOGY_REFINER = 11604;
 	public static final int MAGIC_CHARGING_ORBS = 726;
 	public static final int MAGIC_MAKE_TABLET = 4068;
 	public static final int MAGIC_ENCHANTING_JEWELRY = 931;
@@ -301,6 +305,7 @@ public final class AnimationID
 	// NPC animations
 	public static final int TZTOK_JAD_MAGIC_ATTACK = 2656;
 	public static final int TZTOK_JAD_RANGE_ATTACK = 2652;
+	public static final int CYCLOPS_ATTACK = 4652;
 	public static final int HELLHOUND_DEFENCE = 6566;
 
 	// Farming
diff --git a/runelite-api/src/main/java/net/runelite/api/Constants.java b/runelite-api/src/main/java/net/runelite/api/Constants.java
index d1acb20c7..ef8dca4dc 100644
--- a/runelite-api/src/main/java/net/runelite/api/Constants.java
+++ b/runelite-api/src/main/java/net/runelite/api/Constants.java
@@ -88,6 +88,7 @@ public class Constants
 
 	public static final int TILE_FLAG_BRIDGE = 2;
 	public static final int TILE_FLAG_UNDER_ROOF = 4;
+	public static final int TILE_FLAG_VIS_BELOW = 8;
 
 	/**
 	 * Flag for roof removal to remove the roofs above the player's current position.
diff --git a/runelite-api/src/main/java/net/runelite/api/EnumID.java b/runelite-api/src/main/java/net/runelite/api/EnumID.java
index c8618f6d1..785435c43 100644
--- a/runelite-api/src/main/java/net/runelite/api/EnumID.java
+++ b/runelite-api/src/main/java/net/runelite/api/EnumID.java
@@ -93,4 +93,10 @@ public final class EnumID
 
 	public static final int SPELLBOOKS = 1981;
 	public static final int SPELLBOOKS_SUB = 5280;
+
+	/**
+	 * key: int id
+	 * val: enum potion
+	 */
+	public static final int POTIONSTORE_POTIONS = 4826;
 }
diff --git a/runelite-api/src/main/java/net/runelite/api/FloatProjection.java b/runelite-api/src/main/java/net/runelite/api/FloatProjection.java
new file mode 100644
index 000000000..87c59e567
--- /dev/null
+++ b/runelite-api/src/main/java/net/runelite/api/FloatProjection.java
@@ -0,0 +1,33 @@
+/*
+ * 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.api;
+
+public interface FloatProjection extends Projection
+{
+	/**
+	 * Get the projection as a 4x4 matrix
+	 */
+	float[] getProjection();
+}
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 3b28a9d46..3f261f613 100644
--- a/runelite-api/src/main/java/net/runelite/api/ItemID.java
+++ b/runelite-api/src/main/java/net/runelite/api/ItemID.java
@@ -14291,8 +14291,8 @@ public final class ItemID
 	public static final int REAGENT_POUCH = 29996;
 	public static final int OPEN_REAGENT_POUCH = 29998;
 	public static final int POTION_STORAGE = 29999;
-	public static final int PREPOT_DEVICE = 30000;
-	public static final int PREPOT_DEVICE_DISASSEMBLED = 30002;
+	public static final int CHUGGING_BARREL = 30000;
+	public static final int CHUGGING_BARREL_DISASSEMBLED = 30002;
 	public static final int MOX_PASTE = 30005;
 	public static final int AGA_PASTE = 30007;
 	public static final int LYE_PASTE = 30009;
diff --git a/runelite-api/src/main/java/net/runelite/api/NpcID.java b/runelite-api/src/main/java/net/runelite/api/NpcID.java
index a8c869287..ed2a35e21 100644
--- a/runelite-api/src/main/java/net/runelite/api/NpcID.java
+++ b/runelite-api/src/main/java/net/runelite/api/NpcID.java
@@ -11601,8 +11601,7 @@ public final class NpcID
 	public static final int ICE_BLOCK_13790 = 13790;
 	public static final int FROST_CRAB_13791 = 13791;
 	public static final int ICE_BLOCK_13792 = 13792;
-	public static final int BLUE_DRAGON_13793 = 13793;
-	public static final int BABY_BLUE_DRAGON_13794 = 13794;
+	public static final int BREWMA = 13793;
 	public static final int BRUTAL_BLUE_DRAGON_13795 = 13795;
 	public static final int ICE_GIANT_13796 = 13796;
 	public static final int ICE_GIANT_13797 = 13797;
@@ -11836,5 +11835,9 @@ public final class NpcID
 	public static final int HUBERTE_14045 = 14045;
 	public static final int MOXI_14046 = 14046;
 	public static final int GULL_14047 = 14047;
+	public static final int BLUE_DRAGON_14103 = 14103;
+	public static final int BLUE_DRAGON_14104 = 14104;
+	public static final int BABY_BLUE_DRAGON_14105 = 14105;
+	public static final int BABY_BLUE_DRAGON_14106 = 14106;
 	/* 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 e02cd34fe..8df4af53e 100644
--- a/runelite-api/src/main/java/net/runelite/api/NullObjectID.java
+++ b/runelite-api/src/main/java/net/runelite/api/NullObjectID.java
@@ -28808,5 +28808,12 @@ public final class NullObjectID
 	public static final int NULL_55399 = 55399;
 	public static final int NULL_55400 = 55400;
 	public static final int NULL_55401 = 55401;
+	public static final int NULL_55403 = 55403;
+	public static final int NULL_55404 = 55404;
+	public static final int NULL_55405 = 55405;
+	public static final int NULL_55420 = 55420;
+	public static final int NULL_55421 = 55421;
+	public static final int NULL_55422 = 55422;
+	public static final int NULL_55423 = 55423;
 	/* 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 38708e16e..c00198faf 100644
--- a/runelite-api/src/main/java/net/runelite/api/ObjectID.java
+++ b/runelite-api/src/main/java/net/runelite/api/ObjectID.java
@@ -26207,8 +26207,8 @@ public final class ObjectID
 	public static final int SKELETON_54679 = 54679;
 	public static final int SKELETON_54680 = 54680;
 	public static final int SKELETON_54681 = 54681;
-	public static final int ICEY_WATER = 54682;
-	public static final int ICEY_WATER_54683 = 54683;
+	public static final int ICY_WATER = 54682;
+	public static final int ICY_WATER_54683 = 54683;
 	public static final int GATE_54689 = 54689;
 	public static final int CAVE_54690 = 54690;
 	public static final int CAVE_54691 = 54691;
@@ -26578,5 +26578,22 @@ public final class ObjectID
 	public static final int HOUSE_ADVERTISEMENT_55352 = 55352;
 	public static final int PORTAL_55353 = 55353;
 	public static final int HOPPER_CONTROLS_55402 = 55402;
+	public static final int SHELVES_55406 = 55406;
+	public static final int SHELVES_55407 = 55407;
+	public static final int SHELVES_55408 = 55408;
+	public static final int SHELVES_55409 = 55409;
+	public static final int CAULDRON_55410 = 55410;
+	public static final int REWARD_CART = 55411;
+	public static final int REWARD_CART_55412 = 55412;
+	public static final int REWARD_CART_55413 = 55413;
+	public static final int REWARD_CART_55414 = 55414;
+	public static final int REWARD_CART_55415 = 55415;
+	public static final int REWARD_CART_55416 = 55416;
+	public static final int ROCKSLIDE_55417 = 55417;
+	public static final int ROCKSLIDE_55418 = 55418;
+	public static final int ROCKSLIDE_55419 = 55419;
+	public static final int GRAPE_STORAGE_55424 = 55424;
+	public static final int GRAPE_STORAGE_55425 = 55425;
+	public static final int GRAPE_STORAGE_55426 = 55426;
 	/* This file is automatically generated. Do not edit. */
 }
diff --git a/runelite-api/src/main/java/net/runelite/api/Scene.java b/runelite-api/src/main/java/net/runelite/api/Scene.java
index 886d70998..527f29e4e 100644
--- a/runelite-api/src/main/java/net/runelite/api/Scene.java
+++ b/runelite-api/src/main/java/net/runelite/api/Scene.java
@@ -81,10 +81,14 @@ public interface Scene extends Renderable
 	 */
 	void removeGameObject(GameObject gameObject);
 
-	void generateHouses();
+	void buildRoofs();
+
+	int[][][] getRoofs();
 
 	void setRoofRemovalMode(int flags);
 
+	int getRoofRemovalMode();
+
 	/**
 	 * Get the underlay ids for the scene. The value stored is id + 1, with 0 for no underlay.
 	 * @return
diff --git a/runelite-api/src/main/java/net/runelite/api/ScriptID.java b/runelite-api/src/main/java/net/runelite/api/ScriptID.java
index 0e8d831ba..14c18e6eb 100644
--- a/runelite-api/src/main/java/net/runelite/api/ScriptID.java
+++ b/runelite-api/src/main/java/net/runelite/api/ScriptID.java
@@ -461,4 +461,10 @@ public final class ScriptID
 
 	@ScriptArguments(integer = 2)
 	public static final int MOTHERLODE_HUD_UPDATE = 1634;
+
+	@ScriptArguments(integer = 1)
+	public static final int POTIONSTORE_DOSES = 3750;
+
+	@ScriptArguments(integer = 1)
+	public static final int POTIONSTORE_WITHDRAW_DOSES = 4818;
 }
diff --git a/runelite-api/src/main/java/net/runelite/api/WorldEntity.java b/runelite-api/src/main/java/net/runelite/api/WorldEntity.java
index c4de84c69..2e747e902 100644
--- a/runelite-api/src/main/java/net/runelite/api/WorldEntity.java
+++ b/runelite-api/src/main/java/net/runelite/api/WorldEntity.java
@@ -26,4 +26,5 @@ package net.runelite.api;
 
 public interface WorldEntity
 {
+	WorldView getWorldView();
 }
diff --git a/runelite-api/src/main/java/net/runelite/api/WorldView.java b/runelite-api/src/main/java/net/runelite/api/WorldView.java
index 9037bf5eb..9bc7fee68 100644
--- a/runelite-api/src/main/java/net/runelite/api/WorldView.java
+++ b/runelite-api/src/main/java/net/runelite/api/WorldView.java
@@ -202,4 +202,11 @@ public interface WorldView
 	 * @see InstanceTemplates
 	 */
 	int[][][] getInstanceTemplateChunks();
+
+	/**
+	 * Gets an array of map region IDs that are currently loaded.
+	 *
+	 * @return the map regions
+	 */
+	int[] getMapRegions();
 }
diff --git a/runelite-api/src/main/java/net/runelite/api/events/PreMapLoad.java b/runelite-api/src/main/java/net/runelite/api/events/PreMapLoad.java
new file mode 100644
index 000000000..2e52d70a9
--- /dev/null
+++ b/runelite-api/src/main/java/net/runelite/api/events/PreMapLoad.java
@@ -0,0 +1,41 @@
+/*
+ * 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.api.events;
+
+import lombok.Value;
+import net.runelite.api.Scene;
+import net.runelite.api.WorldView;
+
+/**
+ * This event is run from the maploader thread prior to the map load completing.
+ * Most client operations can't be done from this thread safely.
+ * You probably don't want to use this event.
+ */
+@Value
+public class PreMapLoad
+{
+	WorldView worldView;
+	Scene scene;
+}
diff --git a/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java b/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java
index cbae2943b..26bfc57be 100644
--- a/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java
+++ b/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java
@@ -24,6 +24,8 @@
  */
 package net.runelite.api.hooks;
 
+import java.util.Set;
+import net.runelite.api.GameObject;
 import net.runelite.api.Model;
 import net.runelite.api.Projection;
 import net.runelite.api.Renderable;
@@ -31,33 +33,48 @@ import net.runelite.api.Scene;
 import net.runelite.api.SceneTileModel;
 import net.runelite.api.SceneTilePaint;
 import net.runelite.api.Texture;
+import net.runelite.api.TileObject;
+import net.runelite.api.WorldView;
 
 public interface DrawCallbacks
 {
 	/**
 	 * GPU mode on.
 	 */
-	int GPU = 1;
+	int GPU = 0x1;
 	/**
 	 * GPU hillskew support. Enables the {@link Model#getUnskewedModel()}
 	 * API to get the unskewed model.
 	 */
-	int HILLSKEW = 2;
+	int HILLSKEW = 0x2;
 	/**
 	 * Requests normals be computed for models. Enables the {@link Model#getVertexNormalsX()}
 	 * {@link Model#getVertexNormalsY()} {@link Model#getVertexNormalsZ()} API.
 	 */
-	int NORMALS = 4;
+	int NORMALS = 0x4;
 	/**
 	 * Disable vertex snapping for animations
 	 */
-	int NO_VERTEX_SNAPPING = 8;
+	int NO_VERTEX_SNAPPING = 0x8;
+	/**
+	 * Enable zbuf renderer.
+	 */
+	int ZBUF = 0x10;
 
-	void draw(Projection projection, Scene scene, Renderable renderable, int orientation, int x, int y, int z, long hash);
+	int PASS_OPAQUE = 0;
+	int PASS_ALPHA = 1;
 
-	void drawScenePaint(Scene scene, SceneTilePaint paint, int plane, int tileX, int tileZ);
+	default void draw(Projection projection, Scene scene, Renderable renderable, int orientation, int x, int y, int z, long hash)
+	{
+	}
 
-	void drawSceneTileModel(Scene scene, SceneTileModel model, int tileX, int tileZ);
+	default void drawScenePaint(Scene scene, SceneTilePaint paint, int plane, int tileX, int tileZ)
+	{
+	}
+
+	default void drawSceneTileModel(Scene scene, SceneTileModel model, int tileX, int tileZ)
+	{
+	}
 
 	/**
 	 * Called when a frame should be drawn.
@@ -69,16 +86,24 @@ public interface DrawCallbacks
 	/**
 	 * Called before the scene is drawn
 	 */
-	void drawScene(double cameraX, double cameraY, double cameraZ, double cameraPitch, double cameraYaw, int plane);
+	default void drawScene(double cameraX, double cameraY, double cameraZ, double cameraPitch, double cameraYaw, int plane)
+	{
+	}
 
 	/**
 	 * Called after the scene has been drawn
 	 */
-	void postDrawScene();
+	default void postDrawScene()
+	{
+	}
 
-	void animate(Texture texture, int diff);
+	default void animate(Texture texture, int diff)
+	{
+	}
 
-	void loadScene(Scene scene);
+	default void loadScene(Scene scene)
+	{
+	}
 
 	void swapScene(Scene scene);
 
@@ -86,4 +111,39 @@ public interface DrawCallbacks
 	{
 		return true;
 	}
+
+	default void loadScene(WorldView worldView, Scene scene)
+	{
+	}
+
+	default void despawnWorldView(WorldView worldView)
+	{
+	}
+
+	default void prepareSceneDraw(
+		Scene scene,
+		float cameraX, float cameraY, float cameraZ, float cameraPitch, float cameraYaw,
+		int minLevel, int level, int maxLevel, Set<Integer> hideRoofIds)
+	{
+	}
+
+	default void drawPass(Projection entityProjection, Scene scene, int pass)
+	{
+	}
+
+	default void drawZone(Projection entityProjection, Scene scene, int pass, int zx, int zz)
+	{
+	}
+
+	default void drawDynamic(Projection worldProjection, Scene scene, TileObject tileObject, Renderable r, Model m, int orient, int x, int y, int z)
+	{
+	}
+
+	default void drawTemp(Projection worldProjection, Scene scene, GameObject gameObject, Model m)
+	{
+	}
+
+	default void invalidateZone(Scene scene, int zx, int zz)
+	{
+	}
 }
diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java b/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java
index 878dc756d..37578d6eb 100644
--- a/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java
+++ b/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java
@@ -993,6 +993,11 @@ public interface Widget
 	 */
 	void setNoScrollThrough(boolean noScrollThrough);
 
+	/**
+	 * {@link net.runelite.api.VarPlayer}s that triggers this widgets varTransmitListener
+	 */
+	int[] getVarTransmitTrigger();
+
 	/**
 	 * {@link net.runelite.api.VarPlayer}s that triggers this widgets varTransmitListener
 	 */
diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml
index 3b2282bd5..98020cc38 100644
--- a/runelite-client/pom.xml
+++ b/runelite-client/pom.xml
@@ -29,7 +29,7 @@
 	<parent>
 		<groupId>net.runelite</groupId>
 		<artifactId>runelite-parent</artifactId>
-		<version>1.10.39-SNAPSHOT</version>
+		<version>1.10.41-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>client</artifactId>
@@ -372,10 +372,21 @@
 							<shadedArtifactAttached>true</shadedArtifactAttached>
 							<shadedClassifierName>shaded</shadedClassifierName>
 							<createDependencyReducedPom>false</createDependencyReducedPom>
+							<filters>
+								<filter>
+									<artifact>*:*</artifact>
+									<excludes>
+										<exclude>META-INF/versions/**/module-info.class</exclude>
+									</excludes>
+								</filter>
+							</filters>
 
 							<transformers>
 								<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
-									<mainClass>net.runelite.client.RuneLite</mainClass>
+									<manifestEntries>
+										<Main-Class>net.runelite.client.RuneLite</Main-Class>
+										<Multi-Release>true</Multi-Release>
+									</manifestEntries>
 								</transformer>
 							</transformers>
 						</configuration>
diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java
index 8c39c5f22..54e85a67e 100644
--- a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java
+++ b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java
@@ -171,6 +171,12 @@ public class ItemManager
 		put(GRACEFUL_LEGS_27455, GRACEFUL_LEGS_27453).
 		put(GRACEFUL_GLOVES_27458, GRACEFUL_GLOVES_27456).
 		put(GRACEFUL_BOOTS_27461, GRACEFUL_BOOTS_27459).
+		put(GRACEFUL_HOOD_30047, GRACEFUL_HOOD_30045).
+		put(GRACEFUL_CAPE_30050, GRACEFUL_CAPE_30048).
+		put(GRACEFUL_TOP_30053, GRACEFUL_TOP_30051).
+		put(GRACEFUL_LEGS_30056, GRACEFUL_LEGS_30054).
+		put(GRACEFUL_GLOVES_30059, GRACEFUL_GLOVES_30057).
+		put(GRACEFUL_BOOTS_30062, GRACEFUL_BOOTS_30060).
 
 		put(MAX_CAPE_13342, MAX_CAPE).
 
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingPlugin.java
index ede1be560..0616adaaa 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingPlugin.java
@@ -69,6 +69,8 @@ public class AnimationSmoothingPlugin extends Plugin
 			case AnimationID.WYRM_ATTACK_MELEE:
 			case AnimationID.WYRM_ATTACK_MAGE:
 
+			case AnimationID.CYCLOPS_ATTACK:
+
 			case AnimationID.TREE_SPIRIT_IDLE:
 			case AnimationID.TREE_SPIRIT_WALK:
 
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesConfig.java
index 7b40f7e6d..68226dd11 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesConfig.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesConfig.java
@@ -27,6 +27,7 @@ package net.runelite.client.plugins.attackstyles;
 import net.runelite.client.config.Config;
 import net.runelite.client.config.ConfigGroup;
 import net.runelite.client.config.ConfigItem;
+import net.runelite.client.config.Notification;
 
 @ConfigGroup("attackIndicator")
 public interface AttackStylesConfig extends Config
@@ -129,4 +130,15 @@ public interface AttackStylesConfig extends Config
 	{
 		return true;
 	}
+
+	@ConfigItem(
+		keyName = "warningNotification",
+		name = "Warning notification",
+		description = "Notification for switching to an unwanted attack style",
+		position = 10
+	)
+	default Notification warningNotification()
+	{
+		return Notification.OFF;
+	}
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesPlugin.java
index ea8cfd9a0..5ddb2204c 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesPlugin.java
@@ -47,6 +47,7 @@ import net.runelite.api.events.ScriptPostFired;
 import net.runelite.api.events.VarbitChanged;
 import net.runelite.api.widgets.ComponentID;
 import net.runelite.api.widgets.Widget;
+import net.runelite.client.Notifier;
 import net.runelite.client.callback.ClientThread;
 import net.runelite.client.chat.ChatColorType;
 import net.runelite.client.chat.ChatMessageBuilder;
@@ -57,6 +58,7 @@ import net.runelite.client.eventbus.Subscribe;
 import net.runelite.client.events.ConfigChanged;
 import net.runelite.client.plugins.Plugin;
 import net.runelite.client.plugins.PluginDescriptor;
+import static net.runelite.client.plugins.attackstyles.AttackStyle.CASTING;
 import static net.runelite.client.plugins.attackstyles.AttackStyle.DEFENSIVE;
 import static net.runelite.client.plugins.attackstyles.AttackStyle.DEFENSIVE_CASTING;
 import static net.runelite.client.plugins.attackstyles.AttackStyle.OTHER;
@@ -95,6 +97,9 @@ public class AttackStylesPlugin extends Plugin
 	@Inject
 	private ChatMessageManager chatManager;
 
+	@Inject
+	private Notifier notifier;
+
 	@Provides
 	AttackStylesConfig provideConfig(ConfigManager configManager)
 	{
@@ -236,17 +241,22 @@ public class AttackStylesPlugin extends Plugin
 	@Subscribe
 	public void onGameTick(GameTick gameTick)
 	{
-		if (attackStyle != prevAttackStyle && warnedSkillSelected && config.showChatWarnings())
+		if (attackStyle != prevAttackStyle && warnedSkillSelected)
 		{
-			final String message = new ChatMessageBuilder()
-				.append(ChatColorType.HIGHLIGHT)
-				.append("Your attack style has been changed to " + attackStyle.getName())
-				.build();
-
-			chatManager.queue(QueuedMessage.builder()
-				.type(ChatMessageType.CONSOLE)
-				.runeLiteFormattedMessage(message)
-				.build());
+			if (config.showChatWarnings())
+			{
+				final String message = new ChatMessageBuilder()
+					.append(ChatColorType.HIGHLIGHT)
+					.append("Your attack style has been changed to " + attackStyle.getName())
+					.build();
+
+				chatManager.queue(QueuedMessage.builder()
+					.type(ChatMessageType.CONSOLE)
+					.runeLiteFormattedMessage(message)
+					.build());
+			}
+
+			notifier.notify(config.warningNotification(), "Attack style changed to " + attackStyle.getName() + "!");
 		}
 		prevAttackStyle = attackStyle;
 	}
@@ -287,6 +297,14 @@ public class AttackStylesPlugin extends Plugin
 		int weaponStyleEnum = client.getEnum(EnumID.WEAPON_STYLES).getIntValue(weaponType);
 		if (weaponStyleEnum == -1)
 		{
+			// Blue moon spear
+			if (weaponType == 22)
+			{
+				return new AttackStyle[]{
+					AttackStyle.ACCURATE, AttackStyle.AGGRESSIVE, null, DEFENSIVE, CASTING, DEFENSIVE_CASTING
+				};
+			}
+
 			if (weaponType == 30)
 			{
 				// Partisan
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java
index 1b7c2bd33..c70ba0cc2 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java
@@ -187,7 +187,7 @@ public class BankTagsPlugin extends Plugin implements BankTagsService
 		cleanConfig();
 		spriteManager.addSpriteOverrides(TabSprites.values());
 		eventBus.register(tabInterface);
-		eventBus.register(layoutManager);
+		layoutManager.register();
 		clientThread.invokeLater(this::reinitBank);
 	}
 
@@ -195,7 +195,7 @@ public class BankTagsPlugin extends Plugin implements BankTagsService
 	public void shutDown()
 	{
 		eventBus.unregister(tabInterface);
-		eventBus.unregister(layoutManager);
+		layoutManager.unregister();
 		clientThread.invokeLater(() ->
 		{
 			// since the tab interface is unregistered from the eventbus, manually deinit it
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/LayoutManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/LayoutManager.java
index 527fe9f26..c7d452b24 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/LayoutManager.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/LayoutManager.java
@@ -64,6 +64,7 @@ import net.runelite.api.widgets.JavaScriptCallback;
 import net.runelite.api.widgets.Widget;
 import net.runelite.client.chat.ChatMessageManager;
 import net.runelite.client.chat.QueuedMessage;
+import net.runelite.client.eventbus.EventBus;
 import net.runelite.client.eventbus.Subscribe;
 import net.runelite.client.game.ItemManager;
 import net.runelite.client.game.ItemVariationMapping;
@@ -94,11 +95,15 @@ public class LayoutManager
 	private final TabManager tabManager;
 	private final TabInterface tabInterface;
 	private final ChatMessageManager chatMessageManager;
+	private final PotionStorage potionStorage;
+	private final EventBus eventBus;
 
 	private final List<PluginAutoLayout> autoLayouts = new ArrayList<>();
 
 	@Inject
-	LayoutManager(Client client, ItemManager itemManager, BankTagsPlugin plugin, ChatboxPanelManager chatboxPanelManager, BankSearch bankSearch, TabManager tabManager, TabInterface tabInterface, ChatMessageManager chatMessageManager)
+	LayoutManager(Client client, ItemManager itemManager, BankTagsPlugin plugin, ChatboxPanelManager chatboxPanelManager,
+		BankSearch bankSearch, TabManager tabManager, TabInterface tabInterface, ChatMessageManager chatMessageManager,
+		PotionStorage potionStorage, EventBus eventBus)
 	{
 		this.client = client;
 		this.itemManager = itemManager;
@@ -108,10 +113,24 @@ public class LayoutManager
 		this.tabManager = tabManager;
 		this.tabInterface = tabInterface;
 		this.chatMessageManager = chatMessageManager;
+		this.potionStorage = potionStorage;
+		this.eventBus = eventBus;
 
 		registerAutoLayout(plugin, "Default", new DefaultLayout());
 	}
 
+	public void register()
+	{
+		eventBus.register(this);
+		eventBus.register(potionStorage);
+	}
+
+	public void unregister()
+	{
+		eventBus.unregister(this);
+		eventBus.unregister(potionStorage);
+	}
+
 	private void layout(Layout l)
 	{
 		ItemContainer bank = client.getItemContainer(InventoryID.BANK);
@@ -144,7 +163,8 @@ public class LayoutManager
 		ItemMatcher[] matchers = {
 			this::matchExact,
 			this::matchPlaceholder,
-			this::matchesVariant
+			this::matchesVariant,
+			potionStorage::matches
 		};
 
 		Map<Integer, Integer> layoutToBank = new HashMap<>();
@@ -165,13 +185,19 @@ public class LayoutManager
 					layoutToBank.put(itemId, matchedId);
 					bankItems.remove(matchedId);
 
+					// Items with objvars can have both the item and the placeholder in the bank at the same time.
+					// Remove both from bankItems so that it won't get added later as an unlayouted tagged item.
+					ItemComposition matchedItemDef = client.getItemDefinition(matchedId);
+					boolean removedPlaceholder = bankItems.remove(matchedItemDef.getPlaceholderId());
+
 					if (log.isDebugEnabled())
 					{
-						var from = itemManager.getItemComposition(itemId);
-						var to = itemManager.getItemComposition(matchedId);
-						log.debug("Matched {}{} -> {}{}",
+						ItemComposition from = itemManager.getItemComposition(itemId);
+						ItemComposition to = matchedItemDef;
+						log.debug("Matched {}{} -> {}{} removed placeholder: {}",
 							from.getName(), from.getPlaceholderId() > -1 && from.getPlaceholderTemplateId() > -1 ? " (placeholder)" : "",
-							to.getName(), to.getPlaceholderId() > -1 && to.getPlaceholderTemplateId() > -1 ? " (placeholder)" : ""
+							to.getName(), to.getPlaceholderId() > -1 && to.getPlaceholderTemplateId() > -1 ? " (placeholder)" : "",
+							removedPlaceholder
 						);
 					}
 				}
@@ -201,7 +227,7 @@ public class LayoutManager
 			}
 
 			Widget c = itemContainer.getChild(pos);
-			drawItem(l, c, bankItemId, bank.count(bankItemId), pos);
+			drawItem(l, c, bankItemId, count(bank, bankItemId), pos);
 		}
 
 		int lastEmptySlot = -1;
@@ -221,13 +247,17 @@ public class LayoutManager
 				break;
 			}
 
-			drawItem(l, c, itemId, bank.count(itemId), lastEmptySlot);
+			drawItem(l, c, itemId, count(bank, itemId), lastEmptySlot);
 
-			int layoutItemId = itemManager.canonicalize(itemId);
 			if (log.isDebugEnabled())
 			{
-				log.debug("Adding {} to layout", itemManager.getItemComposition(layoutItemId).getName());
+				ItemComposition def = itemManager.getItemComposition(itemId);
+				log.debug("Bank contains {}{} but is not in the layout",
+					def.getName(),
+					def.getPlaceholderTemplateId() > -1 && def.getPlaceholderId() > -1 ? " (placeholder)" : "");
 			}
+
+			int layoutItemId = itemManager.canonicalize(itemId);
 			l.addItem(layoutItemId);
 			modified = true;
 		}
@@ -256,6 +286,16 @@ public class LayoutManager
 		}
 	}
 
+	private int count(ItemContainer bank, int itemId)
+	{
+		int count = bank.count(itemId);
+		if (count > 0)
+		{
+			return count;
+		}
+		return potionStorage.count(itemId);
+	}
+
 	// mostly from ~bankmain_drawitem
 	private void drawItem(Layout l, Widget c, int item, int qty, int idx)
 	{
@@ -271,9 +311,9 @@ public class LayoutManager
 			c.clearActions();
 
 			// Jagex Placeholder
-			if (qty == 1 && def.getPlaceholderTemplateId() >= 0 && def.getPlaceholderId() >= 0)
+			if (def.getPlaceholderTemplateId() >= 0 && def.getPlaceholderId() >= 0)
 			{
-				c.setItemQuantity(0);
+				c.setItemQuantity(qty);
 				c.setOpacity(120);
 				c.setAction(8 - 1, "Release");
 				c.setAction(10 - 1, "Examine");
@@ -507,6 +547,12 @@ public class LayoutManager
 			BankTag activeTag = plugin.getActiveTag();
 			if (activeTag != null)
 			{
+				// Since the script vm isn't reentrant, we can't call into POTIONSTORE_DOSES/POTIONSTORE_WITHDRAW_DOSES
+				// from bankmain_finishbuilding for the layout. Instead, we record all of the potions on client tick,
+				// which is after this is run, but before the var/inv transmit listeners run, so that we will have
+				// them by the time the inv transmit listener runs.
+				potionStorage.cachePotions = true;
+
 				Layout layout = activeTag.layout();
 				if (layout != null)
 				{
@@ -598,6 +644,15 @@ public class LayoutManager
 				if (idx > -1 && menu.getParam0() != idx)
 				{
 					menu.setParam0(idx);
+					return;
+				}
+
+				idx = potionStorage.find(w.getItemId());
+				if (idx > -1)
+				{
+					potionStorage.prepareWidgets();
+					menu.setParam1(ComponentID.BANK_POTIONSTORE_CONTENT);
+					menu.setParam0(idx * PotionStorage.COMPONENTS_PER_POTION);
 				}
 			}
 		}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/PotionStorage.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/PotionStorage.java
new file mode 100644
index 000000000..9a2fd23f4
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/PotionStorage.java
@@ -0,0 +1,251 @@
+/*
+ * 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.banktags.tabs;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.runelite.api.Client;
+import net.runelite.api.EnumComposition;
+import net.runelite.api.EnumID;
+import net.runelite.api.ScriptID;
+import net.runelite.api.events.ClientTick;
+import net.runelite.api.events.VarbitChanged;
+import net.runelite.api.events.WidgetClosed;
+import net.runelite.api.widgets.ComponentID;
+import net.runelite.api.widgets.InterfaceID;
+import net.runelite.api.widgets.Widget;
+import net.runelite.api.widgets.WidgetType;
+import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.game.ItemManager;
+import net.runelite.client.plugins.bank.BankSearch;
+import net.runelite.client.plugins.banktags.BankTag;
+import net.runelite.client.plugins.banktags.BankTagsPlugin;
+
+class Potion
+{
+	EnumComposition potionEnum;
+	int itemId;
+	int doses;
+	int withdrawDoses;
+}
+
+@Singleton
+@RequiredArgsConstructor(onConstructor = @__(@Inject))
+@Slf4j
+class PotionStorage
+{
+	static final int BANKTAB_POTIONSTORE = 15;
+	static final int COMPONENTS_PER_POTION = 5;
+
+	private final Client client;
+	private final BankTagsPlugin plugin;
+	private final ItemManager itemManager;
+	private final BankSearch bankSearch;
+
+	private Potion[] potions;
+	boolean cachePotions;
+	private boolean layout;
+	private Set<Integer> potionStoreVars;
+
+	@Subscribe
+	public void onClientTick(ClientTick event)
+	{
+		if (cachePotions)
+		{
+			log.debug("Rebuilding potions");
+			cachePotions = false;
+			rebuildPotions();
+
+			Widget w = client.getWidget(ComponentID.BANK_POTIONSTORE_CONTENT);
+			if (w != null && potionStoreVars == null)
+			{
+				// cache varps that the potion store rebuilds on
+				int[] trigger = w.getVarTransmitTrigger();
+				potionStoreVars = new HashSet<>();
+				Arrays.stream(trigger).forEach(potionStoreVars::add);
+			}
+
+			if (layout)
+			{
+				layout = false;
+				BankTag activeTag = plugin.getActiveTag();
+				if (activeTag != null)
+				{
+					bankSearch.layoutBank();
+				}
+			}
+		}
+	}
+
+	// Use varp change event instead of a widget change listener so that we can recache the potions prior to
+	// the cs2 vm running.
+	@Subscribe
+	public void onVarbitChanged(VarbitChanged varbitChanged)
+	{
+		if (potionStoreVars != null && potionStoreVars.contains(varbitChanged.getVarpId()))
+		{
+			cachePotions = true;
+			layout = true; // trigger a bank rebuild as the qty has changed
+		}
+	}
+
+
+	@Subscribe
+	public void onWidgetClosed(WidgetClosed event)
+	{
+		if (event.getGroupId() == InterfaceID.BANK && event.isUnload())
+		{
+			log.debug("Invalidating potions");
+			potions = null;
+		}
+	}
+
+	private void rebuildPotions()
+	{
+		var potionStorePotions = client.getEnum(EnumID.POTIONSTORE_POTIONS);
+		potions = new Potion[potionStorePotions.size()];
+		int potionsIdx = 0;
+		for (int potionEnumId : potionStorePotions.getIntVals())
+		{
+			var potionEnum = client.getEnum(potionEnumId);
+			client.runScript(ScriptID.POTIONSTORE_DOSES, potionEnumId);
+			int doses = client.getIntStack()[0];
+			client.runScript(ScriptID.POTIONSTORE_WITHDRAW_DOSES, potionEnumId);
+			int withdrawDoses = client.getIntStack()[0];
+
+			if (doses > 0 && withdrawDoses > 0)
+			{
+				Potion p = new Potion();
+				p.potionEnum = potionEnum;
+				p.itemId = potionEnum.getIntValue(withdrawDoses);
+				p.doses = doses;
+				p.withdrawDoses = withdrawDoses;
+				potions[potionsIdx] = p;
+
+				if (log.isDebugEnabled())
+				{
+					log.debug("Potion store has {} doses of {}", p.doses, itemManager.getItemComposition(p.itemId).getName());
+				}
+			}
+
+			++potionsIdx;
+		}
+	}
+
+	int matches(Set<Integer> bank, int itemId)
+	{
+		if (potions == null)
+		{
+			return -1;
+		}
+
+		for (Potion potion : potions)
+		{
+			if (potion == null)
+			{
+				continue;
+			}
+
+			var potionEnum = potion.potionEnum;
+			int potionItemId1 = potionEnum.getIntValue(1);
+			int potionItemId2 = potionEnum.getIntValue(2);
+			int potionItemId3 = potionEnum.getIntValue(3);
+			int potionItemId4 = potionEnum.getIntValue(4);
+
+			if (potionItemId1 == itemId || potionItemId2 == itemId || potionItemId3 == itemId || potionItemId4 == itemId)
+			{
+				int potionStoreItem = potionEnum.getIntValue(potion.withdrawDoses);
+
+				if (log.isDebugEnabled())
+				{
+					log.debug("Item {} matches a potion from potion store {}", itemId, itemManager.getItemComposition(potionStoreItem).getName());
+				}
+
+				return potionStoreItem;
+			}
+		}
+
+		return -1;
+	}
+
+	int count(int itemId)
+	{
+		if (potions == null)
+		{
+			return 0;
+		}
+
+		for (Potion potion : potions)
+		{
+			if (potion != null && potion.itemId == itemId)
+			{
+				return potion.doses / potion.withdrawDoses;
+			}
+		}
+		return 0;
+	}
+
+	int find(int itemId)
+	{
+		if (potions == null)
+		{
+			return -1;
+		}
+
+		int potionIdx = 0;
+		for (Potion potion : potions)
+		{
+			++potionIdx;
+			if (potion != null && potion.itemId == itemId)
+			{
+				return potionIdx - 1;
+			}
+		}
+		return -1;
+	}
+
+	void prepareWidgets()
+	{
+		// if the potion store hasn't been opened yet, the client components won't have been made yet.
+		// they need to exist for the click to work correctly.
+		Widget potStoreContent = client.getWidget(ComponentID.BANK_POTIONSTORE_CONTENT);
+		if (potStoreContent.getChildren() == null)
+		{
+			int childIdx = 0;
+			for (int i = 0; i < potions.length; ++i) // NOPMD: ForLoopCanBeForeach
+			{
+				for (int j = 0; j < COMPONENTS_PER_POTION; ++j)
+				{
+					potStoreContent.createChild(childIdx++, WidgetType.GRAPHIC);
+				}
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java
index 1c5512cb7..d66e683a9 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java
@@ -631,7 +631,15 @@ public class TabInterface
 		{
 			case TAB_OP_OPEN_TAG:
 			{
+				if (client.getVarbitValue(Varbits.CURRENT_BANK_TAB) == PotionStorage.BANKTAB_POTIONSTORE)
+				{
+					// Opening a tag tab with the potion store open would leave the store open in the bankground,
+					// making deposits not work. Force close the potion store.
+					log.debug("Closing potion store");
+					client.menuAction(-1, ComponentID.BANK_POTION_STORE, MenuAction.CC_OP, 1, -1, "Potion store", "");
+				}
 				client.setVarbit(Varbits.CURRENT_BANK_TAB, 0);
+
 				Widget clicked = event.getSource();
 
 				TagTab tab = tabManager.find(Text.removeTags(clicked.getName()));
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 120f42c21..120b36ad7 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
@@ -75,6 +75,7 @@ enum Boss
 	// but we still want the respawn timer to show when it despawns.
 	ARAXXOR(NpcID.ARAXXOR_13669, 15, RSTimeUnit.GAME_TICKS, ItemID.NID, true),
 	AMOXLIATL(NpcID.AMOXLIATL, 28, RSTimeUnit.GAME_TICKS, ItemID.MOXI),
+	HUEYCOATL(NpcID.THE_HUEYCOATL_14012, 50, RSTimeUnit.GAME_TICKS, ItemID.HUBERTE),
 	;
 
 	private static final Map<Integer, Boss> bosses;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/BossTimersPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/BossTimersPlugin.java
index 27a6a0c9e..2d4e2414e 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/BossTimersPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/BossTimersPlugin.java
@@ -28,6 +28,7 @@ package net.runelite.client.plugins.bosstimer;
 import javax.inject.Inject;
 import lombok.extern.slf4j.Slf4j;
 import net.runelite.api.NPC;
+import net.runelite.api.events.NpcChanged;
 import net.runelite.api.events.NpcDespawned;
 import net.runelite.client.eventbus.Subscribe;
 import net.runelite.client.game.ItemManager;
@@ -65,13 +66,26 @@ public class BossTimersPlugin extends Plugin
 		NPC npc = npcDespawned.getNpc();
 		Boss boss = Boss.find(npc.getId());
 
-		if (boss == null || (!boss.isIgnoreDead() && !npcUtil.isDying(npc)))
+		if (boss != null && (boss.isIgnoreDead() || npcUtil.isDying(npc)))
 		{
-			return;
+			createTimer(npc, boss);
 		}
+	}
 
-		// remove existing timer
-		infoBoxManager.removeIf(t -> t instanceof RespawnTimer && ((RespawnTimer) t).getBoss() == boss);
+	@Subscribe
+	public void onNpcChanged(NpcChanged npcChanged)
+	{
+		NPC npc = npcChanged.getNpc();
+		Boss boss = Boss.find(npc.getId());
+		if (boss == Boss.HUEYCOATL)
+		{
+			createTimer(npc, boss);
+		}
+	}
+
+	private void createTimer(NPC npc, Boss boss)
+	{
+		clearTimer(boss);
 
 		log.debug("Creating spawn timer for {} ({})", npc.getName(), boss.getSpawnTime());
 
@@ -79,4 +93,9 @@ public class BossTimersPlugin extends Plugin
 		timer.setTooltip(npc.getName());
 		infoBoxManager.addInfoBox(timer);
 	}
+
+	private void clearTimer(Boss boss)
+	{
+		infoBoxManager.removeIf(t -> t instanceof RespawnTimer && ((RespawnTimer) t).getBoss() == boss);
+	}
 }
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 655c3829b..1c9bbb9cc 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
@@ -24,6 +24,7 @@
  */
 package net.runelite.client.plugins.cluescrolls.clues;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableMap;
 import java.awt.Color;
 import java.awt.Graphics2D;
@@ -32,6 +33,7 @@ import javax.annotation.Nullable;
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.NonNull;
+import lombok.ToString;
 import net.runelite.api.Varbits;
 import net.runelite.api.annotations.Varbit;
 import net.runelite.api.coords.LocalPoint;
@@ -53,7 +55,9 @@ import net.runelite.client.ui.overlay.components.TitleComponent;
 public class CoordinateClue extends ClueScroll implements LocationClueScroll
 {
 	@Getter
-	private static class CoordinateClueInfo
+	@ToString
+	@VisibleForTesting
+	static class CoordinateClueInfo
 	{
 		private final String directions;
 		private final boolean lightRequired;
@@ -221,8 +225,8 @@ public class CoordinateClue extends ClueScroll implements LocationClueScroll
 		.put(new WorldPoint(2094, 2889, 0), new CoordinateClueInfo("West side of the Isle of Souls.", ARMADYLEAN_GUARD))
 		.put(new WorldPoint(1451, 3509, 0), new CoordinateClueInfo("Ruins of Morra.", ARMADYLEAN_OR_BANDOSIAN_GUARD))
 		.put(new WorldPoint(3318, 2706, 0), new CoordinateClueInfo("Necropolis mine", ARMADYLEAN_OR_BANDOSIAN_GUARD))
-		.put(new WorldPoint(1557, 3183, 0), new CoordinateClueInfo("North of Ortus Farm", ARMADYLEAN_GUARD))
-		.put(new WorldPoint(1571, 3245, 0), new CoordinateClueInfo("At the top of The Proudspire", ARMADYLEAN_GUARD))
+		.put(new WorldPoint(1557, 3183, 0), new CoordinateClueInfo("North of Ortus Farm", ARMADYLEAN_OR_BANDOSIAN_GUARD))
+		.put(new WorldPoint(1571, 3245, 0), new CoordinateClueInfo("At the top of The Proudspire", ARMADYLEAN_OR_BANDOSIAN_GUARD))
 		// Master
 		.put(new WorldPoint(2178, 3209, 0), new CoordinateClueInfo("South of Iorwerth Camp.", BRASSICAN_MAGE))
 		.put(new WorldPoint(2155, 3100, 0), new CoordinateClueInfo("South of Port Tyras (BJS if 76 Agility).", BRASSICAN_MAGE))
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java
index 254b96e09..3d5a1a269 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java
@@ -118,7 +118,7 @@ public class EmoteClue extends ClueScroll implements LocationClueScroll
 		new EmoteClue("Cheer in the Barbarian Agility Arena. Headbang before you talk to me. Equip a steel platebody, maple shortbow and a Wilderness cape.", "Barbarian Outpost", BARBARIAN_OUTPOST_OBSTACLE_COURSE, new WorldPoint(2552, 3556, 0), CHEER, HEADBANG, item(STEEL_PLATEBODY), item(MAPLE_SHORTBOW), ANY_TEAM_CAPE),
 		new EmoteClue("Bow upstairs in the Edgeville Monastery. Equip a completed prayer book.", "Edgeville Monastery", SOUTHEAST_CORNER_OF_THE_MONASTERY, new WorldPoint(3056, 3484, 1), BOW, any("Any god book", item(HOLY_BOOK), item(BOOK_OF_BALANCE), item(UNHOLY_BOOK), item(BOOK_OF_LAW), item(BOOK_OF_WAR), item(BOOK_OF_DARKNESS), item(HOLY_BOOK_OR), item(BOOK_OF_BALANCE_OR), item(UNHOLY_BOOK_OR), item(BOOK_OF_LAW_OR), item(BOOK_OF_WAR_OR), item(BOOK_OF_DARKNESS_OR))),
 		new EmoteClue("Cheer in the Shadow dungeon. Equip a rune crossbow, climbing boots and any mitre.", "Shadow dungeon", ENTRANCE_OF_THE_CAVE_OF_DAMIS, new WorldPoint(2629, 5071, 0), CHEER, any("Any mitre", item(GUTHIX_MITRE), item(SARADOMIN_MITRE), item(ZAMORAK_MITRE), item(ANCIENT_MITRE), item(BANDOS_MITRE), item(ARMADYL_MITRE)), any("Rune crossbow", item(RUNE_CROSSBOW), item(RUNE_CROSSBOW_OR)), any("Climbing boots", item(CLIMBING_BOOTS), item(CLIMBING_BOOTS_G)), any("Ring of visibility or ring of shadows", item(RING_OF_VISIBILITY), item(RING_OF_SHADOWS), item(RING_OF_SHADOWS_UNCHARGED))),
-		new EmoteClue("Cheer at the top of the agility pyramid. Beware of double agents! Equip a blue mystic robe top, and any rune heraldic shield.", "Agility Pyramid", AGILITY_PYRAMID, new WorldPoint(3043, 4697, 3), DOUBLE_AGENT_108, CHEER, item(MYSTIC_ROBE_TOP), any("Any rune heraldic shield", item(RUNE_SHIELD_H1), item(RUNE_SHIELD_H2), item(RUNE_SHIELD_H3), item(RUNE_SHIELD_H4), item(RUNE_SHIELD_H5))),
+		new EmoteClue("Cheer at the top of the agility pyramid. Beware of double agents! Equip a blue mystic robe top and any rune heraldic shield.", "Agility Pyramid", AGILITY_PYRAMID, new WorldPoint(3043, 4697, 3), DOUBLE_AGENT_108, CHEER, item(MYSTIC_ROBE_TOP), any("Any rune heraldic shield", item(RUNE_SHIELD_H1), item(RUNE_SHIELD_H2), item(RUNE_SHIELD_H3), item(RUNE_SHIELD_H4), item(RUNE_SHIELD_H5))),
 		new EmoteClue("Dance in Iban's temple. Beware of double agents! Equip Iban's staff, a black mystic top and a black mystic bottom.", "Iban's temple", WELL_OF_VOYAGE, new WorldPoint(2011, 4712, 0), DOUBLE_AGENT_141, DANCE, any("Any iban's staff", item(IBANS_STAFF), item(IBANS_STAFF_U)), item(MYSTIC_ROBE_TOP_DARK), item(MYSTIC_ROBE_BOTTOM_DARK)),
 		new EmoteClue("Dance on the Fishing Platform. Equip barrows gloves, an amulet of glory and a dragon med helm.", "Fishing Platform", SOUTHEAST_CORNER_OF_THE_FISHING_PLATFORM, new WorldPoint(2782, 3273, 0), DANCE, any("Any amulet of glory", item(AMULET_OF_GLORY), item(AMULET_OF_GLORY1), item(AMULET_OF_GLORY2), item(AMULET_OF_GLORY3), item(AMULET_OF_GLORY4), item(AMULET_OF_GLORY5), item(AMULET_OF_GLORY6)), item(BARROWS_GLOVES), item(DRAGON_MED_HELM)),
 		new EmoteClue("Flap at the death altar. Beware of double agents! Equip a death tiara, a legend's cape and any ring of wealth.", "Death altar", DEATH_ALTAR, new WorldPoint(2205, 4838, 0), DOUBLE_AGENT_141, FLAP, ANY_RING_OF_WEALTH, item(DEATH_TIARA), item(CAPE_OF_LEGENDS)),
@@ -181,26 +181,26 @@ public class EmoteClue extends ClueScroll implements LocationClueScroll
 		new EmoteClue("Jump for joy in the Ancient Cavern. Equip a granite shield, splitbark body and any rune heraldic helm.", "Ancient cavern", ENTRANCE_OF_THE_CAVERN_UNDER_THE_WHIRLPOOL, new WorldPoint(1768, 5366, 1), JUMP_FOR_JOY, item(GRANITE_SHIELD), item(SPLITBARK_BODY), range("Any rune heraldic helm", RUNE_HELM_H1, RUNE_HELM_H5)),
 		new EmoteClue("Jump for joy at the Neitiznot rune rock. Equip Rune boots, a proselyte hauberk and a dragonstone ring.", "Fremennik Isles", NEAR_A_RUNITE_ROCK_IN_THE_FREMENNIK_ISLES, new WorldPoint(2375, 3850, 0), JUMP_FOR_JOY, item(RUNE_BOOTS), item(PROSELYTE_HAUBERK), item(DRAGONSTONE_RING)),
 		new EmoteClue("Jump for joy in the centre of Zul-Andra. Beware of double agents! Equip a dragon 2h sword, bandos boots and an obsidian cape.", "Zul-Andra", NEAR_THE_PIER_IN_ZULANDRA, new WorldPoint(2199, 3056, 0), DOUBLE_AGENT_141, JUMP_FOR_JOY, item(DRAGON_2H_SWORD), any("Bandos boots", item(BANDOS_BOOTS), item(GUARDIAN_BOOTS), item(ECHO_BOOTS)), item(OBSIDIAN_CAPE)),
-		new EmoteClue("Jump for Joy in the mine near the Twilight Temple. Bow before you talk to me. Equip a maple longbow, a ruby amulet and some steel platelegs.", "Twilight Temple", null, new WorldPoint (1672, 3284, 0), JUMP_FOR_JOY, item(MAPLE_LONGBOW), item(RUBY_AMULET), item(STEEL_PLATELEGS)),
+		new EmoteClue("Jump for Joy in the mine near the Twilight Temple. Bow before you talk to me. Equip a maple longbow, a ruby amulet and some steel platelegs.", "Twilight Temple", TWILIGHT_TEMPLE_MINE, new WorldPoint (1672, 3284, 0), JUMP_FOR_JOY, BOW, item(MAPLE_LONGBOW), item(RUBY_AMULET), item(STEEL_PLATELEGS)),
 		new EmoteClue("Laugh by the fountain of heroes. Equip splitbark legs, dragon boots and a Rune longsword.", "Fountain of heroes", FOUNTAIN_OF_HEROES, new WorldPoint(2920, 9893, 0), LAUGH, item(SPLITBARK_LEGS), any("Dragon boots", item(DRAGON_BOOTS), item(DRAGON_BOOTS_G), item(PRIMORDIAL_BOOTS)), item(RUNE_LONGSWORD)),
 		new EmoteClue("Laugh in Jokul's tent in the Mountain Camp. Beware of double agents! Equip a rune full helmet, blue dragonhide chaps and a fire battlestaff.", "Mountain Camp", MOUNTAIN_CAMP_GOAT_ENCLOSURE, new WorldPoint(2812, 3681, 0), DOUBLE_AGENT_108, LAUGH, item(RUNE_FULL_HELM), item(BLUE_DHIDE_CHAPS), item(FIRE_BATTLESTAFF)),
 		new EmoteClue("Laugh at the crossroads south of the Sinclair Mansion. Equip a cowl, a blue wizard robe top and an iron scimitar.", "Sinclair Mansion", ROAD_JUNCTION_SOUTH_OF_SINCLAIR_MANSION, new WorldPoint(2741, 3536, 0), LAUGH, item(LEATHER_COWL), item(BLUE_WIZARD_ROBE), item(IRON_SCIMITAR)),
 		new EmoteClue("Laugh in front of the gem store in Ardougne market. Equip a Castlewars bracelet, a dragonstone amulet and a ring of forging.", "Ardougne", NEAR_THE_GEM_STALL_IN_ARDOUGNE_MARKET, new WorldPoint(2666, 3304, 0), LAUGH, any("Castle wars bracelet", range(CASTLE_WARS_BRACELET3, CASTLE_WARS_BRACELET1)), item(DRAGONSTONE_AMULET), item(RING_OF_FORGING)),
-		new EmoteClue("Nod your head where the River Ortus meets the Proudspire. Indicate 'no' before you talk to me. Equip a blue wizard hat, a blue wizard robe and wear nothing on your legs.", "Proudspire", null, new WorldPoint(1626, 3241, 0), YES, NO, item(BLUE_WIZARD_HAT), item(BLUE_WIZARD_ROBE), emptySlot("Nothing on legs", LEGS)),
+		new EmoteClue("Nod your head where the River Ortus meets the Proudspire. Indicate 'no' before you talk to me. Equip a blue wizard hat, a blue wizard robe and wear nothing on your legs.", "East of The Proudspire", ORTUS_MEETS_PROUDSPIRE, new WorldPoint(1626, 3241, 0), YES, NO, item(BLUE_WIZARD_HAT), item(BLUE_WIZARD_ROBE), emptySlot("Nothing on legs", LEGS)),
 		new EmoteClue("Panic in the Limestone Mine. Equip bronze platelegs, a steel pickaxe and a steel medium helmet.", "Limestone Mine", LIMESTONE_MINE, new WorldPoint(3372, 3498, 0), PANIC, item(BRONZE_PLATELEGS), item(STEEL_PICKAXE), item(STEEL_MED_HELM)),
 		new EmoteClue("Panic by the mausoleum in Morytania. Wave before you speak to me. Equip a mithril plate skirt, a maple longbow and no boots.", "Morytania mausoleum, access via the experiments cave", MAUSOLEUM_OFF_THE_MORYTANIA_COAST, new WorldPoint(3504, 3576, 0), PANIC, WAVE, item(MITHRIL_PLATESKIRT), item(MAPLE_LONGBOW), emptySlot("No boots", BOOTS)),
 		new EmoteClue("Panic on the Wilderness volcano bridge. Beware of double agents! Equip any headband and crozier.", "Wilderness volcano", VOLCANO_IN_THE_NORTHEASTERN_WILDERNESS, new WorldPoint(3368, 3935, 0), DOUBLE_AGENT_65, PANIC, any("Any headband", range(RED_HEADBAND, BROWN_HEADBAND), range(WHITE_HEADBAND, GREEN_HEADBAND)), any("Any crozier", item(ANCIENT_CROZIER), item(ARMADYL_CROZIER), item(BANDOS_CROZIER), range(SARADOMIN_CROZIER, ZAMORAK_CROZIER))),
 		new EmoteClue("Panic by the pilot on White Wolf Mountain. Beware of double agents! Equip mithril platelegs, a ring of life and a rune axe.", "White Wolf Mountain", GNOME_GLIDER_ON_WHITE_WOLF_MOUNTAIN, new WorldPoint(2847, 3499, 0), DOUBLE_AGENT_108, PANIC, item(MITHRIL_PLATELEGS), item(RING_OF_LIFE), item(RUNE_AXE)),
 		new EmoteClue("Panic by the big egg where no one dare goes and the ground is burnt. Beware of double agents! Equip a dragon med helm, a TokTz-Ket-Xil, a brine sabre, rune platebody and an uncharged amulet of glory.", "Lava dragon isle", SOUTHEAST_CORNER_OF_LAVA_DRAGON_ISLE, new WorldPoint(3227, 3831, 0), DOUBLE_AGENT_141, PANIC, item(DRAGON_MED_HELM), item(TOKTZKETXIL), item(BRINE_SABRE), item(RUNE_PLATEBODY), any("Uncharged Amulet of glory", item(AMULET_OF_GLORY))),
 		new EmoteClue("Panic at the area flowers meet snow. Equip Blue D'hide vambraces, a dragon spear and a rune plateskirt.", "Trollweiss mountain", HALFWAY_DOWN_TROLLWEISS_MOUNTAIN, new WorldPoint(2776, 3781, 0), PANIC, item(BLUE_DHIDE_VAMBRACES), item(DRAGON_SPEAR), item(RUNE_PLATESKIRT), item(SLED_4084)),
-		new EmoteClue("Panic outside the Twlight Temple. Beware of double agents! Equip a dragon longsword, granite body and a dragon plateskirt.", "Twilight Temple", null, new WorldPoint(1694, 3247, 0), DOUBLE_AGENT_108, PANIC, item(DRAGON_LONGSWORD), item(GRANITE_BODY), item(DRAGON_PLATESKIRT)),
+		new EmoteClue("Panic outside the Twilight Temple. Beware of double agents! Equip a rune longsword, rune platebody and a rune plateskirt.", "Twilight Temple", OUTSIDE_TWILIGHT_TEMPLE, new WorldPoint(1694, 3247, 0), DOUBLE_AGENT_108, PANIC, item(RUNE_LONGSWORD), item(RUNE_PLATEBODY), item(RUNE_PLATESKIRT)),
 		new EmoteClue("Blow a raspberry in the bank of the Warriors' Guild. Beware of double agents! Equip a dragon battleaxe, a slayer helm of any kind and a dragon defender or avernic defender.", "Warriors' guild", WARRIORS_GUILD_BANK_29047, new WorldPoint(2843, 3543, 0), DOUBLE_AGENT_141, RASPBERRY, item(DRAGON_BATTLEAXE), any("Dragon defender or Avernic defender", item(DRAGON_DEFENDER), item(DRAGON_DEFENDER_T), item(DRAGON_DEFENDER_L), item(AVERNIC_DEFENDER), item(AVERNIC_DEFENDER_L), item(GHOMMALS_AVERNIC_DEFENDER_5), item(GHOMMALS_AVERNIC_DEFENDER_5_L), item(GHOMMALS_AVERNIC_DEFENDER_6), item(GHOMMALS_AVERNIC_DEFENDER_6_L)), ANY_SLAYER_HELMET),
 		new EmoteClue("Blow a raspberry at the monkey cage in Ardougne Zoo. Equip a studded leather body, bronze platelegs and a normal staff with no orb.", "Ardougne Zoo", NEAR_THE_PARROTS_IN_ARDOUGNE_ZOO, new WorldPoint(2607, 3282, 0), RASPBERRY, item(STUDDED_BODY), item(BRONZE_PLATELEGS), item(STAFF)),
 		new EmoteClue("Blow raspberries outside the entrance to Keep Le Faye. Equip a coif, an iron platebody and leather gloves.", "Keep Le Faye", OUTSIDE_KEEP_LE_FAYE, new WorldPoint(2757, 3401, 0), RASPBERRY, item(COIF), item(IRON_PLATEBODY), item(LEATHER_GLOVES)),
 		new EmoteClue("Blow a raspberry in the Fishing Guild bank. Beware of double agents! Equip an elemental shield, blue dragonhide chaps and a rune warhammer.", "Fishing Guild", FISHING_GUILD_BANK, new WorldPoint(2588, 3419, 0), DOUBLE_AGENT_108, RASPBERRY, item(ELEMENTAL_SHIELD), item(BLUE_DHIDE_CHAPS), item(RUNE_WARHAMMER)),
 		new EmoteClue("Salute in the banana plantation. Beware of double agents! Equip a diamond ring, amulet of power, and nothing on your chest and legs.", "Karamja", WEST_SIDE_OF_THE_KARAMJA_BANANA_PLANTATION, new WorldPoint(2914, 3168, 0), DOUBLE_AGENT_108, SALUTE, item(DIAMOND_RING), item(AMULET_OF_POWER), emptySlot("Nothing on chest & legs", BODY, LEGS)),
 		new EmoteClue("Salute in the Warriors' Guild bank. Equip only a black salamander.", "Warriors' guild", WARRIORS_GUILD_BANK, new WorldPoint(2844, 3542, 0), SALUTE, item(BLACK_SALAMANDER), emptySlot("Nothing else", HEAD, CAPE, AMULET, BODY, SHIELD, LEGS, GLOVES, BOOTS, RING, AMMO)),
-		new EmoteClue("Salute in the centre of the mess hall. Beware of double agents! Equip a rune halberd rune platebody, and an amulet of strength.", "Hosidius mess hall", HOSIDIUS_MESS, new WorldPoint(1646, 3631, 0), DOUBLE_AGENT_108, SALUTE, item(RUNE_HALBERD), item(RUNE_PLATEBODY), item(AMULET_OF_STRENGTH)),
+		new EmoteClue("Salute in the centre of the mess hall. Beware of double agents! Equip a rune halberd rune platebody and an amulet of strength.", "Hosidius mess hall", HOSIDIUS_MESS, new WorldPoint(1646, 3631, 0), DOUBLE_AGENT_108, SALUTE, item(RUNE_HALBERD), item(RUNE_PLATEBODY), item(AMULET_OF_STRENGTH)),
 		new EmoteClue("Salute outside the gates of Cam Torum. Beware of double agents! Equip a full set of blue moon equipment.", "Cam Torum", CAM_TORUM_ENTRANCE, new WorldPoint(1436, 3115, 0), DOUBLE_AGENT_141, SALUTE, any("Blue moon helm", item(BLUE_MOON_HELM), item(BLUE_MOON_HELM_29041)), any("Blue moon chestplate", item(BLUE_MOON_CHESTPLATE), item(BLUE_MOON_CHESTPLATE_29037)), any("Blue moon tassets", item(BLUE_MOON_TASSETS), item(BLUE_MOON_TASSETS_29039)), item(BLUE_MOON_SPEAR)),
 		new EmoteClue("Shrug in the mine near Rimmington. Equip a gold necklace, a gold ring and a bronze spear.", "Rimmington mine", RIMMINGTON_MINE, new WorldPoint(2976, 3238, 0), SHRUG, item(GOLD_NECKLACE), item(GOLD_RING), item(BRONZE_SPEAR)),
 		new EmoteClue("Shrug in Catherby bank. Yawn before you talk to me. Equip a maple longbow, green d'hide chaps and an iron med helm.", "Catherby", OUTSIDE_CATHERBY_BANK, new WorldPoint(2808, 3440, 0), SHRUG, YAWN, item(MAPLE_LONGBOW), item(GREEN_DHIDE_CHAPS), item(IRON_MED_HELM)),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/emote/STASHUnit.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/emote/STASHUnit.java
index 4ffb6022b..cdbe5fb8a 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/emote/STASHUnit.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/emote/STASHUnit.java
@@ -144,6 +144,9 @@ public enum STASHUnit
 	TEMPLE_SOUTHEAST_OF_THE_BAZAAR(NullObjectID.NULL_50739, new WorldPoint(1702, 3079, 0)),
 	CAM_TORUM_ENTRANCE(NullObjectID.NULL_50740, new WorldPoint(1428, 3118, 0)),
 	WESTERN_SALVAGER_OVERLOOK(NullObjectID.NULL_54275, new WorldPoint(1614, 3296, 0)),
+	TWILIGHT_TEMPLE_MINE(NullObjectID.NULL_55403, new WorldPoint(1668, 3287, 0)),
+	ORTUS_MEETS_PROUDSPIRE(NullObjectID.NULL_55404, new WorldPoint(1629, 3239, 0)),
+	OUTSIDE_TWILIGHT_TEMPLE(NullObjectID.NULL_55405, new WorldPoint(1693, 3243, 0)),
 	;
 
 	private final int objectId;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java
index 0af7b2f86..01926fe63 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java
@@ -151,6 +151,7 @@ public enum HotColdLocation
 	VARLAMORE_BAZAAR(MASTER, new WorldPoint(1680, 3107, 0), VARLAMORE, "In the centre of the Bazaar in Civitas illa Fortis.", BRASSICAN_MAGE),
 	VARLAMORE_LOCUS_OASIS(MASTER, new WorldPoint(1695, 2990, 0), VARLAMORE, "Amongst the trees at the Locus Oasis.", BRASSICAN_MAGE),
 	VARLAMORE_SUNSET_COAST(MASTER, new WorldPoint(1534, 2997, 0), VARLAMORE, "North-east corner of Sunset Coast.", ANCIENT_WIZARDS),
+	VARLAMORE_VILLA_LUCENS(MASTER, new WorldPoint(1469, 2933, 0), VARLAMORE, "On the Villa Lucens theatre stage in Aldarin. Completion of Death on the Isle is required.", BRASSICAN_MAGE),
 	WESTERN_PROVINCE_EAGLES_PEAK(MASTER, new WorldPoint(2297, 3529, 0), WESTERN_PROVINCE, "North-west of Eagles' Peak.", BRASSICAN_MAGE),
 	WESTERN_PROVINCE_PISCATORIS(MASTER, new WorldPoint(2334, 3685, 0), WESTERN_PROVINCE, "Piscatoris Fishing Colony", ANCIENT_WIZARDS),
 	WESTERN_PROVINCE_PISCATORIS_HUNTER_AREA(MASTER, new WorldPoint(2359, 3564, 0), WESTERN_PROVINCE, "Eastern part of Piscatoris Hunter area, south-west of the Falconry.", BRASSICAN_MAGE),
@@ -182,7 +183,7 @@ public enum HotColdLocation
 	ZEAH_COMBAT_RING(MASTER, new WorldPoint(1557, 3624, 0), ZEAH, "Shayzien Encampment, south-east of the Combat Ring.", BRASSICAN_MAGE),
 	ZEAH_SHAYZIEN_BANK_2(MASTER, new WorldPoint(1490, 3602, 0), ZEAH, "North of the bank in Shayzien.", BRASSICAN_MAGE),
 	ZEAH_LIBRARY(MASTER, new WorldPoint(1603, 3843, 0), ZEAH, "North-west of the Arceuus Library.", BRASSICAN_MAGE),
-	ZEAH_HOUSECHURCH(MASTER, new WorldPoint(1682, 3792, 0), ZEAH, "By the entrance to the Arceuus church.", ANCIENT_WIZARDS),
+	ZEAH_HOUSECHURCH(MASTER, new WorldPoint(1681, 3794, 0), ZEAH, "By the entrance to the Arceuus church.", ANCIENT_WIZARDS),
 	ZEAH_DARK_ALTAR(MASTER, new WorldPoint(1698, 3880, 0), ZEAH, "West of the Dark Altar.", BRASSICAN_MAGE),
 	ZEAH_ARCEUUS_HOUSE(MASTER, new WorldPoint(1710, 3700, 0), ZEAH, "By the south-eastern entrance to Arceuus.", BRASSICAN_MAGE),
 	ZEAH_ESSENCE_MINE(MASTER, new WorldPoint(1762, 3852, 0), ZEAH, "By the Arceuus essence mine.", BRASSICAN_MAGE),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/thieving/CrowdsourcingThieving.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/thieving/CrowdsourcingThieving.java
index 669f63716..f674aace8 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/thieving/CrowdsourcingThieving.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/thieving/CrowdsourcingThieving.java
@@ -77,7 +77,7 @@ public class CrowdsourcingThieving
 
 		return equipmentContainer.contains(ItemID.THIEVING_CAPE) ||
 			equipmentContainer.contains(ItemID.THIEVING_CAPET) ||
-			equipmentContainer.contains(ItemID.MAX_CAPE);
+			equipmentContainer.contains(ItemID.MAX_CAPE_13342);
 	}
 
 	private int getArdougneDiary()
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java
index 16611b439..b24e2361f 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java
@@ -126,21 +126,20 @@ class DevToolsOverlay extends Overlay
 			renderGraphicsObjects(graphics);
 		}
 
-		if (plugin.getRoofs().isActive())
+		if (plugin.getTileFlags().isActive())
 		{
-			renderRoofs(graphics);
+			renderTileFlags(graphics);
 		}
 
 		return null;
 	}
 
-	private void renderRoofs(Graphics2D graphics)
+	private void renderTileFlags(Graphics2D graphics)
 	{
 		Scene scene = client.getScene();
 		Tile[][][] tiles = scene.getTiles();
 		byte[][][] settings = client.getTileSettings();
 		int z = client.getPlane();
-		String text = "R";
 
 		for (int x = 0; x < Constants.SCENE_SIZE; ++x)
 		{
@@ -153,19 +152,36 @@ class DevToolsOverlay extends Overlay
 					continue;
 				}
 
+				boolean isbridge = (settings[1][x][y] & Constants.TILE_FLAG_BRIDGE) != 0;
 				int flag = settings[z][x][y];
-				if ((flag & Constants.TILE_FLAG_UNDER_ROOF) == 0)
+				boolean isvisbelow = (flag & Constants.TILE_FLAG_VIS_BELOW) != 0;
+				boolean hasroof = (flag & Constants.TILE_FLAG_UNDER_ROOF) != 0;
+				if (!isbridge && !isvisbelow && !hasroof)
 				{
 					continue;
 				}
 
-				Point loc = Perspective.getCanvasTextLocation(client, graphics, tile.getLocalLocation(), text, z);
+				String s = "";
+				if (isbridge)
+				{
+					s += "B";
+				}
+				if (isvisbelow)
+				{
+					s += "V";
+				}
+				if (hasroof)
+				{
+					s += "R";
+				}
+
+				Point loc = Perspective.getCanvasTextLocation(client, graphics, tile.getLocalLocation(), s, z);
 				if (loc == null)
 				{
 					continue;
 				}
 
-				OverlayUtil.renderTextLocation(graphics, loc, text, Color.RED);
+				OverlayUtil.renderTextLocation(graphics, loc, s, Color.RED);
 			}
 		}
 	}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java
index c751b25aa..05b09c468 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java
@@ -118,7 +118,7 @@ class DevToolsPanel extends PluginPanel
 		container.add(plugin.getTileLocation());
 		container.add(plugin.getCameraPosition());
 
-		container.add(plugin.getChunkBorders());
+		container.add(plugin.getZoneBorders());
 		container.add(plugin.getMapSquares());
 		container.add(plugin.getLoadingLines());
 
@@ -185,7 +185,7 @@ class DevToolsPanel extends PluginPanel
 		disconnectBtn.addActionListener(e -> clientThread.invoke(() -> client.setGameState(GameState.CONNECTION_LOST)));
 		container.add(disconnectBtn);
 
-		container.add(plugin.getRoofs());
+		container.add(plugin.getTileFlags());
 
 		try
 		{
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java
index a2e42b8f2..a6c935603 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java
@@ -148,7 +148,7 @@ public class DevToolsPlugin extends Plugin
 	private DevToolsButton decorations;
 	private DevToolsButton projectiles;
 	private DevToolsButton location;
-	private DevToolsButton chunkBorders;
+	private DevToolsButton zoneBorders;
 	private DevToolsButton mapSquares;
 	private DevToolsButton loadingLines;
 	private DevToolsButton validMovement;
@@ -165,7 +165,7 @@ public class DevToolsPlugin extends Plugin
 	private DevToolsButton soundEffects;
 	private DevToolsButton scriptInspector;
 	private DevToolsButton inventoryInspector;
-	private DevToolsButton roofs;
+	private DevToolsButton tileFlags;
 	private DevToolsButton shell;
 	private DevToolsButton menus;
 	private DevToolsButton uiDefaultsInspector;
@@ -250,7 +250,7 @@ public class DevToolsPlugin extends Plugin
 		tileLocation = new DevToolsButton("Tile Location");
 		cameraPosition = new DevToolsButton("Camera Position");
 
-		chunkBorders = new DevToolsButton("Chunk Borders");
+		zoneBorders = new DevToolsButton("Zone Borders");
 		mapSquares = new DevToolsButton("Map Squares");
 		loadingLines = new DevToolsButton("Loading Lines");
 
@@ -266,7 +266,7 @@ public class DevToolsPlugin extends Plugin
 		soundEffects = new DevToolsButton("Sound Effects");
 		scriptInspector = new DevToolsButton("Script Inspector");
 		inventoryInspector = new DevToolsButton("Inventory Inspector");
-		roofs = new DevToolsButton("Roofs");
+		tileFlags = new DevToolsButton("Tile flags");
 		shell = new DevToolsButton("Shell");
 		menus = new DevToolsButton("Menus");
 
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/SceneOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/SceneOverlay.java
index 1eae7a09a..2c089ba73 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/SceneOverlay.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/SceneOverlay.java
@@ -36,6 +36,7 @@ import java.util.stream.Stream;
 import javax.inject.Inject;
 import net.runelite.api.Actor;
 import net.runelite.api.Client;
+import net.runelite.api.Constants;
 import net.runelite.api.NPC;
 import net.runelite.api.Perspective;
 import net.runelite.api.Player;
@@ -58,8 +59,7 @@ public class SceneOverlay extends Overlay
 	private static final Color INTERACTING_COLOR = Color.CYAN;
 
 	private static final int LOCAL_TILE_SIZE = Perspective.LOCAL_TILE_SIZE;
-	private static final int CHUNK_SIZE = 8;
-	private static final int MAP_SQUARE_SIZE = CHUNK_SIZE * CHUNK_SIZE; // 64
+	private static final int MAP_SQUARE_SIZE = Constants.CHUNK_SIZE * Constants.CHUNK_SIZE; // 64
 	private static final int CULL_CHUNK_BORDERS_RANGE = 16;
 	private static final int STROKE_WIDTH = 4;
 	private static final int CULL_LINE_OF_SIGHT_RANGE = 10;
@@ -87,9 +87,9 @@ public class SceneOverlay extends Overlay
 	@Override
 	public Dimension render(Graphics2D graphics)
 	{
-		if (plugin.getChunkBorders().isActive())
+		if (plugin.getZoneBorders().isActive())
 		{
-			renderChunkBorders(graphics);
+			renderZoneBorders(graphics);
 		}
 
 		if (plugin.getMapSquares().isActive())
@@ -120,28 +120,24 @@ public class SceneOverlay extends Overlay
 		return null;
 	}
 
-	private void renderChunkBorders(Graphics2D graphics)
+	private void renderZoneBorders(Graphics2D graphics)
 	{
-		WorldPoint wp = client.getLocalPlayer().getWorldLocation();
-		int startX = (wp.getX() - CULL_CHUNK_BORDERS_RANGE + CHUNK_SIZE - 1) / CHUNK_SIZE * CHUNK_SIZE;
-		int startY = (wp.getY() - CULL_CHUNK_BORDERS_RANGE + CHUNK_SIZE - 1) / CHUNK_SIZE * CHUNK_SIZE;
-		int endX = (wp.getX() + CULL_CHUNK_BORDERS_RANGE) / CHUNK_SIZE * CHUNK_SIZE;
-		int endY = (wp.getY() + CULL_CHUNK_BORDERS_RANGE) / CHUNK_SIZE * CHUNK_SIZE;
+		int startX = Constants.CHUNK_SIZE;
+		int startZ = Constants.CHUNK_SIZE;
+		int endX = Constants.SCENE_SIZE - Constants.CHUNK_SIZE;
+		int endZ = Constants.SCENE_SIZE - Constants.CHUNK_SIZE;
 
 		graphics.setStroke(new BasicStroke(STROKE_WIDTH));
 		graphics.setColor(CHUNK_BORDER_COLOR);
 
 		GeneralPath path = new GeneralPath();
-		for (int x = startX; x <= endX; x += CHUNK_SIZE)
+		for (int x = startX; x <= endX; x += Constants.CHUNK_SIZE)
 		{
-			LocalPoint lp1 = LocalPoint.fromWorld(client, x, wp.getY() - CULL_CHUNK_BORDERS_RANGE);
-			LocalPoint lp2 = LocalPoint.fromWorld(client, x, wp.getY() + CULL_CHUNK_BORDERS_RANGE);
-
 			boolean first = true;
-			for (int y = lp1.getY(); y <= lp2.getY(); y += LOCAL_TILE_SIZE)
+			for (int z = startZ; z <= endZ; z += Constants.CHUNK_SIZE)
 			{
 				Point p = Perspective.localToCanvas(client,
-					new LocalPoint(lp1.getX() - LOCAL_TILE_SIZE / 2, y - LOCAL_TILE_SIZE / 2),
+					x << Perspective.LOCAL_COORD_BITS, z << Perspective.LOCAL_COORD_BITS,
 					client.getPlane());
 				if (p != null)
 				{
@@ -157,16 +153,13 @@ public class SceneOverlay extends Overlay
 				}
 			}
 		}
-		for (int y = startY; y <= endY; y += CHUNK_SIZE)
+		for (int z = startZ; z <= endZ; z += Constants.CHUNK_SIZE)
 		{
-			LocalPoint lp1 = LocalPoint.fromWorld(client, wp.getX() - CULL_CHUNK_BORDERS_RANGE, y);
-			LocalPoint lp2 = LocalPoint.fromWorld(client, wp.getX() + CULL_CHUNK_BORDERS_RANGE, y);
-
 			boolean first = true;
-			for (int x = lp1.getX(); x <= lp2.getX(); x += LOCAL_TILE_SIZE)
+			for (int x = startX; x <= endX; x += Constants.CHUNK_SIZE)
 			{
 				Point p = Perspective.localToCanvas(client,
-					new LocalPoint(x - LOCAL_TILE_SIZE / 2, lp1.getY() - LOCAL_TILE_SIZE / 2),
+					x << Perspective.LOCAL_COORD_BITS, z << Perspective.LOCAL_COORD_BITS,
 					client.getPlane());
 				if (p != null)
 				{
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java
index c372eae99..d736f48c8 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java
@@ -75,11 +75,6 @@ class GroundItem
 		return gePrice * quantity;
 	}
 
-	boolean isMine()
-	{
-		return ownership == OWNERSHIP_SELF || ownership == OWNERSHIP_GROUP;
-	}
-
 	void reset()
 	{
 		highlighted = hidden = false;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java
index 169350546..08f25dfa7 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java
@@ -39,11 +39,13 @@ import net.runelite.client.plugins.grounditems.config.ItemHighlightMode;
 import net.runelite.client.plugins.grounditems.config.MenuHighlightMode;
 import net.runelite.client.plugins.grounditems.config.PriceDisplayMode;
 import net.runelite.client.plugins.grounditems.config.ValueCalculationMode;
+import net.runelite.client.plugins.grounditems.config.OwnershipFilterMode;
 
 @ConfigGroup(GroundItemsConfig.GROUP)
 public interface GroundItemsConfig extends Config
 {
 	String GROUP = "grounditems";
+	String OWNERSHIP_FILTER_MODE = "ownershipFilterMode";
 
 	@ConfigSection(
 		name = "Item Lists",
@@ -362,14 +364,14 @@ public interface GroundItemsConfig extends Config
 	}
 
 	@ConfigItem(
-		keyName = "onlyShowLoot",
-		name = "Only show own items",
-		description = "Only shows items that are yours or you can pick up",
+		keyName = OWNERSHIP_FILTER_MODE,
+		name = "Ownership filter",
+		description = "Show all items, takeable items, or only your or your group's drops.",
 		position = 25
 	)
-	default boolean onlyShowOwnItems()
+	default OwnershipFilterMode ownershipFilterMode()
 	{
-		return false;
+		return OwnershipFilterMode.ALL;
 	}
 
 	@ConfigItem(
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 0684df67d..c03aa0b7b 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
@@ -43,11 +43,13 @@ import net.runelite.api.ItemID;
 import net.runelite.api.Perspective;
 import net.runelite.api.Player;
 import net.runelite.api.Point;
+import net.runelite.api.Varbits;
 import net.runelite.api.coords.LocalPoint;
 import net.runelite.api.coords.WorldPoint;
 import net.runelite.client.plugins.grounditems.config.DespawnTimerMode;
 import static net.runelite.client.plugins.grounditems.config.ItemHighlightMode.MENU;
 import static net.runelite.client.plugins.grounditems.config.ItemHighlightMode.NONE;
+import net.runelite.client.plugins.grounditems.config.OwnershipFilterMode;
 import net.runelite.client.plugins.grounditems.config.PriceDisplayMode;
 import net.runelite.client.ui.overlay.Overlay;
 import net.runelite.client.ui.overlay.OverlayLayer;
@@ -169,16 +171,17 @@ public class GroundItemsOverlay extends Overlay
 		plugin.setHiddenBoxBounds(null);
 		plugin.setHighlightBoxBounds(null);
 
-		final boolean onlyShowLoot = config.onlyShowOwnItems();
 		final DespawnTimerMode groundItemTimers = config.groundItemTimers();
 		final boolean outline = config.textOutline();
+		final OwnershipFilterMode ownershipFilterMode = config.ownershipFilterMode();
+		final int accountType = client.getVarbitValue(Varbits.ACCOUNT_TYPE);
 
 		for (GroundItem item : groundItemList)
 		{
 			final LocalPoint groundPoint = LocalPoint.fromWorld(client, item.getLocation());
 
 			if (groundPoint == null || localLocation.distanceTo(groundPoint) > MAX_DISTANCE
-				|| (onlyShowLoot && !item.isMine()))
+				|| !plugin.shouldDisplayItem(ownershipFilterMode, item.getOwnership(), accountType))
 			{
 				continue;
 			}
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 ee62ad4b7..7a7c7518a 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
@@ -67,6 +67,9 @@ import net.runelite.api.MenuAction;
 import net.runelite.api.MenuEntry;
 import net.runelite.api.Tile;
 import net.runelite.api.TileItem;
+import static net.runelite.api.TileItem.OWNERSHIP_GROUP;
+import static net.runelite.api.TileItem.OWNERSHIP_OTHER;
+import static net.runelite.api.TileItem.OWNERSHIP_SELF;
 import net.runelite.api.Varbits;
 import net.runelite.api.coords.WorldPoint;
 import net.runelite.api.events.ClientTick;
@@ -81,6 +84,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.ProfileChanged;
 import net.runelite.client.game.ItemManager;
 import net.runelite.client.input.KeyManager;
 import net.runelite.client.input.MouseManager;
@@ -92,6 +96,7 @@ import net.runelite.client.plugins.grounditems.config.MenuHighlightMode;
 import static net.runelite.client.plugins.grounditems.config.MenuHighlightMode.BOTH;
 import static net.runelite.client.plugins.grounditems.config.MenuHighlightMode.NAME;
 import static net.runelite.client.plugins.grounditems.config.MenuHighlightMode.OPTION;
+import net.runelite.client.plugins.grounditems.config.OwnershipFilterMode;
 import net.runelite.client.ui.components.colorpicker.ColorPickerManager;
 import net.runelite.client.ui.components.colorpicker.RuneliteColorPicker;
 import net.runelite.client.ui.overlay.OverlayManager;
@@ -203,6 +208,7 @@ public class GroundItemsPlugin extends Plugin
 		overlayManager.add(overlay);
 		mouseManager.registerMouseListener(mouseAdapter);
 		keyManager.registerKeyListener(hotkeyListener);
+		migrate();
 		executor.execute(this::reset);
 	}
 
@@ -231,6 +237,27 @@ public class GroundItemsPlugin extends Plugin
 		}
 	}
 
+	@Subscribe
+	public void onProfileChanged(ProfileChanged profileChanged)
+	{
+		migrate();
+	}
+
+	private void migrate()
+	{
+		//This was the old "Only show own items" config, which is now obsolete and replaced by ownership filter
+		Boolean onlyShowOwnItems = configManager.getConfiguration(GroundItemsConfig.GROUP, "onlyShowLoot", Boolean.class);
+		if (onlyShowOwnItems != null)
+		{
+			if (onlyShowOwnItems)
+			{
+				//The old behavior maps to 'Drops' in the new dropdown
+				configManager.setConfiguration(GroundItemsConfig.GROUP, GroundItemsConfig.OWNERSHIP_FILTER_MODE, OwnershipFilterMode.DROPS);
+			}
+			configManager.unsetConfiguration(GroundItemsConfig.GROUP, "onlyShowLoot");
+		}
+	}
+
 	@Subscribe
 	public void onGameStateChanged(final GameStateChanged event)
 	{
@@ -260,7 +287,7 @@ public class GroundItemsPlugin extends Plugin
 			collectedGroundItems.put(tile.getWorldLocation(), item.getId(), groundItem);
 		}
 
-		if (groundItem.isMine() || !config.onlyShowOwnItems())
+		if (shouldDisplayItem(config.ownershipFilterMode(), groundItem.getOwnership(), client.getVarbitValue(Varbits.ACCOUNT_TYPE)))
 		{
 			notifyHighlightedItem(groundItem);
 		}
@@ -389,16 +416,6 @@ public class GroundItemsPlugin extends Plugin
 		final int alchPrice = itemComposition.getHaPrice();
 		final int despawnTime = item.getDespawnTime() - client.getTickCount();
 		final int visibleTime = item.getVisibleTime() - client.getTickCount();
-		int ownership = item.getOwnership();
-		final int accountType = client.getVarbitValue(Varbits.ACCOUNT_TYPE);
-		boolean isGim = accountType >= 4 && accountType <= 6; // ~is_group_iron
-
-		// from ~script7240
-		if (ownership == TileItem.OWNERSHIP_GROUP && !isGim)
-		{
-			// non-gims see loot from other people as "group" loop since they are both group -1.
-			ownership = TileItem.OWNERSHIP_OTHER;
-		}
 
 		final GroundItem groundItem = GroundItem.builder()
 			.id(itemId)
@@ -409,7 +426,7 @@ public class GroundItemsPlugin extends Plugin
 			.haPrice(alchPrice)
 			.height(tile.getItemLayer().getHeight())
 			.tradeable(itemComposition.isTradeable())
-			.ownership(ownership)
+			.ownership(item.getOwnership())
 			.isPrivate(item.isPrivate())
 			.spawnTime(Instant.now())
 			.stackable(itemComposition.isStackable())
@@ -747,9 +764,11 @@ public class GroundItemsPlugin extends Plugin
 		int highestPrice = -1;
 		GroundItem highestItem = null;
 		Collection<GroundItem> groundItems = collectedGroundItems.row(worldPoint).values();
+		final OwnershipFilterMode ownershipFilterMode = config.ownershipFilterMode();
+		final int accountType = client.getVarbitValue(Varbits.ACCOUNT_TYPE);
 		for (GroundItem groundItem : groundItems)
 		{
-			if ((config.onlyShowOwnItems() && !groundItem.isMine()))
+			if (!shouldDisplayItem(ownershipFilterMode, groundItem.getOwnership(), accountType))
 			{
 				continue;
 			}
@@ -854,4 +873,22 @@ public class GroundItemsPlugin extends Plugin
 	{
 		configManager.unsetConfiguration(GroundItemsConfig.GROUP, HIGHLIGHT_COLOR_PREFIX + itemId);
 	}
+
+	/*
+	 * All      -> none | self | other | group
+	 * Drops    -> self | group
+	 * Takeable -> none | self | group | (if a main then other)
+	 */
+	boolean shouldDisplayItem(OwnershipFilterMode filterMode, int ownership, int accountType)
+	{
+		switch (filterMode)
+		{
+			case DROPS:
+				return ownership == OWNERSHIP_SELF || ownership == OWNERSHIP_GROUP;
+			case TAKEABLE:
+				return ownership != OWNERSHIP_OTHER || accountType == 0; // Mains can always take items
+			default:
+				return true;
+		}
+	}
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/OwnershipFilterMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/OwnershipFilterMode.java
new file mode 100644
index 000000000..ccdef21c1
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/config/OwnershipFilterMode.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2024, Moshe Ben-Zacharia <https://github.com/MosheBenZacharia>
+ * 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.grounditems.config;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@RequiredArgsConstructor
+public enum OwnershipFilterMode
+{
+	ALL("All"),
+	TAKEABLE("Takeable"),
+	DROPS("Drops");
+
+	private final String name;
+
+	@Override
+	public String toString()
+	{
+		return name;
+	}
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java
index 24a137054..ad29096fe 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java
@@ -342,6 +342,10 @@ public class IdleNotifierPlugin extends Plugin
 			case HERBLORE_PESTLE_AND_MORTAR:
 			case HERBLORE_POTIONMAKING:
 			case HERBLORE_MAKE_TAR:
+			case HERBLORE_MIXOLOGY_CONCENTRATE:
+			case HERBLORE_MIXOLOGY_CRYSTALIZE:
+			case HERBLORE_MIXOLOGY_HOMOGENIZE:
+			case HERBLORE_MIXOLOGY_REFINER:
 			/* Magic */
 			case MAGIC_CHARGING_ORBS:
 			case MAGIC_LUNAR_PLANK_MAKE:
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightPlugin.java
index 6814fcf1e..0664e2b0a 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightPlugin.java
@@ -31,6 +31,7 @@ import lombok.AccessLevel;
 import lombok.Getter;
 import net.runelite.api.Actor;
 import net.runelite.api.Client;
+import net.runelite.api.Constants;
 import net.runelite.api.DecorativeObject;
 import net.runelite.api.GameObject;
 import net.runelite.api.GameState;
@@ -199,8 +200,10 @@ public class InteractHighlightPlugin extends Plugin
 
 	TileObject findTileObject(int x, int y, int id)
 	{
+		x += (Constants.EXTENDED_SCENE_SIZE - Constants.SCENE_SIZE) / 2;
+		y += (Constants.EXTENDED_SCENE_SIZE - Constants.SCENE_SIZE) / 2;
 		Scene scene = client.getScene();
-		Tile[][][] tiles = scene.getTiles();
+		Tile[][][] tiles = scene.getExtendedTiles();
 		Tile tile = tiles[client.getPlane()][x][y];
 		if (tile != null)
 		{
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithCharge.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithCharge.java
index ed815bb78..774295aad 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithCharge.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithCharge.java
@@ -546,6 +546,22 @@ enum ItemWithCharge
 	FORGOTTEN_BR2(POTION, FORGOTTEN_BREW2, 2),
 	FORGOTTEN_BR3(POTION, FORGOTTEN_BREW3, 3),
 	FORGOTTEN_BR4(POTION, FORGOTTEN_BREW4, 4),
+	MENAPHITE_REM1(POTION, MENAPHITE_REMEDY1, 1),
+	MENAPHITE_REM2(POTION, MENAPHITE_REMEDY2, 2),
+	MENAPHITE_REM3(POTION, MENAPHITE_REMEDY3, 3),
+	MENAPHITE_REM4(POTION, MENAPHITE_REMEDY4, 4),
+	EXTENDED_ANTI_VENOM1(POTION, EXTENDED_ANTIVENOM1, 1),
+	EXTENDED_ANTI_VENOM2(POTION, EXTENDED_ANTIVENOM2, 2),
+	EXTENDED_ANTI_VENOM3(POTION, EXTENDED_ANTIVENOM3, 3),
+	EXTENDED_ANTI_VENOM4(POTION, EXTENDED_ANTIVENOM4, 4),
+	GOADING1(POTION, GOADING_POTION1, 1),
+	GOADING2(POTION, GOADING_POTION2, 2),
+	GOADING3(POTION, GOADING_POTION3, 3),
+	GOADING4(POTION, GOADING_POTION4, 4),
+	PRAYER_REGENERATION1(POTION, PRAYER_REGENERATION_POTION1, 1),
+	PRAYER_REGENERATION2(POTION, PRAYER_REGENERATION_POTION2, 2),
+	PRAYER_REGENERATION3(POTION, PRAYER_REGENERATION_POTION3, 3),
+	PRAYER_REGENERATION4(POTION, PRAYER_REGENERATION_POTION4, 4),
 	;
 
 	private final ItemChargeType type;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChanges.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChanges.java
index e526a7be6..eab1a0a39 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChanges.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChanges.java
@@ -34,9 +34,13 @@ import static net.runelite.api.ItemID.*;
 import static net.runelite.client.plugins.itemstats.Builders.*;
 import net.runelite.client.plugins.itemstats.delta.DeltaPercentage;
 import net.runelite.client.plugins.itemstats.food.Anglerfish;
+import net.runelite.client.plugins.itemstats.food.CookedBream;
+import net.runelite.client.plugins.itemstats.food.CookedMossLizard;
+import net.runelite.client.plugins.itemstats.special.SunlightMoth;
 import net.runelite.client.plugins.itemstats.potions.Ambrosia;
 import net.runelite.client.plugins.itemstats.potions.AncientBrew;
 import net.runelite.client.plugins.itemstats.potions.MixedPotion;
+import net.runelite.client.plugins.itemstats.potions.MoonlightPotion;
 import net.runelite.client.plugins.itemstats.potions.PrayerPotion;
 import net.runelite.client.plugins.itemstats.potions.SaradominBrew;
 import net.runelite.client.plugins.itemstats.potions.StaminaPotion;
@@ -56,6 +60,13 @@ public class ItemStatChanges
 		init();
 	}
 
+	public static final SingleEffect ATTACK_POT = boost(ATTACK, perc(.10, 3));
+	public static final SingleEffect STRENGTH_POT = boost(STRENGTH, perc(.10, 3));
+	public static final SingleEffect DEFENCE_POT = boost(DEFENCE, perc(.10, 3));
+	public static final SingleEffect SUPER_ATTACK_POT = boost(ATTACK, perc(.15, 5));
+	public static final SingleEffect SUPER_STRENGTH_POT = boost(STRENGTH, perc(.15, 5));
+	public static final SingleEffect SUPER_DEFENCE_POT = boost(DEFENCE, perc(.15, 5));
+
 	private void init()
 	{
 		add(food(-5), POISON_KARAMBWAN);
@@ -105,6 +116,8 @@ public class ItemStatChanges
 		add(food(maxHP -> (int) Math.ceil(maxHP * .06)), STRAWBERRY);
 		add(food(maxHP -> (int) Math.ceil(maxHP * .05)), WATERMELON_SLICE);
 		add(food(perc(.1, 1)), COOKED_SWEETCORN, SWEETCORN_7088 /* Bowl of cooked sweetcorn */);
+		add(new CookedBream(), COOKED_BREAM);
+		add(new CookedMossLizard(), COOKED_MOSS_LIZARD);
 		add(combo(food(1), boost(DEFENCE, perc(.02, 1))), CABBAGE_1967 /* Draynor Manor */);
 		add(combo(food(8), heal(RUN_ENERGY, 5)), PAPAYA_FRUIT);
 		add(combo(food(3), boost(ATTACK, perc(.02, 2))), CUP_OF_TEA_1978 /* Standard tea */);
@@ -168,6 +181,12 @@ public class ItemStatChanges
 		add(combo(food(2), heal(PRAYER, perc(.04, -2))), BLOODY_BRACER);
 		add(combo(food(1), boost(AGILITY, 1), heal(STRENGTH, -1)), ELVEN_DAWN);
 		add(combo(boost(RANGED, 4), new BoostedStatBoost(STRENGTH, false, perc(.04, -2)), new BoostedStatBoost(MAGIC, false, perc(.04, -2))), LIZARDKICKER);
+		add(combo(food(1), boost(HUNTER, 2), dec(STRENGTH, 1), dec(ATTACK, 2)), TRAPPERS_TIPPLE);
+		add(combo(food(1), boost(AGILITY, 1), boost(STRENGTH, 1), new BoostedStatBoost(ATTACK, false, perc(.05, -1))), SUNBEAM_ALE);
+		add(combo(food(5), boost(STRENGTH, perc(.05, 1)), new BoostedStatBoost(ATTACK, false, perc(0.02, -3))), MOONLITE);
+		add(combo(food(5), boost(STRENGTH, perc(.05, 1)), new BoostedStatBoost(ATTACK, false, perc(0.02, -3))), SUNSHINE);
+		add(combo(food(5), boost(STRENGTH, 5), new BoostedStatBoost(ATTACK, false, perc(.02, -3))), RUM_28896);
+		add(combo(food(16), boost(WOODCUTTING, 1), heal(ATTACK, -5), heal(FLETCHING, -1)), ECLIPSE_RED);
 
 		// Sq'irk Juice
 		add(heal(RUN_ENERGY, 5), WINTER_SQIRKJUICE);
@@ -176,73 +195,68 @@ public class ItemStatChanges
 		add(combo(heal(RUN_ENERGY, 20), boost(THIEVING, 3)), SUMMER_SQIRKJUICE);
 
 		// Combat potions
-		final SingleEffect attackPot = boost(ATTACK, perc(.10, 3));
-		final SingleEffect strengthPot = boost(STRENGTH, perc(.10, 3));
-		final SingleEffect defencePot = boost(DEFENCE, perc(.10, 3));
-		final Effect combatPot = combo(attackPot, strengthPot);
+		final Effect combatPot = combo(ATTACK_POT, STRENGTH_POT);
 		final Effect magicEssence = boost(MAGIC, 3);
 		final SingleEffect magicPot = boost(MAGIC, 4);
 		final SingleEffect imbuedHeart = boost(MAGIC, perc(.10, 1));
 		final SingleEffect rangingPot = boost(RANGED, perc(.10, 4));
-		final SingleEffect superAttackPot = boost(ATTACK, perc(.15, 5));
-		final SingleEffect superStrengthPot = boost(STRENGTH, perc(.15, 5));
-		final SingleEffect superDefencePot = boost(DEFENCE, perc(.15, 5));
 		final SingleEffect superMagicPot = boost(MAGIC, perc(.15, 5));
 		final SingleEffect superRangingPot = boost(RANGED, perc(.15, 5));
 		final SingleEffect divinePot = heal(HITPOINTS, -10);
 		final Effect zamorakBrew = combo(boost(ATTACK, perc(.20, 2)), boost(STRENGTH, perc(.12, 2)), heal(PRAYER, perc(.10, 0)), new BoostedStatBoost(DEFENCE, false, perc(.10, -2)), new BoostedStatBoost(HITPOINTS, false, perc(-.12, 0)));
 		final Effect ancientBrew = new AncientBrew(.05, 2);
-		add(attackPot, ATTACK_POTION1, ATTACK_POTION2, ATTACK_POTION3, ATTACK_POTION4);
-		add(strengthPot, STRENGTH_POTION1, STRENGTH_POTION2, STRENGTH_POTION3, STRENGTH_POTION4);
-		add(defencePot, DEFENCE_POTION1, DEFENCE_POTION2, DEFENCE_POTION3, DEFENCE_POTION4);
+		add(ATTACK_POT, ATTACK_POTION1, ATTACK_POTION2, ATTACK_POTION3, ATTACK_POTION4);
+		add(STRENGTH_POT, STRENGTH_POTION1, STRENGTH_POTION2, STRENGTH_POTION3, STRENGTH_POTION4);
+		add(DEFENCE_POT, DEFENCE_POTION1, DEFENCE_POTION2, DEFENCE_POTION3, DEFENCE_POTION4);
 		add(magicPot, MAGIC_POTION1, MAGIC_POTION2, MAGIC_POTION3, MAGIC_POTION4);
 		add(rangingPot, RANGING_POTION1, RANGING_POTION2, RANGING_POTION3, RANGING_POTION4,
 			RANGING_POTION4_23551, RANGING_POTION3_23553, RANGING_POTION2_23555, RANGING_POTION1_23557 /* LMS */);
 		add(combatPot, COMBAT_POTION1, COMBAT_POTION2, COMBAT_POTION3, COMBAT_POTION4,
 			COMBAT_POTION4_26150, COMBAT_POTION3_26151, COMBAT_POTION2_26152, COMBAT_POTION1_26153 /* Deadman starter pack */);
-		add(superAttackPot, SUPER_ATTACK1, SUPER_ATTACK2, SUPER_ATTACK3, SUPER_ATTACK4);
-		add(superStrengthPot, SUPER_STRENGTH1, SUPER_STRENGTH2, SUPER_STRENGTH3, SUPER_STRENGTH4);
-		add(superDefencePot, SUPER_DEFENCE1, SUPER_DEFENCE2, SUPER_DEFENCE3, SUPER_DEFENCE4);
+		add(SUPER_ATTACK_POT, SUPER_ATTACK1, SUPER_ATTACK2, SUPER_ATTACK3, SUPER_ATTACK4);
+		add(SUPER_STRENGTH_POT, SUPER_STRENGTH1, SUPER_STRENGTH2, SUPER_STRENGTH3, SUPER_STRENGTH4);
+		add(SUPER_DEFENCE_POT, SUPER_DEFENCE1, SUPER_DEFENCE2, SUPER_DEFENCE3, SUPER_DEFENCE4);
 		add(magicEssence, MAGIC_ESSENCE1, MAGIC_ESSENCE2, MAGIC_ESSENCE3, MAGIC_ESSENCE4);
-		add(combo(superAttackPot, superStrengthPot, superDefencePot), SUPER_COMBAT_POTION1, SUPER_COMBAT_POTION2, SUPER_COMBAT_POTION3, SUPER_COMBAT_POTION4);
+		add(combo(SUPER_ATTACK_POT, SUPER_STRENGTH_POT, SUPER_DEFENCE_POT), SUPER_COMBAT_POTION1, SUPER_COMBAT_POTION2, SUPER_COMBAT_POTION3, SUPER_COMBAT_POTION4);
 		add(zamorakBrew, ZAMORAK_BREW1, ZAMORAK_BREW2, ZAMORAK_BREW3, ZAMORAK_BREW4);
 		add(new SaradominBrew(0.15, 0.2, 0.1, 2, 2), SARADOMIN_BREW1, SARADOMIN_BREW2, SARADOMIN_BREW3,
 			SARADOMIN_BREW4, SARADOMIN_BREW4_23575, SARADOMIN_BREW3_23577, SARADOMIN_BREW2_23579, SARADOMIN_BREW1_23581 /* LMS */);
 		add(superRangingPot, SUPER_RANGING_1, SUPER_RANGING_2, SUPER_RANGING_3, SUPER_RANGING_4);
 		add(superMagicPot, SUPER_MAGIC_POTION_1, SUPER_MAGIC_POTION_2, SUPER_MAGIC_POTION_3, SUPER_MAGIC_POTION_4);
-		add(combo(rangingPot, superDefencePot), BASTION_POTION1, BASTION_POTION2, BASTION_POTION3, BASTION_POTION4);
-		add(combo(magicPot, superDefencePot), BATTLEMAGE_POTION1, BATTLEMAGE_POTION2, BATTLEMAGE_POTION3, BATTLEMAGE_POTION4);
+		add(combo(rangingPot, SUPER_DEFENCE_POT), BASTION_POTION1, BASTION_POTION2, BASTION_POTION3, BASTION_POTION4);
+		add(combo(magicPot, SUPER_DEFENCE_POT), BATTLEMAGE_POTION1, BATTLEMAGE_POTION2, BATTLEMAGE_POTION3, BATTLEMAGE_POTION4);
 		add(combo(magicPot, divinePot), DIVINE_MAGIC_POTION1, DIVINE_MAGIC_POTION2, DIVINE_MAGIC_POTION3, DIVINE_MAGIC_POTION4);
 		add(combo(rangingPot, divinePot), DIVINE_RANGING_POTION1, DIVINE_RANGING_POTION2, DIVINE_RANGING_POTION3, DIVINE_RANGING_POTION4);
-		add(combo(superAttackPot, divinePot), DIVINE_SUPER_ATTACK_POTION1, DIVINE_SUPER_ATTACK_POTION2, DIVINE_SUPER_ATTACK_POTION3, DIVINE_SUPER_ATTACK_POTION4);
-		add(combo(superStrengthPot, divinePot), DIVINE_SUPER_STRENGTH_POTION1, DIVINE_SUPER_STRENGTH_POTION2, DIVINE_SUPER_STRENGTH_POTION3, DIVINE_SUPER_STRENGTH_POTION4);
-		add(combo(superDefencePot, divinePot), DIVINE_SUPER_DEFENCE_POTION1, DIVINE_SUPER_DEFENCE_POTION2, DIVINE_SUPER_DEFENCE_POTION3, DIVINE_SUPER_DEFENCE_POTION4);
-		add(combo(superAttackPot, superStrengthPot, superDefencePot, divinePot), DIVINE_SUPER_COMBAT_POTION1, DIVINE_SUPER_COMBAT_POTION2, DIVINE_SUPER_COMBAT_POTION3, DIVINE_SUPER_COMBAT_POTION4);
-		add(combo(rangingPot, superDefencePot, divinePot), DIVINE_BASTION_POTION1, DIVINE_BASTION_POTION2, DIVINE_BASTION_POTION3, DIVINE_BASTION_POTION4);
-		add(combo(magicPot, superDefencePot, divinePot), DIVINE_BATTLEMAGE_POTION1, DIVINE_BATTLEMAGE_POTION2, DIVINE_BATTLEMAGE_POTION3, DIVINE_BATTLEMAGE_POTION4);
-		add(combo(superAttackPot, superStrengthPot, superDefencePot, rangingPot, imbuedHeart),
+		add(combo(SUPER_ATTACK_POT, divinePot), DIVINE_SUPER_ATTACK_POTION1, DIVINE_SUPER_ATTACK_POTION2, DIVINE_SUPER_ATTACK_POTION3, DIVINE_SUPER_ATTACK_POTION4);
+		add(combo(SUPER_STRENGTH_POT, divinePot), DIVINE_SUPER_STRENGTH_POTION1, DIVINE_SUPER_STRENGTH_POTION2, DIVINE_SUPER_STRENGTH_POTION3, DIVINE_SUPER_STRENGTH_POTION4);
+		add(combo(SUPER_DEFENCE_POT, divinePot), DIVINE_SUPER_DEFENCE_POTION1, DIVINE_SUPER_DEFENCE_POTION2, DIVINE_SUPER_DEFENCE_POTION3, DIVINE_SUPER_DEFENCE_POTION4);
+		add(combo(SUPER_ATTACK_POT, SUPER_STRENGTH_POT, SUPER_DEFENCE_POT, divinePot), DIVINE_SUPER_COMBAT_POTION1, DIVINE_SUPER_COMBAT_POTION2, DIVINE_SUPER_COMBAT_POTION3, DIVINE_SUPER_COMBAT_POTION4);
+		add(combo(rangingPot, SUPER_DEFENCE_POT, divinePot), DIVINE_BASTION_POTION1, DIVINE_BASTION_POTION2, DIVINE_BASTION_POTION3, DIVINE_BASTION_POTION4);
+		add(combo(magicPot, SUPER_DEFENCE_POT, divinePot), DIVINE_BATTLEMAGE_POTION1, DIVINE_BATTLEMAGE_POTION2, DIVINE_BATTLEMAGE_POTION3, DIVINE_BATTLEMAGE_POTION4);
+		add(combo(SUPER_ATTACK_POT, SUPER_STRENGTH_POT, SUPER_DEFENCE_POT, rangingPot, imbuedHeart),
 			CASTLEWARS_BREW4, CASTLEWARS_BREW3, CASTLEWARS_BREW2, CASTLEWARS_BREW1);
-		add(combo(superAttackPot, superStrengthPot),
+		add(combo(SUPER_ATTACK_POT, SUPER_STRENGTH_POT),
 			SUPER_COMBAT_POTION4_23543, SUPER_COMBAT_POTION3_23545, SUPER_COMBAT_POTION2_23547, SUPER_COMBAT_POTION1_23549 /* LMS */);
 		add(ancientBrew, ANCIENT_BREW1, ANCIENT_BREW2, ANCIENT_BREW3, ANCIENT_BREW4);
 		add(new AncientBrew(.08, 3), FORGOTTEN_BREW1, FORGOTTEN_BREW2, FORGOTTEN_BREW3, FORGOTTEN_BREW4);
+		add(new MoonlightPotion(), MOONLIGHT_POTION1, MOONLIGHT_POTION2, MOONLIGHT_POTION3, MOONLIGHT_POTION4);
 
 		// Mixed combat potions
-		add(new MixedPotion(3, attackPot), ATTACK_MIX1, ATTACK_MIX2);
-		add(new MixedPotion(3, strengthPot), STRENGTH_MIX1, STRENGTH_MIX2);
+		add(new MixedPotion(3, ATTACK_POT), ATTACK_MIX1, ATTACK_MIX2);
+		add(new MixedPotion(3, STRENGTH_POT), STRENGTH_MIX1, STRENGTH_MIX2);
 		add(new MixedPotion(3, combatPot), COMBAT_MIX1, COMBAT_MIX2);
-		add(new MixedPotion(6, defencePot), DEFENCE_MIX1, DEFENCE_MIX2);
+		add(new MixedPotion(6, DEFENCE_POT), DEFENCE_MIX1, DEFENCE_MIX2);
 		add(new MixedPotion(6, magicPot), MAGIC_MIX1, MAGIC_MIX2);
 		add(new MixedPotion(6, rangingPot), RANGING_MIX1, RANGING_MIX2);
-		add(new MixedPotion(6, superAttackPot), SUPERATTACK_MIX1, SUPERATTACK_MIX2);
-		add(new MixedPotion(6, superStrengthPot), SUPER_STR_MIX1, SUPER_STR_MIX2);
-		add(new MixedPotion(6, superDefencePot), SUPER_DEF_MIX1, SUPER_DEF_MIX2);
+		add(new MixedPotion(6, SUPER_ATTACK_POT), SUPERATTACK_MIX1, SUPERATTACK_MIX2);
+		add(new MixedPotion(6, SUPER_STRENGTH_POT), SUPER_STR_MIX1, SUPER_STR_MIX2);
+		add(new MixedPotion(6, SUPER_DEFENCE_POT), SUPER_DEF_MIX1, SUPER_DEF_MIX2);
 		add(new MixedPotion(6, magicEssence), MAGIC_ESSENCE_MIX1, MAGIC_ESSENCE_MIX2);
 		add(new MixedPotion(6, zamorakBrew), ZAMORAK_MIX1, ZAMORAK_MIX2);
 		add(new MixedPotion(6, ancientBrew), ANCIENT_MIX1, ANCIENT_MIX2);
 
 		// Regular overload (NMZ)
-		add(combo(superAttackPot, superStrengthPot, superDefencePot, superRangingPot, superMagicPot, heal(HITPOINTS, -50)), OVERLOAD_1, OVERLOAD_2, OVERLOAD_3, OVERLOAD_4);
+		add(combo(SUPER_ATTACK_POT, SUPER_STRENGTH_POT, SUPER_DEFENCE_POT, superRangingPot, superMagicPot, heal(HITPOINTS, -50)), OVERLOAD_1, OVERLOAD_2, OVERLOAD_3, OVERLOAD_4);
 
 		// Bandages (Castle Wars)
 		add(new CastleWarsBandage(), BANDAGES);
@@ -358,6 +372,15 @@ public class ItemStatChanges
 		add(combo(heal(HITPOINTS, perc(.15, 1)), heal(RUN_ENERGY, 100)), BANDAGES_25202);
 		add(combo(boost(ATTACK, perc(.15, 5)), boost(STRENGTH, perc(.15, 5)), boost(DEFENCE, perc(.15, 5)), boost(RANGED, perc(.15, 5)), boost(MAGIC, perc(.15, 5)), heal(PRAYER, perc(.25, 8))), POTION_OF_POWER1, POTION_OF_POWER2, POTION_OF_POWER3, POTION_OF_POWER4);
 
+		// Moths & Butterflies
+		add(new SunlightMoth(.2, 6), SUNLIGHT_MOTH_28890, SUNLIGHT_MOTH_MIX_1, SUNLIGHT_MOTH_MIX_2);
+		add(heal(PRAYER, 22), MOONLIGHT_MOTH_28893, MOONLIGHT_MOTH_MIX_1, MOONLIGHT_MOTH_MIX_2);
+		add(heal(HITPOINTS, 15), SNOWY_KNIGHT);
+		add(heal(HITPOINTS, 8), SNOWY_KNIGHT_MIX_1, SNOWY_KNIGHT_MIX_2);
+		add(boost(ATTACK, perc(.15, 4)), RUBY_HARVEST, RUBY_HARVEST_MIX_1, RUBY_HARVEST_MIX_2);
+		add(boost(STRENGTH, perc(.15, 4)), BLACK_WARLOCK, BLACK_WARLOCK_MIX_1, BLACK_WARLOCK_MIX_2);
+		add(boost(DEFENCE, perc(.15, 4)), SAPPHIRE_GLACIALIS, SAPPHIRE_GLACIALIS_MIX_1, SAPPHIRE_GLACIALIS_MIX_2);
+
 		log.debug("{} items; {} behaviours loaded", effects.size(), new HashSet<>(effects.values()).size());
 	}
 
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/food/CookedBream.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/food/CookedBream.java
new file mode 100644
index 000000000..bd725dccc
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/food/CookedBream.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2024 Macweese
+ * 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.itemstats.food;
+
+import net.runelite.api.Client;
+import net.runelite.client.plugins.itemstats.FoodBase;
+import static net.runelite.client.plugins.itemstats.stats.Stats.COOKING;
+import static net.runelite.client.plugins.itemstats.stats.Stats.FISHING;
+
+public class CookedBream extends FoodBase
+{
+	@Override
+	public int heals(Client client)
+	{
+		int cooking = COOKING.getValue(client) / 3;
+		int fishing = FISHING.getValue(client) / 3;
+		return Math.max(7, Math.min(cooking, fishing));
+	}
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/food/CookedMossLizard.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/food/CookedMossLizard.java
new file mode 100644
index 000000000..6103028de
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/food/CookedMossLizard.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2024 Macweese
+ * 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.itemstats.food;
+
+import net.runelite.api.Client;
+import net.runelite.client.plugins.itemstats.FoodBase;
+import static net.runelite.client.plugins.itemstats.stats.Stats.COOKING;
+import static net.runelite.client.plugins.itemstats.stats.Stats.HUNTER;
+
+public class CookedMossLizard extends FoodBase
+{
+	@Override
+	public int heals(Client client)
+	{
+		int cooking = COOKING.getValue(client) / 3;
+		int hunter = HUNTER.getValue(client) / 2;
+		return Math.min(cooking, hunter);
+	}
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/potions/MoonlightPotion.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/potions/MoonlightPotion.java
new file mode 100644
index 000000000..b3230328d
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/potions/MoonlightPotion.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2024 Macweese
+ * 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.itemstats.potions;
+
+import java.util.ArrayList;
+import java.util.Optional;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import net.runelite.api.Client;
+import net.runelite.api.Skill;
+import net.runelite.client.plugins.itemstats.Effect;
+import net.runelite.client.plugins.itemstats.ItemStatChanges;
+import net.runelite.client.plugins.itemstats.SimpleStatBoost;
+import net.runelite.client.plugins.itemstats.SingleEffect;
+import net.runelite.client.plugins.itemstats.StatBoost;
+import net.runelite.client.plugins.itemstats.StatChange;
+import net.runelite.client.plugins.itemstats.StatsChanges;
+import net.runelite.client.plugins.itemstats.delta.DeltaPercentage;
+import net.runelite.client.plugins.itemstats.stats.Stat;
+import java.util.Arrays;
+import java.util.Comparator;
+import static net.runelite.client.plugins.itemstats.stats.Stats.ATTACK;
+import static net.runelite.client.plugins.itemstats.stats.Stats.DEFENCE;
+import static net.runelite.client.plugins.itemstats.stats.Stats.HERBLORE;
+import static net.runelite.client.plugins.itemstats.stats.Stats.PRAYER;
+import static net.runelite.client.plugins.itemstats.stats.Stats.STRENGTH;
+
+@RequiredArgsConstructor
+public class MoonlightPotion implements Effect
+{
+	@Override
+	public StatsChanges calculate(Client client)
+	{
+		StatsChanges changes = new StatsChanges(4);
+		ArrayList<StatChange> statChanges = new ArrayList<>();
+
+		getStatChange(client, ATTACK).ifPresent(e -> statChanges.add(e.getEffect().effect(client)));
+		getStatChange(client, DEFENCE).ifPresent(e -> statChanges.add(e.getEffect().effect(client)));
+		getStatChange(client, STRENGTH).ifPresent(e -> statChanges.add(e.getEffect().effect(client)));
+		getStatChange(client, PRAYER).ifPresent(e -> statChanges.add(e.getEffect().effect(client)));
+
+		changes.setStatChanges(statChanges.toArray(StatChange[]::new));
+		return changes;
+	}
+
+	private Optional<StatEffect> getStatChange(Client client, Stat stat)
+	{
+		return Arrays.stream(StatEffect.values())
+			.filter(t -> t.getStat() == stat && client.getBoostedSkillLevel(Skill.HERBLORE) >= t.getLevelRequirement())
+			.max(Comparator.comparingInt(StatEffect::getLevelRequirement));
+	}
+}
+
+@Getter
+enum StatEffect
+{
+	ATTACK_POTION(ATTACK, 3, ItemStatChanges.ATTACK_POT),
+	DEFENCE_POTION(DEFENCE, 30, ItemStatChanges.DEFENCE_POT),
+	// Misnomer: Moonlight potion gives a stronger effect than Super Defence on defence but requires the level for,
+	// and activates the var/effect timer for—albeit without the drain prevention from—Divine Super Defence potion
+	DIVINE_SUPER_DEFENCE_POTION(DEFENCE, 70, new SimpleStatBoost(DEFENCE, true, new DeltaPercentage(.2, 7))),
+	STRENGTH_POTION(STRENGTH, 12, ItemStatChanges.STRENGTH_POT),
+	SUPER_ATTACK_POTION(ATTACK, 45, ItemStatChanges.SUPER_ATTACK_POT),
+	SUPER_DEFENCE_POTION(DEFENCE, 66, ItemStatChanges.SUPER_DEFENCE_POT),
+	SUPER_STRENGTH_POTION(STRENGTH, 55, ItemStatChanges.SUPER_STRENGTH_POT),
+	PRAYER_POTION(PRAYER, 38, new StatBoost(PRAYER, false)
+	{
+		@Override
+		public int heals(Client client)
+		{
+			int pray = PRAYER.getMaximum(client);
+			int herb = HERBLORE.getValue(client);
+			return herb < 38 ? 0 : (int) Math.max(pray * 0.25, herb * 0.3) + 7;
+		}
+	}),
+	;
+
+	private final Stat stat;
+	private final int levelRequirement;
+	private final SingleEffect effect;
+
+	StatEffect(Stat stat, int levelRequirement, SingleEffect effect)
+	{
+		this.stat = stat;
+		this.levelRequirement = levelRequirement;
+		this.effect = effect;
+	}
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/special/SunlightMoth.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/special/SunlightMoth.java
new file mode 100644
index 000000000..d99e05c53
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/special/SunlightMoth.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2024, Macweese
+ * 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.itemstats.special;
+
+import net.runelite.api.Client;
+import net.runelite.client.plugins.itemstats.Effect;
+import net.runelite.client.plugins.itemstats.SimpleStatBoost;
+import net.runelite.client.plugins.itemstats.StatChange;
+import net.runelite.client.plugins.itemstats.StatsChanges;
+import net.runelite.client.plugins.itemstats.stats.Stat;
+import java.util.Comparator;
+import java.util.stream.Stream;
+import static net.runelite.client.plugins.itemstats.Builders.perc;
+import static net.runelite.client.plugins.itemstats.stats.Stats.*;
+
+public class SunlightMoth implements Effect
+{
+	private static final Stat[] RESTORED_STATS = {
+		ATTACK, DEFENCE, STRENGTH, RANGED, MAGIC, COOKING,
+		WOODCUTTING, FLETCHING, FISHING, FIREMAKING, CRAFTING, SMITHING, MINING,
+		HERBLORE, AGILITY, THIEVING, SLAYER, FARMING, RUNECRAFT, HUNTER,
+		CONSTRUCTION
+	};
+
+	public final double percentRestored;
+	private final int delta;
+
+	public SunlightMoth(double percentRestored, int delta)
+	{
+		this.percentRestored = percentRestored;
+		this.delta = delta;
+	}
+
+	@Override
+	public StatsChanges calculate(Client client)
+	{
+		StatsChanges changes = new StatsChanges(0);
+
+		SimpleStatBoost calcRestore = new SimpleStatBoost(null, false, perc(percentRestored, delta));
+		SimpleStatBoost calcHeal = new SimpleStatBoost(HITPOINTS, false, perc(0, 8));
+
+		changes.setStatChanges(Stream.concat(
+			Stream.of(new Stat[]{HITPOINTS})
+				.map(stat -> calcHeal.effect(client)),
+			Stream.of(RESTORED_STATS)
+				.filter(stat -> stat.getValue(client) < stat.getMaximum(client))
+				.map(stat ->
+				{
+					calcRestore.setStat(stat);
+					return calcRestore.effect(client);
+				})
+		).toArray(StatChange[]::new));
+		changes.setPositivity(Stream.of(changes.getStatChanges())
+				.map(StatChange::getPositivity)
+				.max(Comparator.naturalOrder()).orElse(null));
+		return changes;
+	}
+}
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 e59fd611e..32f02fac5 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
@@ -28,7 +28,6 @@ package net.runelite.client.plugins.motherlode;
 import net.runelite.client.config.Config;
 import net.runelite.client.config.ConfigGroup;
 import net.runelite.client.config.ConfigItem;
-import net.runelite.client.config.Units;
 
 @ConfigGroup("motherlode")
 public interface MotherlodeConfig extends Config
@@ -53,37 +52,6 @@ public interface MotherlodeConfig extends Config
 		return true;
 	}
 
-	@ConfigItem(
-		keyName = "statTimeout",
-		name = "Reset stats",
-		description = "Configures the time until statistics are reset"
-	)
-	@Units(Units.MINUTES)
-	default int statTimeout()
-	{
-		return 5;
-	}
-
-	@ConfigItem(
-		keyName = "showMiningStats",
-		name = "Show mining session stats",
-		description = "Configures whether to display mining session stats"
-	)
-	default boolean showMiningStats()
-	{
-		return true;
-	}
-
-	@ConfigItem(
-		keyName = "showMiningState",
-		name = "Show current mining state",
-		description = "Shows current mining state. 'You are currently mining' / 'You are currently NOT mining'"
-	)
-	default boolean showMiningState()
-	{
-		return true;
-	}
-
 	@ConfigItem(
 		keyName = "showGemsFound",
 		name = "Track gems found",
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java
index 1e7d100ba..6e92bc229 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java
@@ -87,6 +87,8 @@ import net.runelite.client.util.WildcardMatcher;
 public class NpcIndicatorsPlugin extends Plugin
 {
 	private static final int MAX_ACTOR_VIEW_RANGE = 15;
+	// Longest known respawn time is currently held by Suspicious Water.
+	private static final int MAX_RESPAWN_TIME_TICKS = 500;
 
 	// Option added to NPC menu
 	private static final String TAG = "Tag";
@@ -748,7 +750,9 @@ public class NpcIndicatorsPlugin extends Plugin
 						// By killing a monster and leaving the area before seeing it again, an erroneously lengthy
 						// respawn time can be recorded. Thus, if the respawn time is already set and is greater than
 						// the observed time, assume that the lower observed respawn time is correct.
-						if (mn.getRespawnTime() == -1 || respawnTime < mn.getRespawnTime())
+						// Similarly, if the observed respawn time exceeds the maximum known respawn time,
+						// we can ignore the calculated time as obviously incorrect.
+						if ((mn.getRespawnTime() == -1 || respawnTime < mn.getRespawnTime()) && respawnTime <= MAX_RESPAWN_TIME_TICKS)
 						{
 							mn.setRespawnTime(respawnTime);
 						}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerReorder.java b/runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerReorder.java
index 698250a67..1a170ee5b 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerReorder.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerReorder.java
@@ -50,6 +50,8 @@ import net.runelite.api.widgets.InterfaceID;
 import net.runelite.api.widgets.Widget;
 import static net.runelite.api.widgets.WidgetConfig.DRAG;
 import static net.runelite.api.widgets.WidgetConfig.DRAG_ON;
+import net.runelite.api.widgets.WidgetSizeMode;
+import net.runelite.api.widgets.WidgetType;
 import net.runelite.api.widgets.WidgetUtil;
 import net.runelite.client.callback.ClientThread;
 import net.runelite.client.chat.ChatMessageManager;
@@ -407,6 +409,8 @@ class PrayerReorder
 
 				++index;
 			}
+
+			createWarning(unlocked);
 		}
 
 		if (isInterfaceOpen(InterfaceID.QUICK_PRAYER))
@@ -455,6 +459,23 @@ class PrayerReorder
 		}
 	}
 
+	private void createWarning(boolean unlocked)
+	{
+		Widget w = client.getWidget(ComponentID.PRAYER_PARENT);
+		w.deleteAllChildren();
+
+		if (unlocked)
+		{
+			Widget c = w.createChild(WidgetType.RECTANGLE);
+			c.setHeightMode(WidgetSizeMode.MINUS);
+			c.setWidthMode(WidgetSizeMode.MINUS);
+			c.setTextColor(0xff0000);
+			c.setFilled(true);
+			c.setOpacity(220);
+			c.revalidate();
+		}
+	}
+
 	private boolean isInterfaceOpen(@Interface int interfaceId)
 	{
 		return client.getWidget(interfaceId, 0) != null;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/roofremoval/RoofRemovalPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/roofremoval/RoofRemovalPlugin.java
index e13f88541..28c338696 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/roofremoval/RoofRemovalPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/roofremoval/RoofRemovalPlugin.java
@@ -49,8 +49,9 @@ import static net.runelite.api.Constants.ROOF_FLAG_POSITION;
 import net.runelite.api.GameState;
 import net.runelite.api.Scene;
 import net.runelite.api.Tile;
+import net.runelite.api.WorldView;
 import net.runelite.api.coords.WorldPoint;
-import net.runelite.api.events.GameStateChanged;
+import net.runelite.api.events.PreMapLoad;
 import net.runelite.client.callback.ClientThread;
 import net.runelite.client.config.ConfigManager;
 import net.runelite.client.eventbus.Subscribe;
@@ -115,7 +116,7 @@ public class RoofRemovalPlugin extends Plugin
 
 			if (client.getGameState() == GameState.LOGGED_IN)
 			{
-				performRoofRemoval();
+				client.setGameState(GameState.LOADING);
 			}
 			return true;
 		});
@@ -137,12 +138,9 @@ public class RoofRemovalPlugin extends Plugin
 	}
 
 	@Subscribe
-	public void onGameStateChanged(GameStateChanged e)
+	public void onPreMapLoad(PreMapLoad preMapLoad)
 	{
-		if (e.getGameState() == GameState.LOGGED_IN)
-		{
-			performRoofRemoval();
-		}
+		performRoofRemoval(preMapLoad.getWorldView(), preMapLoad.getScene());
 	}
 
 	@Subscribe
@@ -204,14 +202,13 @@ public class RoofRemovalPlugin extends Plugin
 		}
 	}
 
-	private void performRoofRemoval()
+	private void performRoofRemoval(WorldView wv, Scene scene)
 	{
-		assert client.isClientThread();
-		applyRoofOverrides();
+		applyRoofOverrides(wv, scene);
 
 		Stopwatch sw = Stopwatch.createStarted();
-		client.getScene().generateHouses();
-		log.debug("House generation duration: {}", sw.stop());
+		scene.buildRoofs();
+		log.debug("Roof building duration: {}", sw.stop());
 	}
 
 	private void loadRoofOverrides() throws IOException
@@ -250,13 +247,13 @@ public class RoofRemovalPlugin extends Plugin
 		}
 	}
 
-	private void applyRoofOverrides()
+	private void applyRoofOverrides(WorldView wv, Scene scene)
 	{
 		Stopwatch sw = Stopwatch.createStarted();
 		boolean regionsHaveOverrides = false;
 
 		outer:
-		for (int regionID : client.getMapRegions())
+		for (int regionID : wv.getMapRegions())
 		{
 			if (configOverrideRegions.contains(regionID))
 			{
@@ -277,10 +274,10 @@ public class RoofRemovalPlugin extends Plugin
 			return;
 		}
 
-		Tile[][][] tiles = client.getScene().getTiles();
+		Tile[][][] tiles = scene.getTiles();
 		// the extended tile settings control what is actually drawn, the normal
 		// tile settings are just a copy
-		byte[][][] settings = client.getScene().getExtendedTileSettings();
+		byte[][][] settings = scene.getExtendedTileSettings();
 		final int SCENE_OFFSET = (Constants.EXTENDED_SCENE_SIZE - Constants.SCENE_SIZE) / 2;
 
 		for (int z = 0; z < Constants.MAX_Z; z++)
@@ -296,7 +293,7 @@ public class RoofRemovalPlugin extends Plugin
 					}
 
 					// Properly account for instances shifting worldpoints around
-					final WorldPoint wp = WorldPoint.fromLocalInstance(client, tile.getLocalLocation(), tile.getPlane());
+					final WorldPoint wp = WorldPoint.fromLocalInstance(scene, tile.getLocalLocation(), tile.getPlane());
 
 					int regionAndPlane = wp.getRegionID() << 2 | wp.getPlane();
 					if (configOverrideRegions.contains(wp.getRegionID()))
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/AgilityAction.java b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/AgilityAction.java
index 383958503..ed6d2dcd0 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/AgilityAction.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/AgilityAction.java
@@ -42,7 +42,7 @@ public enum AgilityAction implements NamedSkillAction
 {
 	GNOME_STRONGHOLD_COURSE("Gnome Stronghold", 1, 110.5f, ItemID.SWAMP_TOAD),
 	DRAYNOR_VILLAGE_ROOFTOP("Draynor Village Rooftop", 1, 120, ItemID.MARK_OF_GRACE),
-	SHAYZIEN_BASIC_COURSE("Shayzien Basic Course", 1, 153.5f, ItemID.SHAYZIEN_HELM_1),
+	SHAYZIEN_BASIC_COURSE("Shayzien Basic Course", 1, 153.5f, ItemID.SHAYZIEN_BANNER),
 	LEAPING_TROUT("Leaping trout", 15, 5, ItemID.LEAPING_TROUT),
 	AL_KHARID_ROOFTOP("Al Kharid Rooftop", 20, 216, ItemID.MARK_OF_GRACE),
 	LEAPING_SALMON("Leaping salmon", 30, 6, ItemID.LEAPING_SALMON),
@@ -51,16 +51,16 @@ public enum AgilityAction implements NamedSkillAction
 	BARBARIAN_OUTPOST("Barbarian Outpost", 35, 152.5f, ItemID.STEEL_BATTLEAXE),
 	CANIFIS_ROOFTOP("Canifis Rooftop", 40, 240, ItemID.MARK_OF_GRACE),
 	LEAPING_STURGEON("Leaping sturgeon", 45, 7, ItemID.LEAPING_STURGEON),
-	SHAYZIEN_ADVANCED_COURSE("Shayzien Advanced Course", 45, 508, ItemID.SHAYZIEN_HELM_5),
+	SHAYZIEN_ADVANCED_COURSE("Shayzien Advanced Course", 45, 508, ItemID.SHAYZIEN_BANNER),
 	APE_ATOLL_COURSE("Ape Atoll", 48, 580, ItemID.GORILLA_GREEGREE),
-	COLOSSAL_WYRM_BASIC_COURSE("Colossal Wyrm Basic", 50, 504, ItemID.TERMITES),
+	COLOSSAL_WYRM_BASIC_COURSE("Colossal Wyrm Basic", 50, 504.1f, ItemID.TERMITES),
 	FALADOR_ROOFTOP("Falador Rooftop", 50, 586, ItemID.MARK_OF_GRACE),
 	WILDERNESS_AGILITY_COURSE_TICKET("Wilderness Agility Ticket", 52, 200, ItemID.WILDERNESS_AGILITY_TICKET),
 	WILDERNESS_AGILITY_COURSE("Wilderness Agility Course", 52, 571.4f, ItemID.SKULL),
 	HALLOWED_SEPULCHRE_FLOOR_1("Hallowed Sepulchre Floor 1", 52, 575, ItemID.RING_OF_ENDURANCE),
 	SEERS_VILLAGE_ROOFTOP("Seers' Village Rooftop", 60, 570, ItemID.MARK_OF_GRACE),
 	WEREWOLF_AGILITY_COURSE("Werewolf Agility Course", 60, 730, ItemID.STICK),
-	COLOSSAL_WYRM_ADVANCED_COURSE("Colossal Wyrm Advanced", 62, 685, ItemID.BONE_SQUIRREL),
+	COLOSSAL_WYRM_ADVANCED_COURSE("Colossal Wyrm Advanced", 62, 749.6f, ItemID.TERMITES),
 	HALLOWED_SEPULCHRE_FLOOR_2("Hallowed Sepulchre Floor 2", 62, 925, ItemID.RING_OF_ENDURANCE),
 	POLLNIVNEACH_ROOFTOP("Pollnivneach Rooftop", 70, 890, ItemID.MARK_OF_GRACE),
 	DORGESH_KAAN_AGILITY_COURSE("Dorgesh-Kaan Agility Course", 70, 2750, ItemID.POWERBOX),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/HerbloreAction.java b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/HerbloreAction.java
index 985867b35..f56a58e47 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/HerbloreAction.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/HerbloreAction.java
@@ -71,47 +71,47 @@ public enum HerbloreAction implements ItemSkillAction
 	HUASCA(ItemID.HUASCA, 58, 11.8f),
 	PRAYER_REGENERATION_POTION(ItemID.PRAYER_REGENERATION_POTION3, 58, 132),
 	SNAPDRAGON(ItemID.SNAPDRAGON, 59, 11.8f),
-	MAMMOTHMIGHT_MIX(ItemID.MAMMOTHMIGHT_MIX, 60, 135),
 	WEAPON_POISON(ItemID.WEAPON_POISON, 60, 137.5f),
-	MYSTIC_MANA_AMALGAM(ItemID.MYSTIC_MANA_AMALGAM, 60, 175),
-	MARLEYS_MOONLIGHT(ItemID.MARLEYS_MOONLIGHT, 60, 215),
+	ALCOAUGMENTATOR(ItemID.ALCOAUGMENTATOR, 60, 190),
+	LIPLACK_LIQUOR(ItemID.LIPLACK_LIQUOR, 60, 190),
+	MAMMOTHMIGHT_MIX(ItemID.MAMMOTHMIGHT_MIX, 60, 190),
 	SUPER_RESTORE_3(ItemID.SUPER_RESTORE3, 63, 142.5f),
-	MIXALOT(ItemID.MIXALOT, 64, 255),
+	MYSTIC_MANA_AMALGAM(ItemID.MYSTIC_MANA_AMALGAM, 63, 215),
 	CADANTINE(ItemID.CADANTINE, 65, 12.5f),
 	SANFEW_SERUM_3(ItemID.SANFEW_SERUM3, 65, 160),
 	SUPER_DEFENCE_3(ItemID.SUPER_DEFENCE3, 66, 150),
+	MARLEYS_MOONLIGHT(ItemID.MARLEYS_MOONLIGHT, 66, 240),
 	LANTADYME(ItemID.LANTADYME, 67, 13.1f),
 	ANTIDOTE_PLUS_4(ItemID.ANTIDOTE4, 68, 155),
-	AZURE_AURA_MIX(ItemID.AZURE_AURA_MIX, 68, 215),
 	ANTIFIRE_POTION_3(ItemID.ANTIFIRE_POTION3, 69, 157.5f),
+	AZURE_AURA_MIX(ItemID.AZURE_AURA_MIX, 69, 265),
 	DIVINE_SUPER_ATTACK_POTION_4(ItemID.DIVINE_SUPER_ATTACK_POTION4, 70, 2),
 	DIVINE_SUPER_DEFENCE_POTION_4(ItemID.DIVINE_SUPER_DEFENCE_POTION4, 70, 2),
 	DIVINE_SUPER_STRENGTH_POTION_4(ItemID.DIVINE_SUPER_STRENGTH_POTION4, 70, 2),
 	DWARF_WEED(ItemID.DWARF_WEED, 70, 13.8f),
 	RANGING_POTION_3(ItemID.RANGING_POTION3, 72, 162.5f),
-	AQUALUX_AMALGAM(ItemID.AQUALUX_AMALGAM, 72, 295),
+	AQUALUX_AMALGAM(ItemID.AQUALUX_AMALGAM, 72, 290),
 	WEAPON_POISON_PLUS(ItemID.WEAPON_POISON_5937, 73, 165),
 	DIVINE_RANGING_POTION_4(ItemID.DIVINE_RANGING_POTION4, 74, 2),
 	TORSTOL(ItemID.TORSTOL, 75, 15),
+	MEGALITE_LIQUID(ItemID.MEGALITE_LIQUID, 75, 315),
 	MAGIC_POTION_3(ItemID.MAGIC_POTION3, 76, 172.5f),
-	ALCOAUGMENTATOR(ItemID.ALCOAUGMENTATOR, 76, 255),
 	STAMINA_POTION_3(ItemID.STAMINA_POTION3, 77, 76.5f),
 	STAMINA_POTION_4(ItemID.STAMINA_POTION4, 77, 102),
 	DIVINE_MAGIC_POTION_4(ItemID.DIVINE_MAGIC_POTION4, 78, 2),
 	ZAMORAK_BREW_3(ItemID.ZAMORAK_BREW3, 78, 175),
+	ANTILEECH_LOTION(ItemID.ANTILEECH_LOTION, 78, 340),
 	ANTIDOTE_PLUS_PLUS_4(ItemID.ANTIDOTE4_5952, 79, 177.5f),
 	BASTION_POTION_3(ItemID.BASTION_POTION3, 80, 155),
 	BATTLEMAGE_POTION_3(ItemID.BATTLEMAGE_POTION3, 80, 155),
-	MEGALITE_LIQUID(ItemID.MEGALITE_LIQUID, 80, 295),
 	SARADOMIN_BREW_3(ItemID.SARADOMIN_BREW3, 81, 180),
+	MIXALOT(ItemID.MIXALOT, 81, 365),
 	WEAPON_POISON_PLUS_PLUS(ItemID.WEAPON_POISON_5940, 82, 190),
 	EXTENDED_ANTIFIRE_3(ItemID.EXTENDED_ANTIFIRE3, 84, 82.5f),
 	EXTENDED_ANTIFIRE_4(ItemID.EXTENDED_ANTIFIRE4, 84, 110),
-	ANTILEECH_LOTION(ItemID.ANTILEECH_LOTION, 84, 335),
 	ANCIENT_BREW_4(ItemID.ANCIENT_BREW4, 85, 190),
 	DIVINE_BASTION_POTION_4(ItemID.DIVINE_BASTION_POTION4, 86, 2),
 	DIVINE_BATTLEMAGE_POTION_4(ItemID.DIVINE_BATTLEMAGE_POTION4, 86, 2),
-	LIPLACK_LIQUOR(ItemID.LIPLACK_LIQUOR, 86, 375),
 	ANTIVENOM_3(ItemID.ANTIVENOM3, 87, 90),
 	ANTIVENOM_4(ItemID.ANTIVENOM4, 87, 120),
 	MENAPHITE_REMEDY_3(ItemID.MENAPHITE_REMEDY3, 88, 200),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java
index e7c104c5b..f9c263050 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java
@@ -43,6 +43,7 @@ enum Task
 	ADAMANT_DRAGONS("Adamant dragons", ItemID.ADAMANT_DRAGON_MASK),
 	ALCHEMICAL_HYDRA("The Alchemical Hydra", ItemID.IKKLE_HYDRA),
 	ANKOU("Ankou", ItemID.ANKOU_MASK),
+	ARAXXOR("Araxxor", ItemID.NID),
 	ARAXYTES("Araxytes", ItemID.ARAXYTE_HEAD, "Araxxor"),
 	AVIANSIES("Aviansies", ItemID.ENSOULED_AVIANSIE_HEAD, "Kree'arra", "Flight Kilisa", "Flockleader Geerin", "Wingman Skree"),
 	BANDITS("Bandits", ItemID.BANDIT, "Bandit", "Black Heather", "Donny the Lad", "Speedy Keith"),
@@ -124,7 +125,7 @@ enum Task
 	KURASK("Kurask", ItemID.KURASK),
 	LAVA_DRAGONS("Lava Dragons", ItemID.LAVA_SCALE, "Lava dragon"),
 	LESSER_DEMONS("Lesser demons", ItemID.LESSER_DEMON_MASK, "Zakl'n Gritch"),
-	LESSER_NAGUA("Lesser Nagua", ItemID.LESSER_NAGUA, "Sulphur Nagua"),
+	LESSER_NAGUA("Lesser Nagua", ItemID.LESSER_NAGUA, "Sulphur Nagua", "Frost Nagua", "Amoxliatl"),
 	LIZARDMEN("Lizardmen", ItemID.LIZARDMAN_FANG, "Lizardman"),
 	LIZARDS("Lizards", ItemID.DESERT_LIZARD),
 	MAGIC_AXES("Magic axes", ItemID.IRON_BATTLEAXE),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/spellbook/SpellbookPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/spellbook/SpellbookPlugin.java
index 813643977..fb735f009 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/spellbook/SpellbookPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/spellbook/SpellbookPlugin.java
@@ -47,6 +47,8 @@ import net.runelite.api.widgets.JavaScriptCallback;
 import net.runelite.api.widgets.Widget;
 import static net.runelite.api.widgets.WidgetConfig.DRAG;
 import static net.runelite.api.widgets.WidgetConfig.DRAG_ON;
+import net.runelite.api.widgets.WidgetSizeMode;
+import net.runelite.api.widgets.WidgetType;
 import net.runelite.api.widgets.WidgetUtil;
 import net.runelite.client.callback.ClientThread;
 import net.runelite.client.chat.ChatMessageManager;
@@ -289,6 +291,8 @@ public class SpellbookPlugin extends Plugin
 			return;
 		}
 
+		createWarning(reordering);
+
 		// this is called after ~magic_spellbook_redraw has built and sorted the array of visible spells
 		// based on the vanilla filtering
 
@@ -361,6 +365,23 @@ public class SpellbookPlugin extends Plugin
 		stack[size - 1] = numSpells = numNewSpells;
 	}
 
+	private void createWarning(boolean unlocked)
+	{
+		Widget w = client.getWidget(ComponentID.SPELLBOOK_PARENT);
+		w.deleteAllChildren();
+
+		if (unlocked)
+		{
+			Widget c = w.createChild(WidgetType.RECTANGLE);
+			c.setHeightMode(WidgetSizeMode.MINUS);
+			c.setWidthMode(WidgetSizeMode.MINUS);
+			c.setTextColor(0xff0000);
+			c.setFilled(true);
+			c.setOpacity(220);
+			c.revalidate();
+		}
+	}
+
 	private void initializeSpells(int spellbookEnum)
 	{
 		EnumComposition spellbook = client.getEnum(spellbookEnum);
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiPlugin.java
index 45a63c149..502bf6676 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiPlugin.java
@@ -31,6 +31,7 @@ import javax.inject.Inject;
 import javax.inject.Provider;
 import lombok.extern.slf4j.Slf4j;
 import net.runelite.api.Client;
+import net.runelite.api.Menu;
 import net.runelite.api.MenuAction;
 import net.runelite.api.MenuEntry;
 import net.runelite.api.NPC;
@@ -386,18 +387,27 @@ public class WikiPlugin extends Plugin
 
 		if (wikiSelected && event.getType() == MenuAction.WIDGET_TARGET_ON_WIDGET.getId())
 		{
-			MenuEntry[] menuEntries = client.getMenuEntries();
+			Menu menu = client.getMenu();
+			MenuEntry[] menuEntries = menu.getMenuEntries();
 			Widget w = getWidget(widgetID, widgetIndex);
 			if (w.getType() == WidgetType.GRAPHIC && w.getItemId() != -1 && w.getItemId() != NullItemID.NULL_6512)
 			{
-				for (int ourEntry = menuEntries.length - 1;ourEntry >= 0; ourEntry--)
+				for (int ourEntry = menuEntries.length - 1; ourEntry >= 0; ourEntry--)
 				{
 					MenuEntry entry = menuEntries[ourEntry];
 					if (entry.getType() == MenuAction.WIDGET_TARGET_ON_WIDGET)
 					{
 						int id = itemManager.canonicalize(w.getItemId());
 						String name = itemManager.getItemComposition(id).getMembersName();
-						entry.setTarget(JagexColors.MENU_TARGET_TAG + name);
+						if ("null".equals(name))
+						{
+							// may be a dummy item for the UI without a name
+							menu.removeMenuEntry(entry);
+						}
+						else
+						{
+							entry.setTarget(JagexColors.MENU_TARGET_TAG + name);
+						}
 						break;
 					}
 				}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/TeleportLocationData.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/TeleportLocationData.java
index 9297b8f91..3d96efda7 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/TeleportLocationData.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/TeleportLocationData.java
@@ -200,6 +200,7 @@ enum TeleportLocationData
 	KEY_MASTER_SCROLL(TeleportType.SCROLL, "Key Master Teleport", new WorldPoint(2686, 9882, 0), "scroll_teleport_icon.png"),
 	REVENANT_CAVE_SCROLL(TeleportType.SCROLL, "Revenant Cave Teleport", new WorldPoint(3127, 3833, 0), "scroll_teleport_icon.png"),
 	WATSON_SCROLL(TeleportType.SCROLL, "Watson Teleport", new WorldPoint(1645, 3579, 0), "scroll_teleport_icon.png"),
+	SPIDER_CAVE_SCROLL(TeleportType.SCROLL, "Spider cave teleport", new WorldPoint(3658, 3403, 0), "scroll_teleport_icon.png"),
 
 	// Skillcapes
 	ACHIEVEMENT_CAPE_TWO_PINTS(TeleportType.OTHER, "Achievement Cape", "Two-pints", new WorldPoint(2574, 3324, 0), "achievement_cape_icon.png"),
diff --git a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java
index 2690b5550..7ee5c856c 100644
--- a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java
+++ b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java
@@ -925,6 +925,7 @@ public class ClientUI
 		{
 			case MacOS:
 				OSXUtil.requestForeground();
+				frame.setState(Frame.NORMAL);
 				break;
 			case Windows:
 				WinUtil.requestForeground(frame);
diff --git a/runelite-client/src/main/resources/item_variations.json b/runelite-client/src/main/resources/item_variations.json
index 41ad4188b..2a43036c3 100644
--- a/runelite-client/src/main/resources/item_variations.json
+++ b/runelite-client/src/main/resources/item_variations.json
@@ -12270,7 +12270,7 @@
     29996,
     29998
   ],
-  "prepot device": [
+  "chugging barrel": [
     30000,
     30002
   ],
diff --git a/runelite-client/src/main/scripts/BankMainBuild.hash b/runelite-client/src/main/scripts/BankMainBuild.hash
index 006d22d9c..4d1c9b45d 100644
--- a/runelite-client/src/main/scripts/BankMainBuild.hash
+++ b/runelite-client/src/main/scripts/BankMainBuild.hash
@@ -1,1 +1,1 @@
-C2F0851465815DB751D28B1D6459FA00DB85273452BA6DE14BB2B5A10FB423AA
\ No newline at end of file
+AA424C74E9017ACF4DAB947F6F25501D2C65E53974355CB7CE860AE203C9969F
\ No newline at end of file
diff --git a/runelite-client/src/main/scripts/BankMainBuild.rs2asm b/runelite-client/src/main/scripts/BankMainBuild.rs2asm
index f05efe39d..677acd83d 100644
--- a/runelite-client/src/main/scripts/BankMainBuild.rs2asm
+++ b/runelite-client/src/main/scripts/BankMainBuild.rs2asm
@@ -204,7 +204,7 @@ LABEL174:
    iload                  18
    iconst                 1
    if_icmpeq              LABEL180
-   jump                   LABEL204
+   jump                   LABEL210
 LABEL180:
    iconst                 1
    iload                  8
@@ -223,14 +223,20 @@ LABEL180:
    iconst                 1
    iload                  3
    if_setsize            
+   iconst                 53
+   iconst                 39
+   iconst                 1
+   iconst                 1
+   iconst                 786447
+   if_setsize            
    iconst                 28
    iconst                 42
    iconst                 2
    iconst                 0
    iload                  1
    if_setposition        
-   jump                   LABEL225
-LABEL204:
+   jump                   LABEL237
+LABEL210:
    iconst                 0
    iload                  8
    if_sethide            
@@ -246,28 +252,34 @@ LABEL204:
    iconst                 1
    iload                  3
    if_setsize            
+   iconst                 53
+   iconst                 81
+   iconst                 1
+   iconst                 1
+   iconst                 786447
+   if_setsize            
    iconst                 12
    iconst                 42
    iconst                 2
    iconst                 0
    iload                  1
    if_setposition        
-LABEL225:
+LABEL237:
    get_varbit             4150
    iconst                 15
-   if_icmpeq              LABEL229
-   jump                   LABEL234
-LABEL229:
+   if_icmpeq              LABEL241
+   jump                   LABEL246
+LABEL241:
    iconst                 0
    iconst                 786447
    if_sethide            
    invoke                 6080
-   jump                   LABEL237
-LABEL234:
+   jump                   LABEL249
+LABEL246:
    iconst                 1
    iconst                 786447
    if_sethide            
-LABEL237:
+LABEL249:
    iload                  3
    iload                  2
    invoke                 231
@@ -279,28 +291,28 @@ LABEL237:
    multiply              
    add                   
    istore                 21
-LABEL248:
+LABEL260:
    iload                  20
    iload                  21
-   if_icmple              LABEL252
-   jump                   LABEL265
-LABEL252:
+   if_icmple              LABEL264
+   jump                   LABEL277
+LABEL264:
    iload                  2
    iload                  20
    cc_find               
    iconst                 1
-   if_icmpeq              LABEL258
-   jump                   LABEL260
-LABEL258:
+   if_icmpeq              LABEL270
+   jump                   LABEL272
+LABEL270:
    iconst                 1
    cc_sethide            
-LABEL260:
+LABEL272:
    iload                  20
    iconst                 1
    add                   
    istore                 20
-   jump                   LABEL248
-LABEL265:
+   jump                   LABEL260
+LABEL277:
    iconst                 0
    istore                 20
    iconst                 8
@@ -347,52 +359,52 @@ LABEL265:
    if_icmpeq              singletabbuildmode ;
    get_varbit             4150
    iconst                 0
-   if_icmple              LABEL309
+   if_icmple              LABEL321
    get_varbit             4150
    iconst                 9
-   if_icmpgt              LABEL309
-   jump                   LABEL766
-LABEL309:
+   if_icmpgt              LABEL321
+   jump                   LABEL778
+LABEL321:
    get_varbit             4150
    iconst                 15
-   if_icmpne              LABEL313
-   jump                   LABEL766
-LABEL313:
+   if_icmpne              LABEL325
+   jump                   LABEL778
+LABEL325:
    iload                  20
    iconst                 1220
-   if_icmplt              LABEL317
-   jump                   LABEL342
-LABEL317:
+   if_icmplt              LABEL329
+   jump                   LABEL354
+LABEL329:
    iload                  2
    iload                  20
    cc_find               
    iconst                 1
-   if_icmpeq              LABEL323
-   jump                   LABEL325
-LABEL323:
+   if_icmpeq              LABEL335
+   jump                   LABEL337
+LABEL335:
    iconst                 1
    cc_sethide            
-LABEL325:
+LABEL337:
    iconst                 95
    iload                  20
    inv_getobj            
    iconst                 -1
-   if_icmpne              LABEL331
-   jump                   LABEL337
-LABEL331:
+   if_icmpne              LABEL343
+   jump                   LABEL349
+LABEL343:
    iload                  29
    iconst                 1
    add                   
    iload                  20
    istore                 30
    istore                 29
-LABEL337:
+LABEL349:
    iload                  20
    iconst                 1
    add                   
    istore                 20
-   jump                   LABEL313
-LABEL342:
+   jump                   LABEL325
+LABEL354:
    get_varbit             4171
    get_varbit             4172
    add                   
@@ -413,14 +425,14 @@ LABEL342:
    istore                 31
    iload                  31
    iconst                 0
-   if_icmple              LABEL364
-   jump                   LABEL368
-LABEL364:
+   if_icmple              LABEL376
+   jump                   LABEL380
+LABEL376:
    iconst                 1220
    iconst                 1
    sub                   
    istore                 30
-LABEL368:
+LABEL380:
    iload                  31
    iload                  30
    iconst                 1
@@ -446,9 +458,9 @@ LABEL368:
    istore                 20
    get_varbit             4171
    iconst                 0
-   if_icmpgt              LABEL395
-   jump                   LABEL425
-LABEL395:
+   if_icmpgt              LABEL407
+   jump                   LABEL437
+LABEL407:
    iconst                 1
    iload                  2
    iload                  28
@@ -479,12 +491,12 @@ LABEL395:
    get_varbit             4171
    add                   
    istore                 20
-LABEL425:
+LABEL437:
    get_varbit             4172
    iconst                 0
-   if_icmpgt              LABEL429
-   jump                   LABEL459
-LABEL429:
+   if_icmpgt              LABEL441
+   jump                   LABEL471
+LABEL441:
    iconst                 2
    iload                  2
    iload                  28
@@ -515,12 +527,12 @@ LABEL429:
    get_varbit             4172
    add                   
    istore                 20
-LABEL459:
+LABEL471:
    get_varbit             4173
    iconst                 0
-   if_icmpgt              LABEL463
-   jump                   LABEL493
-LABEL463:
+   if_icmpgt              LABEL475
+   jump                   LABEL505
+LABEL475:
    iconst                 3
    iload                  2
    iload                  28
@@ -551,12 +563,12 @@ LABEL463:
    get_varbit             4173
    add                   
    istore                 20
-LABEL493:
+LABEL505:
    get_varbit             4174
    iconst                 0
-   if_icmpgt              LABEL497
-   jump                   LABEL527
-LABEL497:
+   if_icmpgt              LABEL509
+   jump                   LABEL539
+LABEL509:
    iconst                 4
    iload                  2
    iload                  28
@@ -587,12 +599,12 @@ LABEL497:
    get_varbit             4174
    add                   
    istore                 20
-LABEL527:
+LABEL539:
    get_varbit             4175
    iconst                 0
-   if_icmpgt              LABEL531
-   jump                   LABEL561
-LABEL531:
+   if_icmpgt              LABEL543
+   jump                   LABEL573
+LABEL543:
    iconst                 5
    iload                  2
    iload                  28
@@ -623,12 +635,12 @@ LABEL531:
    get_varbit             4175
    add                   
    istore                 20
-LABEL561:
+LABEL573:
    get_varbit             4176
    iconst                 0
-   if_icmpgt              LABEL565
-   jump                   LABEL595
-LABEL565:
+   if_icmpgt              LABEL577
+   jump                   LABEL607
+LABEL577:
    iconst                 6
    iload                  2
    iload                  28
@@ -659,12 +671,12 @@ LABEL565:
    get_varbit             4176
    add                   
    istore                 20
-LABEL595:
+LABEL607:
    get_varbit             4177
    iconst                 0
-   if_icmpgt              LABEL599
-   jump                   LABEL629
-LABEL599:
+   if_icmpgt              LABEL611
+   jump                   LABEL641
+LABEL611:
    iconst                 7
    iload                  2
    iload                  28
@@ -695,12 +707,12 @@ LABEL599:
    get_varbit             4177
    add                   
    istore                 20
-LABEL629:
+LABEL641:
    get_varbit             4178
    iconst                 0
-   if_icmpgt              LABEL633
-   jump                   LABEL663
-LABEL633:
+   if_icmpgt              LABEL645
+   jump                   LABEL675
+LABEL645:
    iconst                 8
    iload                  2
    iload                  28
@@ -731,12 +743,12 @@ LABEL633:
    get_varbit             4178
    add                   
    istore                 20
-LABEL663:
+LABEL675:
    get_varbit             4179
    iconst                 0
-   if_icmpgt              LABEL667
-   jump                   LABEL697
-LABEL667:
+   if_icmpgt              LABEL679
+   jump                   LABEL709
+LABEL679:
    iconst                 9
    iload                  2
    iload                  28
@@ -767,12 +779,12 @@ LABEL667:
    get_varbit             4179
    add                   
    istore                 20
-LABEL697:
+LABEL709:
    invoke                 514
    iconst                 1
-   if_icmpeq              LABEL701
-   jump                   LABEL742
-LABEL701:
+   if_icmpeq              LABEL713
+   jump                   LABEL754
+LABEL713:
    jump                   GET_INPUT_TEXT ;  Skip truncating of varcstr 22 by not calling 280
    invoke                 280
 GET_INPUT_TEXT:                          ;
@@ -782,9 +794,9 @@ GET_INPUT_TEXT:                          ;
    sload                  0
    string_length         
    iconst                 0
-   if_icmpgt              LABEL708
-   jump                   LABEL727
-LABEL708:
+   if_icmpgt              LABEL720
+   jump                   LABEL739
+LABEL720:
    sconst                 "Showing items: "
    sconst                 "<col=ff0000>"
    sload                  0
@@ -794,9 +806,9 @@ LABEL708:
    if_settext            
    get_varc_int           5
    iconst                 11
-   if_icmpeq              LABEL719
-   jump                   LABEL726
-LABEL719:
+   if_icmpeq              LABEL731
+   jump                   LABEL738
+LABEL731:
    sconst                 "Show items whose names contain the following text: ("
    iload                  27
    tostring              
@@ -808,9 +820,9 @@ LABEL719:
    pop_int               ; pop number of matches
    iconst                 10616873
    if_settext            
-LABEL726:
-   jump                   LABEL741
-LABEL727:
+LABEL738:
+   jump                   LABEL753
+LABEL739:
    sconst                 "Showing items: "
    sconst                 "<col=ff0000>"
    sconst                 "*"
@@ -820,21 +832,21 @@ LABEL727:
    if_settext            
    get_varc_int           5
    iconst                 11
-   if_icmpeq              LABEL738
-   jump                   LABEL741
-LABEL738:
+   if_icmpeq              LABEL750
+   jump                   LABEL753
+LABEL750:
    sconst                 "Show items whose names contain the following text:"
    sconst                 "setSearchBankInputText"  ; load event name
    runelite_callback     ; invoke callback
    iconst                 10616873
    if_settext            
-LABEL741:
-   jump                   LABEL745
-LABEL742:
+LABEL753:
+   jump                   LABEL757
+LABEL754:
    sconst                 "The Bank of Gielinor"
    iload                  5
    if_settext            
-LABEL745:
+LABEL757:
    iload                  0
    iload                  1
    iload                  2
@@ -878,18 +890,18 @@ singletabbuildmode:
    get_varbit             4179  ;
    add                          ;
    istore                 31    ; store start to the first item in the "All items" tab
-LABEL766:
+LABEL778:
   ; if (~bankmain_searching = 1) {
    invoke                 514
    iconst                 1
-   if_icmpeq              LABEL770
-   jump                   LABEL774
-LABEL770:
+   if_icmpeq              LABEL782
+   jump                   LABEL786
+LABEL782:
    iconst                 1
    iconst                 1
    iconst                 1
    invoke                 299
-LABEL774:
+LABEL786:
   ; after ~meslayer close
    iconst                 -1
    istore                 32
@@ -903,12 +915,12 @@ LABEL774:
    istore                 34
    iconst                 0
    istore                 35
-LABEL786:
+LABEL798:
    iload                  20
    iconst                 1220
-   if_icmplt              LABEL790
-   jump                   LABEL870
-LABEL790:
+   if_icmplt              LABEL802
+   jump                   LABEL882
+LABEL802:
    ; item index = (loop index + offset) % bank size
    iload                  20   ; loop index
    iload                  31   ; offset
@@ -923,9 +935,9 @@ LOAD_ITEM_INDEX:
    iload                  30   ; use item index instead of loop index
    cc_find               
    iconst                 1
-   if_icmpeq              LABEL796
-   jump                   LABEL865
-LABEL796:
+   if_icmpeq              LABEL808
+   jump                   LABEL877
+LABEL808:
    iconst                 95
    jump                   LOAD_ITEM_INDEX2
    iload                  20
@@ -935,32 +947,32 @@ LOAD_ITEM_INDEX2:
    istore                 25
    iload                  25
    iconst                 -1
-   if_icmpne              LABEL804
-   jump                   LABEL808
-LABEL804:
+   if_icmpne              LABEL816
+   jump                   LABEL820
+LABEL816:
    iload                  29
    iconst                 1
    add                   
    istore                 29
-LABEL808:
+LABEL820:
    iload                  36         ; overriding single tab building mode?
    iconst                 1          ;
    if_icmpeq              filtertest ;
    iload                  20
    iload                  32
-   if_icmpge              LABEL812
-   jump                   LABEL863
-LABEL812:
+   if_icmpge              LABEL824
+   jump                   LABEL875
+LABEL824:
    iload                  20
    iload                  33
-   if_icmplt              LABEL816
-   jump                   LABEL863
+   if_icmplt              LABEL828
+   jump                   LABEL875
 filtertest:
    iload                  25        ; obj
    invoke                 279       ; ~bankmain_filtertest
    iconst                 1         ;
-   if_icmpne              LABEL863  ;
-LABEL816:
+   if_icmpne              LABEL875  ;
+LABEL828:
    iconst                 0
    cc_sethide            
    iload                  25
@@ -997,49 +1009,49 @@ LOAD_ITEM_INDEX3:
    istore                 28
    iload                  34
    iload                  22
-   if_icmplt              LABEL851
-   jump                   LABEL856
-LABEL851:
+   if_icmplt              LABEL863
+   jump                   LABEL868
+LABEL863:
    iload                  34
    iconst                 1
    add                   
    istore                 34
-   jump                   LABEL862
-LABEL856:
+   jump                   LABEL874
+LABEL868:
    iconst                 0
    iload                  35
    iconst                 1
    add                   
    istore                 35
    istore                 34
-LABEL862:
-   jump                   LABEL865
-LABEL863:
+LABEL874:
+   jump                   LABEL877
+LABEL875:
    iconst                 1
    cc_sethide            
-LABEL865:
+LABEL877:
    iload                  20
    iconst                 1
    add                   
    istore                 20
-   jump                   LABEL786
-LABEL870:
+   jump                   LABEL798
+LABEL882:
    iconst                 1
    iconst                 786447
    if_sethide            
    get_varbit             4150
    iconst                 15
-   if_icmpeq              LABEL877
-   jump                   LABEL884
-LABEL877:
+   if_icmpeq              LABEL889
+   jump                   LABEL896
+LABEL889:
    sconst                 "Potion store"
    iload                  5
    if_settext            
    iconst                 0
    iconst                 786447
    if_sethide            
-   jump                   LABEL904
-LABEL884:
+   jump                   LABEL916
+LABEL896:
    iload                  36                     ; overriding single tab building mode?
    iconst                 1                      ;
    if_icmpne              tabtitle               ; set normal "Tab" title
@@ -1050,9 +1062,9 @@ LABEL884:
 tabtitle:                                        ;
    get_varbit             4170
    iconst                 2
-   if_icmpeq              LABEL888
-   jump                   LABEL898
-LABEL888:
+   if_icmpeq              LABEL900
+   jump                   LABEL910
+LABEL900:
    sconst                 "Tab "
    iconst                 105
    iconst                 115
@@ -1062,8 +1074,8 @@ LABEL888:
    join_string            2
    iload                  5
    if_settext            
-   jump                   LABEL904
-LABEL898:
+   jump                   LABEL916
+LABEL910:
    sconst                 "Tab "
    get_varbit             4150
    tostring              
@@ -1071,7 +1083,7 @@ LABEL898:
    iload                  5
    if_settext            
 FinishBuilding:
-LABEL904:
+LABEL916:
    iload                  0
    iload                  1
    iload                  2
diff --git a/runelite-jshell/pom.xml b/runelite-jshell/pom.xml
index d921b66e5..61f866178 100644
--- a/runelite-jshell/pom.xml
+++ b/runelite-jshell/pom.xml
@@ -30,7 +30,7 @@
     <parent>
         <groupId>net.runelite</groupId>
         <artifactId>runelite-parent</artifactId>
-        <version>1.10.39-SNAPSHOT</version>
+        <version>1.10.41-SNAPSHOT</version>
     </parent>
 
     <artifactId>jshell</artifactId>
diff --git a/runelite-maven-plugin/pom.xml b/runelite-maven-plugin/pom.xml
index 76ff2979d..a3cb2f0d7 100644
--- a/runelite-maven-plugin/pom.xml
+++ b/runelite-maven-plugin/pom.xml
@@ -29,7 +29,7 @@
 	<parent>
 		<groupId>net.runelite</groupId>
 		<artifactId>runelite-parent</artifactId>
-		<version>1.10.39-SNAPSHOT</version>
+		<version>1.10.41-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>runelite-maven-plugin</artifactId>


Test patch:
diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/attackstyles/AttackStylesPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/attackstyles/AttackStylesPluginTest.java
index 26cc018af..f3d7a3ee2 100644
--- a/runelite-client/src/test/java/net/runelite/client/plugins/attackstyles/AttackStylesPluginTest.java
+++ b/runelite-client/src/test/java/net/runelite/client/plugins/attackstyles/AttackStylesPluginTest.java
@@ -37,11 +37,14 @@ import net.runelite.api.Skill;
 import net.runelite.api.StructComposition;
 import net.runelite.api.VarPlayer;
 import net.runelite.api.Varbits;
+import net.runelite.api.events.GameTick;
 import net.runelite.api.events.VarbitChanged;
 import net.runelite.api.widgets.ComponentID;
 import net.runelite.api.widgets.Widget;
+import net.runelite.client.Notifier;
 import net.runelite.client.callback.ClientThread;
 import net.runelite.client.chat.ChatMessageManager;
+import net.runelite.client.config.Notification;
 import net.runelite.client.events.ConfigChanged;
 import net.runelite.client.ui.overlay.OverlayManager;
 import static org.junit.Assert.assertFalse;
@@ -50,10 +53,12 @@ import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import org.mockito.Mock;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import org.mockito.junit.MockitoJUnitRunner;
 
@@ -80,6 +85,10 @@ public class AttackStylesPluginTest
 	@Bind
 	ChatMessageManager chatMessageManager;
 
+	@Mock
+	@Bind
+	Notifier notifier;
+
 	@Inject
 	AttackStylesPlugin attackPlugin;
 
@@ -119,6 +128,8 @@ public class AttackStylesPluginTest
 
 			when(client.getStructComposition(styleStructId)).thenReturn(s);
 		}
+
+		when(attackConfig.warningNotification()).thenReturn(Notification.OFF);
 	}
 
 	/*
@@ -149,6 +160,9 @@ public class AttackStylesPluginTest
 
 		assertTrue(attackPlugin.isWarnedSkillSelected());
 
+		attackPlugin.onGameTick(new GameTick());
+		verify(notifier).notify(any(Notification.class), eq("Attack style changed to Accurate!"));
+
 		// Switch to attack style that doesn't give attack xp
 		when(client.getVarpValue(VarPlayer.ATTACK_STYLE)).thenReturn(AttackStyle.AGGRESSIVE.ordinal());
 
diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClueTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClueTest.java
index d3854d843..91d0080cb 100644
--- a/runelite-client/src/test/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClueTest.java
+++ b/runelite-client/src/test/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClueTest.java
@@ -24,8 +24,10 @@
  */
 package net.runelite.client.plugins.cluescrolls.clues;
 
+import java.util.Map;
 import net.runelite.api.coords.WorldPoint;
 import org.junit.Test;
+import static org.junit.Assert.assertTrue;
 
 public class CoordinateClueTest
 {
@@ -35,4 +37,20 @@ public class CoordinateClueTest
 		// If this doesn't throw then the clues map doesn't have duplicate keys
 		new CoordinateClue("test", new WorldPoint(0, 0, 0), null);
 	}
-}
\ No newline at end of file
+
+	@Test
+	public void testIsleOfSoulsNpcs()
+	{
+		for (Map.Entry<WorldPoint, CoordinateClue.CoordinateClueInfo> clueEntry : CoordinateClue.CLUES.entrySet())
+		{
+			final CoordinateClue.CoordinateClueInfo clueInfo = clueEntry.getValue();
+			final Enemy enemy = clueInfo.getEnemy();
+			if (!(enemy == Enemy.ARMADYLEAN_GUARD || enemy == Enemy.BANDOSIAN_GUARD))
+			{
+				continue;
+			}
+
+			assertTrue("Armadylean guard-only and Bandosian guard-only clues only occur on the Isle of Souls; the following entry must be corrected:\n" + clueEntry, clueInfo.getDirections().contains("Isle of Souls"));
+		}
+	}
+}
diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/GroundItemsPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/GroundItemsPluginTest.java
index 1c3d2e238..7741203fc 100644
--- a/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/GroundItemsPluginTest.java
+++ b/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/GroundItemsPluginTest.java
@@ -44,6 +44,7 @@ import net.runelite.client.game.ItemManager;
 import net.runelite.client.input.KeyManager;
 import net.runelite.client.input.MouseManager;
 import net.runelite.client.plugins.grounditems.config.HighlightTier;
+import net.runelite.client.plugins.grounditems.config.OwnershipFilterMode;
 import net.runelite.client.ui.overlay.OverlayManager;
 import org.junit.Before;
 import org.junit.Test;
@@ -124,6 +125,7 @@ public class GroundItemsPluginTest
 		when(config.getHighlightItems()).thenReturn("abyssal whip");
 		when(config.notifyTier()).thenReturn(HighlightTier.OFF);
 		when(config.notifyHighlightedDrops()).thenReturn(true);
+		when(config.ownershipFilterMode()).thenReturn(OwnershipFilterMode.ALL);
 
 		when(itemManager.getItemComposition(ItemID.ABYSSAL_WHIP)).thenAnswer(a ->
 		{
diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/itemstats/ItemStatEffectTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/itemstats/ItemStatEffectTest.java
index b9ed3a5c2..075dfc098 100644
--- a/runelite-client/src/test/java/net/runelite/client/plugins/itemstats/ItemStatEffectTest.java
+++ b/runelite-client/src/test/java/net/runelite/client/plugins/itemstats/ItemStatEffectTest.java
@@ -247,6 +247,176 @@ public class ItemStatEffectTest
 		assertEquals(0, skillChange(Skill.PRAYER, 99, 123, ambrosia));
 	}
 
+	@Test
+	public void testMoonlightPotion()
+	{
+		final Effect moonlightPotion = new ItemStatChanges().get(ItemID.MOONLIGHT_POTION1);
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(11);
+		assertEquals(12, skillChange(Skill.ATTACK, 99, 1, moonlightPotion));
+		assertEquals(0, skillChange(Skill.STRENGTH, 99, 99, moonlightPotion));
+		assertEquals(0, skillChange(Skill.DEFENCE, 40, 33, moonlightPotion));
+		assertEquals(0, skillChange(Skill.PRAYER, 99, 54, moonlightPotion));
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(38);
+		assertEquals(12, skillChange(Skill.ATTACK, 99, 99, moonlightPotion));
+		assertEquals(12, skillChange(Skill.STRENGTH, 99, 99, moonlightPotion));
+		assertEquals(4, skillChange(Skill.DEFENCE, 40, 43, moonlightPotion));
+		assertEquals(31, skillChange(Skill.PRAYER, 99, 0, moonlightPotion));
+		assertEquals(9, skillChange(Skill.PRAYER, 99, 90, moonlightPotion));
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(54);
+		assertEquals(3, skillChange(Skill.ATTACK, 60, 71, moonlightPotion));
+		assertEquals(2, skillChange(Skill.STRENGTH, 60, 67, moonlightPotion));
+		assertEquals(2, skillChange(Skill.DEFENCE, 60, 67, moonlightPotion));
+		assertEquals(31, skillChange(Skill.PRAYER, 99, 10, moonlightPotion));
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(65);
+		assertEquals(12, skillChange(Skill.ATTACK, 50, 19, moonlightPotion));
+		assertEquals(13, skillChange(Skill.STRENGTH, 58, 22, moonlightPotion));
+		assertEquals(0, skillChange(Skill.DEFENCE, 34, 40, moonlightPotion));
+		assertEquals(26, skillChange(Skill.PRAYER, 42, 0, moonlightPotion));
+		assertEquals(4, skillChange(Skill.PRAYER, 42, 38, moonlightPotion));
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(77);
+		assertEquals(30, skillChange(Skill.PRAYER, 77, 18, moonlightPotion));
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(80);
+		assertEquals(6, skillChange(Skill.ATTACK, 80, 91, moonlightPotion));
+		assertEquals(7, skillChange(Skill.STRENGTH, 92, 103, moonlightPotion));
+		assertEquals(8, skillChange(Skill.DEFENCE, 75, 89, moonlightPotion));
+		assertEquals(31, skillChange(Skill.PRAYER, 77, 18, moonlightPotion));
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(99);
+		assertEquals(19, skillChange(Skill.ATTACK, 99, 99, moonlightPotion));
+		assertEquals(19, skillChange(Skill.STRENGTH, 99, 99, moonlightPotion));
+		assertEquals(26, skillChange(Skill.DEFENCE, 99, 99, moonlightPotion));
+		assertEquals(36, skillChange(Skill.PRAYER, 99, 0, moonlightPotion));
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(102);
+		assertEquals(37, skillChange(Skill.PRAYER, 99, 0, moonlightPotion));
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(104);
+		assertEquals(33, skillChange(Skill.PRAYER, 34, 1, moonlightPotion));
+		assertEquals(38, skillChange(Skill.PRAYER, 54, 0, moonlightPotion));
+	}
+
+	@Test
+	public void testMoonlightMoth()
+	{
+		final Effect moonlightMoth = new ItemStatChanges().get(ItemID.MOONLIGHT_MOTH_28893);
+
+		assertEquals(0, skillChange(Skill.PRAYER, 1, 1, moonlightMoth));
+		assertEquals(5, skillChange(Skill.PRAYER, 7, 2, moonlightMoth));
+		assertEquals(22, skillChange(Skill.PRAYER, 30, 0, moonlightMoth));
+		assertEquals(19, skillChange(Skill.PRAYER, 68, 49, moonlightMoth));
+		assertEquals(22, skillChange(Skill.PRAYER, 99, 70, moonlightMoth));
+
+		final Effect moonlightMothMix = new ItemStatChanges().get(ItemID.MOONLIGHT_MOTH_MIX_1);
+
+		assertEquals(0, skillChange(Skill.PRAYER, 1, 1, moonlightMothMix));
+		assertEquals(5, skillChange(Skill.PRAYER, 7, 2, moonlightMothMix));
+		assertEquals(22, skillChange(Skill.PRAYER, 30, 0, moonlightMothMix));
+		assertEquals(19, skillChange(Skill.PRAYER, 68, 49, moonlightMothMix));
+		assertEquals(22, skillChange(Skill.PRAYER, 99, 70, moonlightMothMix));
+	}
+
+	@Test
+	public void testSunlightMoth()
+	{
+		final Effect sunlightMoth = new ItemStatChanges().get(ItemID.SUNLIGHT_MOTH_28890);
+
+		assertEquals(16, skillChange(Skill.AGILITY, 50, 1, sunlightMoth));
+		assertEquals(0, skillChange(Skill.ATTACK, 70, 70, sunlightMoth));
+		assertEquals(17, skillChange(Skill.CONSTRUCTION, 66, 49, sunlightMoth));
+		assertEquals(0, skillChange(Skill.COOKING, 70, 70, sunlightMoth));
+		assertEquals(12, skillChange(Skill.CRAFTING, 70, 58, sunlightMoth));
+		assertEquals(21, skillChange(Skill.DEFENCE, 75, 2, sunlightMoth));
+		assertEquals(13, skillChange(Skill.FARMING, 83, 70, sunlightMoth));
+		assertEquals(1, skillChange(Skill.FIREMAKING, 71, 70, sunlightMoth));
+		assertEquals(19, skillChange(Skill.FISHING, 89, 70, sunlightMoth));
+		assertEquals(7, skillChange(Skill.FLETCHING, 77, 70, sunlightMoth));
+		assertEquals(0, skillChange(Skill.HERBLORE, 1, 1, sunlightMoth));
+		assertEquals(1, skillChange(Skill.HITPOINTS, 84, 83, sunlightMoth));
+		assertEquals(0, skillChange(Skill.HUNTER, 75, 77, sunlightMoth));
+		assertEquals(12, skillChange(Skill.MAGIC, 30, 0, sunlightMoth));
+		assertEquals(19, skillChange(Skill.MINING, 68, 49, sunlightMoth));
+		assertEquals(0, skillChange(Skill.PRAYER, 99, 70, sunlightMoth));
+		assertEquals(25, skillChange(Skill.RANGED, 99, 70, sunlightMoth));
+		assertEquals(25, skillChange(Skill.RUNECRAFT, 99, 70, sunlightMoth));
+		assertEquals(12, skillChange(Skill.SLAYER, 99, 87, sunlightMoth));
+		assertEquals(0, skillChange(Skill.SMITHING, 31, 31, sunlightMoth));
+		assertEquals(0, skillChange(Skill.STRENGTH, 68, 81, sunlightMoth));
+		assertEquals(11, skillChange(Skill.THIEVING, 28, 10, sunlightMoth));
+		assertEquals(3, skillChange(Skill.WOODCUTTING, 54, 51, sunlightMoth));
+
+		final Effect sunlightMothMix = new ItemStatChanges().get(ItemID.SUNLIGHT_MOTH_MIX_1);
+
+		assertEquals(16, skillChange(Skill.AGILITY, 50, 1, sunlightMothMix));
+		assertEquals(0, skillChange(Skill.ATTACK, 70, 70, sunlightMothMix));
+		assertEquals(17, skillChange(Skill.CONSTRUCTION, 66, 49, sunlightMothMix));
+		assertEquals(0, skillChange(Skill.COOKING, 70, 70, sunlightMothMix));
+		assertEquals(12, skillChange(Skill.CRAFTING, 70, 58, sunlightMothMix));
+		assertEquals(21, skillChange(Skill.DEFENCE, 75, 2, sunlightMothMix));
+		assertEquals(13, skillChange(Skill.FARMING, 83, 70, sunlightMothMix));
+		assertEquals(1, skillChange(Skill.FIREMAKING, 71, 70, sunlightMothMix));
+		assertEquals(19, skillChange(Skill.FISHING, 89, 70, sunlightMothMix));
+		assertEquals(7, skillChange(Skill.FLETCHING, 77, 70, sunlightMothMix));
+		assertEquals(0, skillChange(Skill.HERBLORE, 1, 1, sunlightMothMix));
+		assertEquals(1, skillChange(Skill.HITPOINTS, 84, 83, sunlightMothMix));
+		assertEquals(0, skillChange(Skill.HUNTER, 75, 77, sunlightMothMix));
+		assertEquals(12, skillChange(Skill.MAGIC, 30, 0, sunlightMothMix));
+		assertEquals(19, skillChange(Skill.MINING, 68, 49, sunlightMothMix));
+		assertEquals(0, skillChange(Skill.PRAYER, 99, 70, sunlightMothMix));
+		assertEquals(25, skillChange(Skill.RANGED, 99, 70, sunlightMothMix));
+		assertEquals(25, skillChange(Skill.RUNECRAFT, 99, 70, sunlightMothMix));
+		assertEquals(12, skillChange(Skill.SLAYER, 99, 87, sunlightMothMix));
+		assertEquals(0, skillChange(Skill.SMITHING, 31, 31, sunlightMothMix));
+		assertEquals(0, skillChange(Skill.STRENGTH, 68, 81, sunlightMothMix));
+		assertEquals(11, skillChange(Skill.THIEVING, 28, 10, sunlightMothMix));
+		assertEquals(3, skillChange(Skill.WOODCUTTING, 54, 51, sunlightMothMix));
+	}
+
+	@Test
+	public void testRubyHarvest()
+	{
+		final Effect rubyHarvest = new ItemStatChanges().get(ItemID.RUBY_HARVEST);
+
+		assertEquals(16, skillChange(Skill.ATTACK, 82, 82, rubyHarvest));
+	}
+
+	@Test
+	public void testSapphireGlacial()
+	{
+		final Effect sapphireGlacial = new ItemStatChanges().get(ItemID.SAPPHIRE_GLACIALIS);
+
+		assertEquals(13, skillChange(Skill.DEFENCE, 64, 64, sapphireGlacial));
+	}
+
+	@Test
+	public void testBlackWarlock()
+	{
+		final Effect blackWarlock = new ItemStatChanges().get(ItemID.BLACK_WARLOCK);
+
+		assertEquals(13, skillChange(Skill.STRENGTH, 64, 64, blackWarlock));
+	}
+
+	@Test
+	public void testSnowyKnight()
+	{
+		final Effect snowyKnight = new ItemStatChanges().get(ItemID.SNOWY_KNIGHT);
+
+		assertEquals(5, skillChange(Skill.HITPOINTS, 49, 44, snowyKnight));
+		assertEquals(0, skillChange(Skill.HITPOINTS, 64, 64, snowyKnight));
+		assertEquals(15, skillChange(Skill.HITPOINTS, 99, 77, snowyKnight));
+
+		final Effect snowyKnightMix = new ItemStatChanges().get(ItemID.SNOWY_KNIGHT_MIX_1);
+
+		assertEquals(5, skillChange(Skill.HITPOINTS, 50, 45, snowyKnightMix));
+		assertEquals(0, skillChange(Skill.HITPOINTS, 64, 64, snowyKnightMix));
+		assertEquals(8, skillChange(Skill.HITPOINTS, 99, 10, snowyKnightMix));
+	}
+
 	@Test
 	public void prayerRestoreVariants()
 	{