Repository: runelite/runelite
Pull request #18434 (2024-10-14 15:03:58)

skillcalculator: fix weapon poison (+) xp
Updates experience from making weapon poison (+) from 165 to 190.

This appears to have been incorrect since at least February (see [here](https://oldschool.runescape.wiki/w/Weapon_poison(%2B)?direction=next&oldid=14519811)). 
This value, as well as weapon poison (++) also yielding 190 XP, was just confirmed in the wiki's Discord server.




Patch:
diff --git a/cache/pom.xml b/cache/pom.xml
index 34f2e8dc4..70c61be2a 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.41-SNAPSHOT</version>
+		<version>1.10.43-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>cache</artifactId>
diff --git a/cache/src/main/java/net/runelite/cache/definitions/SequenceDefinition.java b/cache/src/main/java/net/runelite/cache/definitions/SequenceDefinition.java
index 726b71c68..7be03f8d2 100644
--- a/cache/src/main/java/net/runelite/cache/definitions/SequenceDefinition.java
+++ b/cache/src/main/java/net/runelite/cache/definitions/SequenceDefinition.java
@@ -24,7 +24,8 @@
  */
 package net.runelite.cache.definitions;
 
-import java.util.Map;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
 import lombok.Data;
 import lombok.Value;
 
@@ -34,8 +35,7 @@ public class SequenceDefinition
 	private final int id;
 	public int[] frameIDs; // top 16 bits are FrameDefinition ids
 	public int[] chatFrameIds;
-	public int[] frameLenghts;
-	public Sound[] frameSounds;
+	public int[] frameLengths;
 	public int frameStep = -1;
 	public int[] interleaveLeave;
 	public boolean stretches = false;
@@ -47,7 +47,7 @@ public class SequenceDefinition
 	public int priority = -1;
 	public int replyMode = 2;
 	public int animMayaID = -1;
-	public Map<Integer, Sound> animMayaFrameSounds;
+	public Multimap<Integer, Sound> frameSounds = ArrayListMultimap.create();
 	public int animMayaStart;
 	public int animMayaEnd;
 	public boolean[] animMayaMasks;
@@ -59,5 +59,6 @@ public class SequenceDefinition
 		public int loops;
 		public int location;
 		public int retain;
+		public int weight;
 	}
 }
diff --git a/cache/src/main/java/net/runelite/cache/definitions/loaders/SequenceLoader.java b/cache/src/main/java/net/runelite/cache/definitions/loaders/SequenceLoader.java
index 201a7f670..574928ed8 100644
--- a/cache/src/main/java/net/runelite/cache/definitions/loaders/SequenceLoader.java
+++ b/cache/src/main/java/net/runelite/cache/definitions/loaders/SequenceLoader.java
@@ -24,7 +24,6 @@
  */
 package net.runelite.cache.definitions.loaders;
 
-import java.util.HashMap;
 import lombok.Data;
 import lombok.experimental.Accessors;
 import lombok.extern.slf4j.Slf4j;
@@ -37,12 +36,15 @@ import net.runelite.cache.io.InputStream;
 public class SequenceLoader
 {
 	public static final int REV_220_SEQ_ARCHIVE_REV = 1141;
+	public static final int REV_226_SEQ_ARCHIVE_REV = 1268;
 
 	private boolean rev220FrameSounds = true;
+	private boolean rev226 = true;
 
 	public SequenceLoader configureForRevision(int rev)
 	{
 		this.rev220FrameSounds = rev > REV_220_SEQ_ARCHIVE_REV;
+		this.rev226 = rev > REV_226_SEQ_ARCHIVE_REV;
 		return this;
 	}
 
@@ -72,11 +74,11 @@ public class SequenceLoader
 		if (opcode == 1)
 		{
 			var3 = stream.readUnsignedShort();
-			def.frameLenghts = new int[var3];
+			def.frameLengths = new int[var3];
 
 			for (var4 = 0; var4 < var3; ++var4)
 			{
-				def.frameLenghts[var4] = stream.readUnsignedShort();
+				def.frameLengths[var4] = stream.readUnsignedShort();
 			}
 
 			def.frameIDs = new int[var3];
@@ -154,32 +156,30 @@ public class SequenceLoader
 				def.chatFrameIds[var4] += stream.readUnsignedShort() << 16;
 			}
 		}
-		else if (opcode == 13)
+		else if (opcode == 13 && !rev226)
 		{
 			var3 = stream.readUnsignedByte();
-			def.frameSounds = new SequenceDefinition.Sound[var3];
 
 			for (var4 = 0; var4 < var3; ++var4)
 			{
-				def.frameSounds[var4] = this.readFrameSound(stream);
+				def.frameSounds.put(var4, this.readFrameSound(stream));
 			}
 		}
-		else if (opcode == 14)
+		else if (opcode == (rev226 ? 13 : 14))
 		{
 			def.animMayaID = stream.readInt();
 		}
-		else if (opcode == 15)
+		else if (opcode == (rev226 ? 14 : 15))
 		{
 			var3 = stream.readUnsignedShort();
-			def.animMayaFrameSounds = new HashMap<>();
 
 			for (var4 = 0; var4 < var3; ++var4)
 			{
 				int frame = stream.readUnsignedShort();
-				def.animMayaFrameSounds.put(frame, this.readFrameSound(stream));
+				def.frameSounds.put(frame, this.readFrameSound(stream));
 			}
 		}
-		else if (opcode == 16)
+		else if (opcode == (rev226 ? 15 : 16))
 		{
 			def.animMayaStart = stream.readUnsignedShort();
 			def.animMayaEnd = stream.readUnsignedShort();
@@ -207,6 +207,7 @@ public class SequenceLoader
 		int loops;
 		int location;
 		int retain;
+		int weight = -1;
 		if (!rev220FrameSounds)
 		{
 			int bits = stream.read24BitInt();
@@ -218,6 +219,10 @@ public class SequenceLoader
 		else
 		{
 			id = stream.readUnsignedShort();
+			if (rev226)
+			{
+				weight = stream.readUnsignedByte();
+			}
 			loops = stream.readUnsignedByte();
 			location = stream.readUnsignedByte();
 			retain = stream.readUnsignedByte();
@@ -225,7 +230,7 @@ public class SequenceLoader
 
 		if (id >= 1 && loops >= 1 && location >= 0 && retain >= 0)
 		{
-			return new SequenceDefinition.Sound(id, loops, location, retain);
+			return new SequenceDefinition.Sound(id, loops, location, retain, weight);
 		}
 		else
 		{
diff --git a/pom.xml b/pom.xml
index 362a39015..4663571c0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
 
 	<groupId>net.runelite</groupId>
 	<artifactId>runelite-parent</artifactId>
-	<version>1.10.41-SNAPSHOT</version>
+	<version>1.10.43-SNAPSHOT</version>
 	<packaging>pom</packaging>
 
 	<name>RuneLite</name>
@@ -47,7 +47,7 @@
 		<checkstyle.skip>true</checkstyle.skip>
 		<glslang.path></glslang.path>
 
-		<rs.version>225</rs.version>
+		<rs.version>226</rs.version>
 	</properties>
 
 	<licenses>
diff --git a/runelite-api/pom.xml b/runelite-api/pom.xml
index ef55a7ec1..efbc160ee 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.41-SNAPSHOT</version>
+		<version>1.10.43-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>runelite-api</artifactId>
diff --git a/runelite-api/src/main/java/net/runelite/api/Client.java b/runelite-api/src/main/java/net/runelite/api/Client.java
index 8c70ec7ab..6f036f1a1 100644
--- a/runelite-api/src/main/java/net/runelite/api/Client.java
+++ b/runelite-api/src/main/java/net/runelite/api/Client.java
@@ -2051,6 +2051,38 @@ public interface Client extends OAuthApi, GameEngine
 	 */
 	void setCameraShakeDisabled(boolean disabled);
 
+	/**
+	 * Draw all 2D extras. This is the default.
+	 */
+	int DRAW_2D_ALL = ~0;
+	/**
+	 * Hide all 2D extras.
+	 */
+	int DRAW_2D_NONE = 0;
+	/**
+	 * Render overhead text.
+	 */
+	int DRAW_2D_OVERHEAD_TEXT = 1;
+	/**
+	 * Render elements not otherwise specified in this bitflag.
+	 */
+	int DRAW_2D_OTHERS = 1 << 30;
+
+	/**
+	 * Gets the current draw2D mask. 
+	 * @return the current mask
+	 * @see Client#setDraw2DMask(int)
+	 */
+	@MagicConstant(intValues = {DRAW_2D_NONE, DRAW_2D_ALL, DRAW_2D_OVERHEAD_TEXT, DRAW_2D_OTHERS})
+	int getDraw2DMask();
+
+	/**
+	 * Sets the current draw2D mask.
+	 * Use bit operations on the value returned by {@link Client#getDraw2DMask()} to modify specific features.
+	 * @param mask The new mask.
+	 */
+	void setDraw2DMask(@MagicConstant(intValues = {DRAW_2D_NONE, DRAW_2D_ALL, DRAW_2D_OVERHEAD_TEXT, DRAW_2D_OTHERS}) int mask);
+
 	/**
 	 * Contains a 3D array of template chunks for instanced areas.
 	 * <p>
diff --git a/runelite-api/src/main/java/net/runelite/api/EquipmentInventorySlot.java b/runelite-api/src/main/java/net/runelite/api/EquipmentInventorySlot.java
index 6c4064549..c2c46f3e8 100644
--- a/runelite-api/src/main/java/net/runelite/api/EquipmentInventorySlot.java
+++ b/runelite-api/src/main/java/net/runelite/api/EquipmentInventorySlot.java
@@ -42,9 +42,12 @@ public enum EquipmentInventorySlot
 	WEAPON(3),
 	BODY(4),
 	SHIELD(5),
+	ARMS(6),
 	LEGS(7),
+	HAIR(8),
 	GLOVES(9),
 	BOOTS(10),
+	JAW(11),
 	RING(12),
 	AMMO(13);
 
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 3f261f613..2e44b36ce 100644
--- a/runelite-api/src/main/java/net/runelite/api/ItemID.java
+++ b/runelite-api/src/main/java/net/runelite/api/ItemID.java
@@ -14379,5 +14379,147 @@ public final class ItemID
 	public static final int MOXI = 30154;
 	public static final int BUTLERS_TRAY_30156 = 30156;
 	public static final int BUTLERS_TRAY_30157 = 30157;
+	public static final int PICTURE_MAP = 30160;
+	public static final int SEED_PACKETS = 30161;
+	public static final int BEIGE_PUMPKIN = 30162;
+	public static final int WHITE_PUMPKIN = 30163;
+	public static final int YELLOW_PUMPKIN = 30164;
+	public static final int ORANGE_PUMPKIN = 30165;
+	public static final int RED_PUMPKIN = 30166;
+	public static final int DARK_GREEN_PUMPKIN = 30167;
+	public static final int POWDER_GREY_PUMPKIN = 30168;
+	public static final int BEIGE_PUMPKIN_DISGUSTED = 30169;
+	public static final int BEIGE_PUMPKIN_SILLY = 30170;
+	public static final int BEIGE_PUMPKIN_EVIL = 30171;
+	public static final int BEIGE_PUMPKIN_ANGRY = 30172;
+	public static final int BEIGE_PUMPKIN_DEPRESSED = 30173;
+	public static final int BEIGE_PUMPKIN_SHOCKED = 30174;
+	public static final int BEIGE_PUMPKIN_SAD = 30175;
+	public static final int BEIGE_PUMPKIN_HAPPY = 30176;
+	public static final int BEIGE_PUMPKIN_LAUGHING = 30177;
+	public static final int WHITE_PUMPKIN_DISGUSTED = 30178;
+	public static final int WHITE_PUMPKIN_SILLY = 30179;
+	public static final int WHITE_PUMPKIN_EVIL = 30180;
+	public static final int WHITE_PUMPKIN_ANGRY = 30181;
+	public static final int WHITE_PUMPKIN_DEPRESSED = 30182;
+	public static final int WHITE_PUMPKIN_SHOCKED = 30183;
+	public static final int WHITE_PUMPKIN_SAD = 30184;
+	public static final int WHITE_PUMPKIN_HAPPY = 30185;
+	public static final int WHITE_PUMPKIN_LAUGHING = 30186;
+	public static final int YELLOW_PUMPKIN_DISGUSTED = 30187;
+	public static final int YELLOW_PUMPKIN_SILLY = 30188;
+	public static final int YELLOW_PUMPKIN_EVIL = 30189;
+	public static final int YELLOW_PUMPKIN_ANGRY = 30190;
+	public static final int YELLOW_PUMPKIN_DEPRESSED = 30191;
+	public static final int YELLOW_PUMPKIN_SHOCKED = 30192;
+	public static final int YELLOW_PUMPKIN_SAD = 30193;
+	public static final int YELLOW_PUMPKIN_HAPPY = 30194;
+	public static final int YELLOW_PUMPKIN_LAUGHING = 30195;
+	public static final int ORANGE_PUMPKIN_DISGUSTED = 30196;
+	public static final int ORANGE_PUMPKIN_SILLY = 30197;
+	public static final int ORANGE_PUMPKIN_EVIL = 30198;
+	public static final int ORANGE_PUMPKIN_ANGRY = 30199;
+	public static final int ORANGE_PUMPKIN_DEPRESSED = 30200;
+	public static final int ORANGE_PUMPKIN_SHOCKED = 30201;
+	public static final int ORANGE_PUMPKIN_SAD = 30202;
+	public static final int ORANGE_PUMPKIN_HAPPY = 30203;
+	public static final int ORANGE_PUMPKIN_LAUGHING = 30204;
+	public static final int RED_PUMPKIN_DISGUSTED = 30205;
+	public static final int RED_PUMPKIN_SILLY = 30206;
+	public static final int RED_PUMPKIN_EVIL = 30207;
+	public static final int RED_PUMPKIN_ANGRY = 30208;
+	public static final int RED_PUMPKIN_DEPRESSED = 30209;
+	public static final int RED_PUMPKIN_SHOCKED = 30210;
+	public static final int RED_PUMPKIN_SAD = 30211;
+	public static final int RED_PUMPKIN_HAPPY = 30212;
+	public static final int RED_PUMPKIN_LAUGHING = 30213;
+	public static final int DARK_GREEN_PUMPKIN_DISGUSTED = 30214;
+	public static final int DARK_GREEN_PUMPKIN_SILLY = 30215;
+	public static final int DARK_GREEN_PUMPKIN_EVIL = 30216;
+	public static final int DARK_GREEN_PUMPKIN_ANGRY = 30217;
+	public static final int DARK_GREEN_PUMPKIN_DEPRESSED = 30218;
+	public static final int DARK_GREEN_PUMPKIN_SHOCKED = 30219;
+	public static final int DARK_GREEN_PUMPKIN_SAD = 30220;
+	public static final int DARK_GREEN_PUMPKIN_HAPPY = 30221;
+	public static final int DARK_GREEN_PUMPKIN_LAUGHING = 30222;
+	public static final int POWDER_GREY_PUMPKIN_DISGUSTED = 30223;
+	public static final int POWDER_GREY_PUMPKIN_SILLY = 30224;
+	public static final int POWDER_GREY_PUMPKIN_EVIL = 30225;
+	public static final int POWDER_GREY_PUMPKIN_ANGRY = 30226;
+	public static final int POWDER_GREY_PUMPKIN_DEPRESSED = 30227;
+	public static final int POWDER_GREY_PUMPKIN_SHOCKED = 30228;
+	public static final int POWDER_GREY_PUMPKIN_SAD = 30229;
+	public static final int POWDER_GREY_PUMPKIN_HAPPY = 30230;
+	public static final int POWDER_GREY_PUMPKIN_LAUGHING = 30231;
+	public static final int SCARECROW_SHIRT = 30232;
+	public static final int HALLOWEEN_SCARECROW = 30234;
+	public static final int SCARECROW_30236 = 30236;
+	public static final int BEIGE_PUMPKIN_DISGUSTED_30237 = 30237;
+	public static final int BEIGE_PUMPKIN_SILLY_30239 = 30239;
+	public static final int BEIGE_PUMPKIN_EVIL_30241 = 30241;
+	public static final int BEIGE_PUMPKIN_ANGRY_30242 = 30242;
+	public static final int BEIGE_PUMPKIN_DEPRESSED_30243 = 30243;
+	public static final int BEIGE_PUMPKIN_SHOCKED_30244 = 30244;
+	public static final int BEIGE_PUMPKIN_SAD_30245 = 30245;
+	public static final int BEIGE_PUMPKIN_HAPPY_30246 = 30246;
+	public static final int BEIGE_PUMPKIN_LAUGHING_30247 = 30247;
+	public static final int WHITE_PUMPKIN_DISGUSTED_30248 = 30248;
+	public static final int WHITE_PUMPKIN_SILLY_30249 = 30249;
+	public static final int WHITE_PUMPKIN_EVIL_30250 = 30250;
+	public static final int WHITE_PUMPKIN_ANGRY_30251 = 30251;
+	public static final int WHITE_PUMPKIN_DEPRESSED_30252 = 30252;
+	public static final int WHITE_PUMPKIN_SHOCKED_30253 = 30253;
+	public static final int WHITE_PUMPKIN_SAD_30254 = 30254;
+	public static final int WHITE_PUMPKIN_HAPPY_30255 = 30255;
+	public static final int WHITE_PUMPKIN_LAUGHING_30256 = 30256;
+	public static final int YELLOW_PUMPKIN_DISGUSTED_30257 = 30257;
+	public static final int YELLOW_PUMPKIN_SILLY_30258 = 30258;
+	public static final int YELLOW_PUMPKIN_EVIL_30259 = 30259;
+	public static final int YELLOW_PUMPKIN_ANGRY_30260 = 30260;
+	public static final int YELLOW_PUMPKIN_DEPRESSED_30261 = 30261;
+	public static final int YELLOW_PUMPKIN_SHOCKED_30262 = 30262;
+	public static final int YELLOW_PUMPKIN_SAD_30263 = 30263;
+	public static final int YELLOW_PUMPKIN_HAPPY_30264 = 30264;
+	public static final int YELLOW_PUMPKIN_LAUGHING_30265 = 30265;
+	public static final int ORANGE_PUMPKIN_DISGUSTED_30266 = 30266;
+	public static final int ORANGE_PUMPKIN_SILLY_30267 = 30267;
+	public static final int ORANGE_PUMPKIN_EVIL_30268 = 30268;
+	public static final int ORANGE_PUMPKIN_ANGRY_30269 = 30269;
+	public static final int ORANGE_PUMPKIN_DEPRESSED_30270 = 30270;
+	public static final int ORANGE_PUMPKIN_SHOCKED_30271 = 30271;
+	public static final int ORANGE_PUMPKIN_SAD_30272 = 30272;
+	public static final int ORANGE_PUMPKIN_HAPPY_30273 = 30273;
+	public static final int ORANGE_PUMPKIN_LAUGHING_30274 = 30274;
+	public static final int RED_PUMPKIN_DISGUSTED_30275 = 30275;
+	public static final int RED_PUMPKIN_SILLY_30276 = 30276;
+	public static final int RED_PUMPKIN_EVIL_30277 = 30277;
+	public static final int RED_PUMPKIN_ANGRY_30278 = 30278;
+	public static final int RED_PUMPKIN_DEPRESSED_30279 = 30279;
+	public static final int RED_PUMPKIN_SHOCKED_30280 = 30280;
+	public static final int RED_PUMPKIN_SAD_30281 = 30281;
+	public static final int RED_PUMPKIN_HAPPY_30282 = 30282;
+	public static final int RED_PUMPKIN_LAUGHING_30283 = 30283;
+	public static final int DARK_GREEN_PUMPKIN_DISGUSTED_30284 = 30284;
+	public static final int DARK_GREEN_PUMPKIN_SILLY_30285 = 30285;
+	public static final int DARK_GREEN_PUMPKIN_EVIL_30286 = 30286;
+	public static final int DARK_GREEN_PUMPKIN_ANGRY_30287 = 30287;
+	public static final int DARK_GREEN_PUMPKIN_DEPRESSED_30288 = 30288;
+	public static final int DARK_GREEN_PUMPKIN_SHOCKED_30289 = 30289;
+	public static final int DARK_GREEN_PUMPKIN_SAD_30290 = 30290;
+	public static final int DARK_GREEN_PUMPKIN_HAPPY_30291 = 30291;
+	public static final int DARK_GREEN_PUMPKIN_LAUGHING_30292 = 30292;
+	public static final int POWDER_GREY_PUMPKIN_DISGUSTED_30293 = 30293;
+	public static final int POWDER_GREY_PUMPKIN_SILLY_30294 = 30294;
+	public static final int POWDER_GREY_PUMPKIN_EVIL_30295 = 30295;
+	public static final int POWDER_GREY_PUMPKIN_ANGRY_30296 = 30296;
+	public static final int POWDER_GREY_PUMPKIN_DEPRESSED_30297 = 30297;
+	public static final int POWDER_GREY_PUMPKIN_SHOCKED_30298 = 30298;
+	public static final int POWDER_GREY_PUMPKIN_SAD_30299 = 30299;
+	public static final int POWDER_GREY_PUMPKIN_HAPPY_30300 = 30300;
+	public static final int POWDER_GREY_PUMPKIN_LAUGHING_30301 = 30301;
+	public static final int TORVA_FULL_HELM_30302 = 30302;
+	public static final int TORVA_PLATEBODY_30303 = 30303;
+	public static final int TORVA_PLATELEGS_30304 = 30304;
+	public static final int ARCLIGHT_INACTIVE = 30305;
 	/* This file is automatically generated. Do not edit. */
 }
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 ed2a35e21..641520c64 100644
--- a/runelite-api/src/main/java/net/runelite/api/NpcID.java
+++ b/runelite-api/src/main/java/net/runelite/api/NpcID.java
@@ -11602,6 +11602,7 @@ public final class NpcID
 	public static final int FROST_CRAB_13791 = 13791;
 	public static final int ICE_BLOCK_13792 = 13792;
 	public static final int BREWMA = 13793;
+	public static final int MARIKA = 13794;
 	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;
@@ -11839,5 +11840,10 @@ public final class NpcID
 	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;
+	public static final int MARIKA_14107 = 14107;
+	public static final int POSTIE_PETE_14108 = 14108;
+	public static final int GHOST_CAT = 14109;
+	public static final int CROW_14110 = 14110;
+	public static final int STRAY_DOG_14112 = 14112;
 	/* This file is automatically generated. Do not edit. */
 }
diff --git a/runelite-api/src/main/java/net/runelite/api/NullItemID.java b/runelite-api/src/main/java/net/runelite/api/NullItemID.java
index 97b567970..33aa75d57 100644
--- a/runelite-api/src/main/java/net/runelite/api/NullItemID.java
+++ b/runelite-api/src/main/java/net/runelite/api/NullItemID.java
@@ -15570,5 +15570,10 @@ public final class NullItemID
 	public static final int NULL_30155 = 30155;
 	public static final int NULL_30158 = 30158;
 	public static final int NULL_30159 = 30159;
+	public static final int NULL_30233 = 30233;
+	public static final int NULL_30235 = 30235;
+	public static final int NULL_30238 = 30238;
+	public static final int NULL_30240 = 30240;
+	public static final int NULL_30306 = 30306;
 	/* This file is automatically generated. Do not edit. */
 }
diff --git a/runelite-api/src/main/java/net/runelite/api/NullNpcID.java b/runelite-api/src/main/java/net/runelite/api/NullNpcID.java
index 80e9b8eb7..fc40a40ee 100644
--- a/runelite-api/src/main/java/net/runelite/api/NullNpcID.java
+++ b/runelite-api/src/main/java/net/runelite/api/NullNpcID.java
@@ -2248,5 +2248,6 @@ public final class NullNpcID
 	public static final int NULL_14100 = 14100;
 	public static final int NULL_14101 = 14101;
 	public static final int NULL_14102 = 14102;
+	public static final int NULL_14111 = 14111;
 	/* 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 8df4af53e..f83e56093 100644
--- a/runelite-api/src/main/java/net/runelite/api/NullObjectID.java
+++ b/runelite-api/src/main/java/net/runelite/api/NullObjectID.java
@@ -28815,5 +28815,30 @@ public final class NullObjectID
 	public static final int NULL_55421 = 55421;
 	public static final int NULL_55422 = 55422;
 	public static final int NULL_55423 = 55423;
+	public static final int NULL_55476 = 55476;
+	public static final int NULL_55477 = 55477;
+	public static final int NULL_55478 = 55478;
+	public static final int NULL_55479 = 55479;
+	public static final int NULL_55480 = 55480;
+	public static final int NULL_55622 = 55622;
+	public static final int NULL_55623 = 55623;
+	public static final int NULL_55624 = 55624;
+	public static final int NULL_55625 = 55625;
+	public static final int NULL_55626 = 55626;
+	public static final int NULL_55627 = 55627;
+	public static final int NULL_55628 = 55628;
+	public static final int NULL_55629 = 55629;
+	public static final int NULL_55630 = 55630;
+	public static final int NULL_55631 = 55631;
+	public static final int NULL_55632 = 55632;
+	public static final int NULL_55633 = 55633;
+	public static final int NULL_55634 = 55634;
+	public static final int NULL_55635 = 55635;
+	public static final int NULL_55636 = 55636;
+	public static final int NULL_55637 = 55637;
+	public static final int NULL_55771 = 55771;
+	public static final int NULL_55772 = 55772;
+	public static final int NULL_55773 = 55773;
+	public static final int NULL_55774 = 55774;
 	/* 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 c00198faf..8e6342a3e 100644
--- a/runelite-api/src/main/java/net/runelite/api/ObjectID.java
+++ b/runelite-api/src/main/java/net/runelite/api/ObjectID.java
@@ -26595,5 +26595,329 @@ public final class ObjectID
 	public static final int GRAPE_STORAGE_55424 = 55424;
 	public static final int GRAPE_STORAGE_55425 = 55425;
 	public static final int GRAPE_STORAGE_55426 = 55426;
+	public static final int JACKOLANTERN_55427 = 55427;
+	public static final int JACKOLANTERN_55428 = 55428;
+	public static final int JACKOLANTERN_55429 = 55429;
+	public static final int JACKOLANTERN_55430 = 55430;
+	public static final int JACKOLANTERN_55431 = 55431;
+	public static final int JACKOLANTERN_55432 = 55432;
+	public static final int JACKOLANTERNS_55433 = 55433;
+	public static final int SHADE_DECORATION_55434 = 55434;
+	public static final int SHADE_DECORATION_55435 = 55435;
+	public static final int SHADE_DECORATION_55436 = 55436;
+	public static final int SHADE_DECORATION_55437 = 55437;
+	public static final int SPOOKY_STOCKS_55438 = 55438;
+	public static final int SPOOKY_GRAVESTONE_55439 = 55439;
+	public static final int JACKOLANTERN_55440 = 55440;
+	public static final int JACKOLANTERN_55441 = 55441;
+	public static final int JACKOLANTERN_55442 = 55442;
+	public static final int JACKOLANTERN_55443 = 55443;
+	public static final int JACKOLANTERN_55444 = 55444;
+	public static final int JACKOLANTERN_55445 = 55445;
+	public static final int JACKOLANTERN_55446 = 55446;
+	public static final int JACKOLANTERN_55447 = 55447;
+	public static final int JACKOLANTERN_55448 = 55448;
+	public static final int JACKOLANTERN_55449 = 55449;
+	public static final int JACKOLANTERNS_55450 = 55450;
+	public static final int JACKOLANTERNS_55451 = 55451;
+	public static final int JACKOLANTERNS_55452 = 55452;
+	public static final int JACKOLANTERNS_55453 = 55453;
+	public static final int JACKOLANTERNS_55454 = 55454;
+	public static final int JACKOLANTERNS_55455 = 55455;
+	public static final int JACKOLANTERNS_55456 = 55456;
+	public static final int JACKOLANTERNS_55457 = 55457;
+	public static final int JACKOLANTERNS_55458 = 55458;
+	public static final int JACKOLANTERNS_55459 = 55459;
+	public static final int JACKOLANTERNS_55460 = 55460;
+	public static final int JACKOLANTERNS_55461 = 55461;
+	public static final int JACKOLANTERNS_55462 = 55462;
+	public static final int JACKOLANTERNS_55463 = 55463;
+	public static final int JACKOLANTERN_55464 = 55464;
+	public static final int JACKOLANTERN_55465 = 55465;
+	public static final int JACKOLANTERN_55466 = 55466;
+	public static final int JACKOLANTERN_55467 = 55467;
+	public static final int JACKOLANTERN_55468 = 55468;
+	public static final int JACKOLANTERN_55469 = 55469;
+	public static final int JACKOLANTERN_55470 = 55470;
+	public static final int JACKOLANTERN_55471 = 55471;
+	public static final int JACKOLANTERN_55472 = 55472;
+	public static final int JACKOLANTERN_55473 = 55473;
+	public static final int JACKOLANTERN_55474 = 55474;
+	public static final int JACKOLANTERN_55475 = 55475;
+	public static final int SCARECROW_55481 = 55481;
+	public static final int SCARECROW_55482 = 55482;
+	public static final int SCARECROW_55483 = 55483;
+	public static final int SCARECROW_55484 = 55484;
+	public static final int SCARECROW_55485 = 55485;
+	public static final int SCARECROW_55486 = 55486;
+	public static final int SCARECROW_55487 = 55487;
+	public static final int SCARECROW_55488 = 55488;
+	public static final int SCARECROW_55489 = 55489;
+	public static final int SCARECROW_55490 = 55490;
+	public static final int SCARECROW_55491 = 55491;
+	public static final int SCARECROW_55492 = 55492;
+	public static final int SCARECROW_55493 = 55493;
+	public static final int SCARECROW_55494 = 55494;
+	public static final int SCARECROW_55495 = 55495;
+	public static final int SCARECROW_55496 = 55496;
+	public static final int SCARECROW_55497 = 55497;
+	public static final int SCARECROW_55498 = 55498;
+	public static final int SCARECROW_55499 = 55499;
+	public static final int SCARECROW_55500 = 55500;
+	public static final int SCARECROW_55501 = 55501;
+	public static final int SCARECROW_55502 = 55502;
+	public static final int SCARECROW_55503 = 55503;
+	public static final int SCARECROW_55504 = 55504;
+	public static final int SCARECROW_55505 = 55505;
+	public static final int SCARECROW_55506 = 55506;
+	public static final int SCARECROW_55507 = 55507;
+	public static final int SCARECROW_55508 = 55508;
+	public static final int SCARECROW_55509 = 55509;
+	public static final int SCARECROW_55510 = 55510;
+	public static final int SCARECROW_55511 = 55511;
+	public static final int SCARECROW_55512 = 55512;
+	public static final int SCARECROW_55513 = 55513;
+	public static final int SCARECROW_55514 = 55514;
+	public static final int SCARECROW_55515 = 55515;
+	public static final int SCARECROW_55516 = 55516;
+	public static final int SCARECROW_55517 = 55517;
+	public static final int SCARECROW_55518 = 55518;
+	public static final int SCARECROW_55519 = 55519;
+	public static final int SCARECROW_55520 = 55520;
+	public static final int SCARECROW_55521 = 55521;
+	public static final int SCARECROW_55522 = 55522;
+	public static final int SCARECROW_55523 = 55523;
+	public static final int SCARECROW_55524 = 55524;
+	public static final int SCARECROW_55525 = 55525;
+	public static final int SCARECROW_55526 = 55526;
+	public static final int SCARECROW_55527 = 55527;
+	public static final int SCARECROW_55528 = 55528;
+	public static final int SCARECROW_55529 = 55529;
+	public static final int SCARECROW_55530 = 55530;
+	public static final int SCARECROW_55531 = 55531;
+	public static final int SCARECROW_55532 = 55532;
+	public static final int SCARECROW_55533 = 55533;
+	public static final int SCARECROW_55534 = 55534;
+	public static final int SCARECROW_55535 = 55535;
+	public static final int SCARECROW_55536 = 55536;
+	public static final int SCARECROW_55537 = 55537;
+	public static final int SCARECROW_55538 = 55538;
+	public static final int SCARECROW_55539 = 55539;
+	public static final int SCARECROW_55540 = 55540;
+	public static final int SCARECROW_55541 = 55541;
+	public static final int SCARECROW_55542 = 55542;
+	public static final int SCARECROW_55543 = 55543;
+	public static final int SCARECROW_55544 = 55544;
+	public static final int SCARECROW_55545 = 55545;
+	public static final int SCARECROW_55546 = 55546;
+	public static final int SCARECROW_55547 = 55547;
+	public static final int SCARECROW_55548 = 55548;
+	public static final int SCARECROW_55549 = 55549;
+	public static final int SCARECROW_55550 = 55550;
+	public static final int SCARECROW_55551 = 55551;
+	public static final int SCARECROW_55552 = 55552;
+	public static final int SCARECROW_55553 = 55553;
+	public static final int SCARECROW_55554 = 55554;
+	public static final int SCARECROW_55555 = 55555;
+	public static final int SCARECROW_55556 = 55556;
+	public static final int SCARECROW_55557 = 55557;
+	public static final int SCARECROW_55558 = 55558;
+	public static final int SCARECROW_55559 = 55559;
+	public static final int SCARECROW_55560 = 55560;
+	public static final int SCARECROW_55561 = 55561;
+	public static final int SCARECROW_55562 = 55562;
+	public static final int SCARECROW_55563 = 55563;
+	public static final int SCARECROW_55564 = 55564;
+	public static final int SCARECROW_55565 = 55565;
+	public static final int SCARECROW_55566 = 55566;
+	public static final int SCARECROW_55567 = 55567;
+	public static final int SCARECROW_55568 = 55568;
+	public static final int SCARECROW_55569 = 55569;
+	public static final int SCARECROW_55570 = 55570;
+	public static final int SCARECROW_55571 = 55571;
+	public static final int SCARECROW_55572 = 55572;
+	public static final int SCARECROW_55573 = 55573;
+	public static final int SCARECROW_55574 = 55574;
+	public static final int SCARECROW_55575 = 55575;
+	public static final int SCARECROW_55576 = 55576;
+	public static final int SCARECROW_55577 = 55577;
+	public static final int SCARECROW_55578 = 55578;
+	public static final int SCARECROW_55579 = 55579;
+	public static final int SCARECROW_55580 = 55580;
+	public static final int SCARECROW_55581 = 55581;
+	public static final int SCARECROW_55582 = 55582;
+	public static final int SCARECROW_55583 = 55583;
+	public static final int SCARECROW_55584 = 55584;
+	public static final int SCARECROW_55585 = 55585;
+	public static final int SCARECROW_55586 = 55586;
+	public static final int SCARECROW_55587 = 55587;
+	public static final int SCARECROW_55588 = 55588;
+	public static final int SCARECROW_55589 = 55589;
+	public static final int SCARECROW_55590 = 55590;
+	public static final int SCARECROW_55591 = 55591;
+	public static final int SCARECROW_55592 = 55592;
+	public static final int SCARECROW_55593 = 55593;
+	public static final int SCARECROW_55594 = 55594;
+	public static final int SCARECROW_55595 = 55595;
+	public static final int SCARECROW_55596 = 55596;
+	public static final int SCARECROW_55597 = 55597;
+	public static final int SCARECROW_55598 = 55598;
+	public static final int SCARECROW_55599 = 55599;
+	public static final int SCARECROW_55600 = 55600;
+	public static final int SCARECROW_55601 = 55601;
+	public static final int SCARECROW_55602 = 55602;
+	public static final int SCARECROW_55603 = 55603;
+	public static final int SCARECROW_55604 = 55604;
+	public static final int SCARECROW_55605 = 55605;
+	public static final int SCARECROW_55606 = 55606;
+	public static final int BEIGE_PUMPKINS = 55607;
+	public static final int WHITE_PUMPKINS = 55608;
+	public static final int YELLOW_PUMPKINS = 55609;
+	public static final int ORANGE_PUMPKINS = 55610;
+	public static final int RED_PUMPKINS = 55611;
+	public static final int DARK_GREEN_PUMPKINS = 55612;
+	public static final int POWDER_GREY_PUMPKINS = 55613;
+	public static final int BEIGE_PUMPKIN_SEEDLING = 55614;
+	public static final int WHITE_PUMPKIN_SEEDLING = 55615;
+	public static final int YELLOW_PUMPKIN_SEEDLING = 55616;
+	public static final int ORANGE_PUMPKIN_SEEDLING = 55617;
+	public static final int RED_PUMPKIN_SEEDLING = 55618;
+	public static final int DARK_GREEN_PUMPKIN_SEEDLING = 55619;
+	public static final int POWDER_GREY_PUMPKIN_SEEDLING = 55620;
+	public static final int PUMPKIN_PATCH = 55621;
+	public static final int STORAGE_BIN = 55638;
+	public static final int STORAGE_BIN_55639 = 55639;
+	public static final int STORAGE_BIN_55640 = 55640;
+	public static final int PUMPKIN_CARVING_TABLE = 55641;
+	public static final int PUMPKIN_CARVING_TABLE_55642 = 55642;
+	public static final int PUMPKIN_CARVING_TABLE_55643 = 55643;
+	public static final int SCARECROW_55644 = 55644;
+	public static final int SCARECROW_55645 = 55645;
+	public static final int SCARECROW_55646 = 55646;
+	public static final int SCARECROW_55647 = 55647;
+	public static final int SCARECROW_55648 = 55648;
+	public static final int SCARECROW_55649 = 55649;
+	public static final int SCARECROW_55650 = 55650;
+	public static final int SCARECROW_55651 = 55651;
+	public static final int SCARECROW_55652 = 55652;
+	public static final int SCARECROW_55653 = 55653;
+	public static final int SCARECROW_55654 = 55654;
+	public static final int SCARECROW_55655 = 55655;
+	public static final int SCARECROW_55656 = 55656;
+	public static final int SCARECROW_55657 = 55657;
+	public static final int SCARECROW_55658 = 55658;
+	public static final int SCARECROW_55659 = 55659;
+	public static final int SCARECROW_55660 = 55660;
+	public static final int SCARECROW_55661 = 55661;
+	public static final int SCARECROW_55662 = 55662;
+	public static final int SCARECROW_55663 = 55663;
+	public static final int SCARECROW_55664 = 55664;
+	public static final int SCARECROW_55665 = 55665;
+	public static final int SCARECROW_55666 = 55666;
+	public static final int SCARECROW_55667 = 55667;
+	public static final int SCARECROW_55668 = 55668;
+	public static final int SCARECROW_55669 = 55669;
+	public static final int SCARECROW_55670 = 55670;
+	public static final int SCARECROW_55671 = 55671;
+	public static final int SCARECROW_55672 = 55672;
+	public static final int SCARECROW_55673 = 55673;
+	public static final int SCARECROW_55674 = 55674;
+	public static final int SCARECROW_55675 = 55675;
+	public static final int SCARECROW_55676 = 55676;
+	public static final int SCARECROW_55677 = 55677;
+	public static final int SCARECROW_55678 = 55678;
+	public static final int SCARECROW_55679 = 55679;
+	public static final int SCARECROW_55680 = 55680;
+	public static final int SCARECROW_55681 = 55681;
+	public static final int SCARECROW_55682 = 55682;
+	public static final int SCARECROW_55683 = 55683;
+	public static final int SCARECROW_55684 = 55684;
+	public static final int SCARECROW_55685 = 55685;
+	public static final int SCARECROW_55686 = 55686;
+	public static final int SCARECROW_55687 = 55687;
+	public static final int SCARECROW_55688 = 55688;
+	public static final int SCARECROW_55689 = 55689;
+	public static final int SCARECROW_55690 = 55690;
+	public static final int SCARECROW_55691 = 55691;
+	public static final int SCARECROW_55692 = 55692;
+	public static final int SCARECROW_55693 = 55693;
+	public static final int SCARECROW_55694 = 55694;
+	public static final int SCARECROW_55695 = 55695;
+	public static final int SCARECROW_55696 = 55696;
+	public static final int SCARECROW_55697 = 55697;
+	public static final int SCARECROW_55698 = 55698;
+	public static final int SCARECROW_55699 = 55699;
+	public static final int SCARECROW_55700 = 55700;
+	public static final int SCARECROW_55701 = 55701;
+	public static final int SCARECROW_55702 = 55702;
+	public static final int SCARECROW_55703 = 55703;
+	public static final int SCARECROW_55704 = 55704;
+	public static final int SCARECROW_55705 = 55705;
+	public static final int SCARECROW_55706 = 55706;
+	public static final int SCARECROW_55707 = 55707;
+	public static final int SCARECROW_55708 = 55708;
+	public static final int SCARECROW_55709 = 55709;
+	public static final int SCARECROW_55710 = 55710;
+	public static final int SCARECROW_55711 = 55711;
+	public static final int SCARECROW_55712 = 55712;
+	public static final int SCARECROW_55713 = 55713;
+	public static final int SCARECROW_55714 = 55714;
+	public static final int SCARECROW_55715 = 55715;
+	public static final int SCARECROW_55716 = 55716;
+	public static final int SCARECROW_55717 = 55717;
+	public static final int SCARECROW_55718 = 55718;
+	public static final int SCARECROW_55719 = 55719;
+	public static final int SCARECROW_55720 = 55720;
+	public static final int SCARECROW_55721 = 55721;
+	public static final int SCARECROW_55722 = 55722;
+	public static final int SCARECROW_55723 = 55723;
+	public static final int SCARECROW_55724 = 55724;
+	public static final int SCARECROW_55725 = 55725;
+	public static final int SCARECROW_55726 = 55726;
+	public static final int SCARECROW_55727 = 55727;
+	public static final int SCARECROW_55728 = 55728;
+	public static final int SCARECROW_55729 = 55729;
+	public static final int SCARECROW_55730 = 55730;
+	public static final int SCARECROW_55731 = 55731;
+	public static final int SCARECROW_55732 = 55732;
+	public static final int SCARECROW_55733 = 55733;
+	public static final int SCARECROW_55734 = 55734;
+	public static final int SCARECROW_55735 = 55735;
+	public static final int SCARECROW_55736 = 55736;
+	public static final int SCARECROW_55737 = 55737;
+	public static final int SCARECROW_55738 = 55738;
+	public static final int SCARECROW_55739 = 55739;
+	public static final int SCARECROW_55740 = 55740;
+	public static final int SCARECROW_55741 = 55741;
+	public static final int SCARECROW_55742 = 55742;
+	public static final int SCARECROW_55743 = 55743;
+	public static final int SCARECROW_55744 = 55744;
+	public static final int SCARECROW_55745 = 55745;
+	public static final int SCARECROW_55746 = 55746;
+	public static final int SCARECROW_55747 = 55747;
+	public static final int SCARECROW_55748 = 55748;
+	public static final int SCARECROW_55749 = 55749;
+	public static final int SCARECROW_55750 = 55750;
+	public static final int SCARECROW_55751 = 55751;
+	public static final int SCARECROW_55752 = 55752;
+	public static final int SCARECROW_55753 = 55753;
+	public static final int SCARECROW_55754 = 55754;
+	public static final int SCARECROW_55755 = 55755;
+	public static final int SCARECROW_55756 = 55756;
+	public static final int SCARECROW_55757 = 55757;
+	public static final int SCARECROW_55758 = 55758;
+	public static final int SCARECROW_55759 = 55759;
+	public static final int SCARECROW_55760 = 55760;
+	public static final int SCARECROW_55761 = 55761;
+	public static final int SCARECROW_55762 = 55762;
+	public static final int SCARECROW_55763 = 55763;
+	public static final int SCARECROW_55764 = 55764;
+	public static final int SCARECROW_55765 = 55765;
+	public static final int SCARECROW_55766 = 55766;
+	public static final int SCARECROW_55767 = 55767;
+	public static final int SCARECROW_55768 = 55768;
+	public static final int SCARECROW_55769 = 55769;
+	public static final int SCARECROW_55770 = 55770;
+	public static final int MURAL_55775 = 55775;
 	/* 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 527f29e4e..a92247aaf 100644
--- a/runelite-api/src/main/java/net/runelite/api/Scene.java
+++ b/runelite-api/src/main/java/net/runelite/api/Scene.java
@@ -161,4 +161,11 @@ public interface Scene extends Renderable
 	 * @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/ScriptID.java b/runelite-api/src/main/java/net/runelite/api/ScriptID.java
index 840196d77..acdfba409 100644
--- a/runelite-api/src/main/java/net/runelite/api/ScriptID.java
+++ b/runelite-api/src/main/java/net/runelite/api/ScriptID.java
@@ -468,7 +468,7 @@ public final class ScriptID
 	@ScriptArguments(integer = 1)
 	public static final int POTIONSTORE_WITHDRAW_DOSES = 4818;
 
-	@ScriptArguments
+	@ScriptArguments()
 	public static final int POTIONSTORE_BUILD = 6080;
 
 	@ScriptArguments(integer = 3)
diff --git a/runelite-api/src/main/java/net/runelite/api/VarPlayer.java b/runelite-api/src/main/java/net/runelite/api/VarPlayer.java
index 8242b60f5..fb16cbf64 100644
--- a/runelite-api/src/main/java/net/runelite/api/VarPlayer.java
+++ b/runelite-api/src/main/java/net/runelite/api/VarPlayer.java
@@ -181,6 +181,9 @@ public final class VarPlayer
 	 */
 	public static final int HP_HUD_NPC_ID = 1683;
 
+	public static final int CLOG_LOGGED = 2943;
+	public static final int CLOG_TOTAL = 2944;
+
 	/**
 	 * Colors for chat messages
 	 */
diff --git a/runelite-api/src/main/java/net/runelite/api/Varbits.java b/runelite-api/src/main/java/net/runelite/api/Varbits.java
index de6790707..847eb84d4 100644
--- a/runelite-api/src/main/java/net/runelite/api/Varbits.java
+++ b/runelite-api/src/main/java/net/runelite/api/Varbits.java
@@ -936,4 +936,11 @@ public final class Varbits
 	 * </p>
 	 */
 	public static final int COLOSSAL_WYRM_COURSE_ADVANCED = 11293;
+
+	public static final int COMBAT_TASK_EASY = 12885;
+	public static final int COMBAT_TASK_MEDIUM = 12886;
+	public static final int COMBAT_TASK_HARD = 12887;
+	public static final int COMBAT_TASK_ELITE = 12888;
+	public static final int COMBAT_TASK_MASTER = 12889;
+	public static final int COMBAT_TASK_GRANDMASTER = 12890;
 }
diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml
index 98020cc38..ef85d7476 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.41-SNAPSHOT</version>
+		<version>1.10.43-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>client</artifactId>
diff --git a/runelite-client/src/main/java/net/runelite/client/config/RuneScapeProfileType.java b/runelite-client/src/main/java/net/runelite/client/config/RuneScapeProfileType.java
index e17590cdb..25ccb26d1 100644
--- a/runelite-client/src/main/java/net/runelite/client/config/RuneScapeProfileType.java
+++ b/runelite-client/src/main/java/net/runelite/client/config/RuneScapeProfileType.java
@@ -44,7 +44,8 @@ public enum RuneScapeProfileType
 	TRAILBLAZER_LEAGUE,
 	DEADMAN_REBORN,
 	SHATTERED_RELICS_LEAGUE,
-	TRAILBLAZER_RELOADED_LEAGUE(client -> client.getWorldType().contains(WorldType.SEASONAL)),
+	TRAILBLAZER_RELOADED_LEAGUE,
+	RAGING_ECHOES_LEAGUE(client -> client.getWorldType().contains(WorldType.SEASONAL)),
 	;
 
 	private final Predicate<Client> test;
diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemClient.java b/runelite-client/src/main/java/net/runelite/client/game/ItemClient.java
index 6c2a28831..2da536f99 100644
--- a/runelite-client/src/main/java/net/runelite/client/game/ItemClient.java
+++ b/runelite-client/src/main/java/net/runelite/client/game/ItemClient.java
@@ -37,7 +37,6 @@ import javax.inject.Inject;
 import javax.inject.Named;
 import lombok.extern.slf4j.Slf4j;
 import net.runelite.http.api.item.ItemPrice;
-import net.runelite.http.api.item.ItemStats;
 import okhttp3.HttpUrl;
 import okhttp3.OkHttpClient;
 import okhttp3.Request;
@@ -98,7 +97,6 @@ public class ItemClient
 	{
 		HttpUrl.Builder urlBuilder = staticBase.newBuilder()
 			.addPathSegment("item")
-			// TODO: Change this to stats.min.json later after release is undeployed
 			.addPathSegment("stats.ids.min.json");
 
 		HttpUrl url = urlBuilder.build();
diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemEquipmentStats.java b/runelite-client/src/main/java/net/runelite/client/game/ItemEquipmentStats.java
new file mode 100644
index 000000000..dd5755b5e
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/game/ItemEquipmentStats.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ *    list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.game;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Builder;
+import lombok.Value;
+
+@Value
+@Builder
+public class ItemEquipmentStats
+{
+	int slot;
+
+	@SerializedName("is2h")
+	boolean isTwoHanded;
+
+	int astab;
+	int aslash;
+	int acrush;
+	int amagic;
+	int arange;
+
+	int dstab;
+	int dslash;
+	int dcrush;
+	int dmagic;
+	int drange;
+
+	int str;
+	int rstr;
+	float mdmg;
+	int prayer;
+	int aspeed;
+
+	net.runelite.http.api.item.ItemEquipmentStats toHttpApiFormat()
+	{
+		return net.runelite.http.api.item.ItemEquipmentStats.builder()
+			.slot(this.slot)
+			.isTwoHanded(this.isTwoHanded)
+			.astab(this.astab)
+			.aslash(this.aslash)
+			.acrush(this.acrush)
+			.amagic(this.amagic)
+			.arange(this.arange)
+			.dstab(this.dstab)
+			.dslash(this.dslash)
+			.dcrush(this.dcrush)
+			.dmagic(this.dmagic)
+			.drange(this.drange)
+			.str(this.str)
+			.rstr(this.rstr)
+			.mdmg((int) this.mdmg)
+			.prayer(this.prayer)
+			.aspeed(this.aspeed)
+			.build();
+	}
+}
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 54e85a67e..1b22e42ef 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
@@ -58,7 +58,6 @@ import net.runelite.client.callback.ClientThread;
 import net.runelite.client.config.RuneLiteConfig;
 import net.runelite.client.util.AsyncBufferedImage;
 import net.runelite.http.api.item.ItemPrice;
-import net.runelite.http.api.item.ItemStats;
 
 @Singleton
 @Slf4j
@@ -351,11 +350,11 @@ public class ItemManager
 	 * @return item stats
 	 */
 	@Nullable
-	public ItemStats getItemStats(int itemId, boolean allowNote)
+	public ItemStats getItemStats(int itemId)
 	{
 		ItemComposition itemComposition = getItemComposition(itemId);
 
-		if (itemComposition == null || itemComposition.getName() == null || (!allowNote && itemComposition.getNote() != -1))
+		if (itemComposition.getName() == null || itemComposition.getNote() != -1)
 		{
 			return null;
 		}
@@ -363,6 +362,28 @@ public class ItemManager
 		return itemStats.get(canonicalize(itemId));
 	}
 
+	/**
+	 * Look up an item's stats
+	 *
+	 * @param itemId item id
+	 * @return item stats
+	 * @deprecated See {@link #getItemStats(int)}
+	 */
+	@Nullable
+	@Deprecated
+	public net.runelite.http.api.item.ItemStats getItemStats(int itemId, boolean allowNote)
+	{
+		ItemComposition itemComposition = getItemComposition(itemId);
+
+		if (itemComposition.getName() == null || (!allowNote && itemComposition.getNote() != -1))
+		{
+			return null;
+		}
+
+		var stats = itemStats.get(canonicalize(itemId));
+		return stats != null ? stats.toHttpApiFormat() : null;
+	}
+
 	/**
 	 * Search for tradeable items based on item name
 	 *
diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemStats.java b/runelite-client/src/main/java/net/runelite/client/game/ItemStats.java
new file mode 100644
index 000000000..1357879c1
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/game/ItemStats.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ *    list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.game;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Value;
+
+@Value
+public class ItemStats
+{
+	boolean equipable;
+	double weight;
+	@SerializedName("ge_limit")
+	int geLimit;
+
+	ItemEquipmentStats equipment;
+
+	net.runelite.http.api.item.ItemStats toHttpApiFormat()
+	{
+		var equipment = this.equipment == null ? null : this.equipment.toHttpApiFormat();
+		return new net.runelite.http.api.item.ItemStats(this.equipable, this.weight, this.geLimit, equipment);
+	}
+}
+
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTag.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTag.java
index 064a51fd2..a0a56fede 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTag.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTag.java
@@ -25,17 +25,14 @@
  */
 package net.runelite.client.plugins.banktags;
 
-import net.runelite.client.plugins.banktags.tabs.Layout;
-
 /**
  * A bank tag. Plugins may implement this interface to define custom bank tags.
  * You may register a BankTag with {@link TagManager#registerTag(String, BankTag)} to
- * make it searchable in the bank UI and tab interface. You may also set the active bank tag
- * via {@link BankTagsService#openBankTag(BankTag)}, regardless of if the banktag is registered.
+ * make it searchable in the bank UI and tab interface.
  *
  * @see TagManager#registerTag(String, BankTag)
  * @see TagManager#unregisterTag(String)
- * @see BankTagsService#openBankTag(BankTag)
+ * @see BankTagsService#openBankTag(String, int)
  */
 public interface BankTag
 {
@@ -45,13 +42,4 @@ public interface BankTag
 	 * @return
 	 */
 	boolean contains(int itemId);
-
-	/**
-	 * The tag layout
-	 * @return the layout for the tag, or null for no layout
-	 */
-	default Layout layout()
-	{
-		return null;
-	}
 }
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 c70ba0cc2..d75f620c4 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
@@ -68,7 +68,6 @@ import net.runelite.client.plugins.banktags.tabs.LayoutManager;
 import net.runelite.client.plugins.banktags.tabs.TabInterface;
 import static net.runelite.client.plugins.banktags.tabs.TabInterface.FILTERED_CHARS;
 import net.runelite.client.plugins.banktags.tabs.TabSprites;
-import net.runelite.client.plugins.banktags.tabs.TagTab;
 import net.runelite.client.util.Text;
 
 @PluginDescriptor(
@@ -141,7 +140,16 @@ public class BankTagsPlugin extends Plugin implements BankTagsService
 	private BankTagsConfig config;
 
 	@Getter
-	private BankTag activeTag;
+	private String activeTag;
+
+	@Getter
+	private BankTag activeBankTag;
+
+	@Getter
+	private Layout activeLayout;
+
+	@Getter
+	private int options;
 
 	@Override
 	public void configure(Binder binder)
@@ -323,7 +331,7 @@ public class BankTagsPlugin extends Plugin implements BankTagsService
 				final int itemId = intStack[intStackSize - 1];
 				String searchfilter = stringStack[stringStackSize - 1];
 
-				BankTag tag = activeTag;
+				BankTag tag = activeBankTag;
 				boolean tagSearch = true;
 				// Shared storage uses ~bankmain_filteritem too. Allow using tag searches in it but don't
 				// apply the tag search from the active tab.
@@ -345,7 +353,7 @@ public class BankTagsPlugin extends Plugin implements BankTagsService
 					tag = buildSearchFilterBankTag(searchfilter);
 				}
 
-				if (itemId == -1 && tag.layout() != null)
+				if (itemId == -1 && activeLayout != null)
 				{
 					// item -1 always passes on a laid out tab so items can be dragged to it
 					return;
@@ -364,12 +372,12 @@ public class BankTagsPlugin extends Plugin implements BankTagsService
 				}
 				break;
 			case "getSearchingTagTab":
-				intStack[intStackSize - 1] = activeTag != null ? 1 : 0;
+				intStack[intStackSize - 1] = activeBankTag != null ? 1 : 0;
 				break;
 			case "bankBuildTab":
 				// Use the per-tab view when we want to hide the separators to avoid having to reposition items &
 				// recomputing the scroll height.
-				if (activeTag != null && (tabInterface.isTagTabActive() || config.removeSeparators() || activeTag.layout() != null))
+				if (activeBankTag != null && (tabInterface.isTagTabActive() || config.removeSeparators() || activeLayout != null))
 				{
 					var stack = client.getIntStack();
 					var sz = client.getIntStackSize();
@@ -455,32 +463,28 @@ public class BankTagsPlugin extends Plugin implements BankTagsService
 		}
 	}
 
-	public void open(TagTab tab)
+	public void openTag(String tag, Layout layout)
 	{
-		if (tab == null)
+		openTag(tag, layout, OPTION_ALLOW_MODIFICATIONS);
+	}
+
+	public void openTag(String tag, Layout layout, int options)
+	{
+		if (tag == null)
 		{
-			activeTag = null;
+			this.activeTag = null;
+			this.activeBankTag = null;
+			this.activeLayout = null;
+			this.options = 0;
 			return;
 		}
 
-		// custom tags are combined with the tab
-		final BankTag custom = tagManager.findTag(tab.getTag());
+		this.activeTag = tag;
+		this.activeBankTag = buildSearchFilterBankTag(tag);
+		this.activeLayout = layout;
+		this.options = options;
 
-		activeTag = new BankTag()
-		{
-			@Override
-			public boolean contains(int itemId)
-			{
-				return tagManager.findTag(itemId, tab.getTag())
-					|| (custom != null && custom.contains(itemId));
-			}
-
-			@Override
-			public Layout layout()
-			{
-				return tab.getLayout();
-			}
-		};
+		tabInterface.openTag(tag, layout, options, true);
 	}
 
 	private BankTag buildSearchFilterBankTag(String tag)
@@ -491,19 +495,26 @@ public class BankTagsPlugin extends Plugin implements BankTagsService
 			|| (custom != null && custom.contains(itemId));
 	}
 
+	public void openBankTag(String name)
+	{
+		openBankTag(name, OPTION_ALLOW_MODIFICATIONS);
+	}
+
 	@Override
-	public void openTagTab(TagTab tagTab)
+	public void openBankTag(String name, int options)
 	{
-		tabInterface.closeTag(false);
-		open(tagTab);
-		bankSearch.layoutBank();
+		Layout layout = layoutManager.loadLayout(name);
+		openTag(name, layout, options);
 	}
 
 	@Override
-	public void openBankTag(BankTag bankTag)
+	public void closeBankTag()
 	{
 		tabInterface.closeTag(false);
-		activeTag = bankTag;
+		this.activeTag = null;
+		this.activeBankTag = null;
+		this.activeLayout = null;
+		this.options = 0;
 		bankSearch.layoutBank();
 	}
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsService.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsService.java
index da41171e1..641a7701c 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsService.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsService.java
@@ -25,19 +25,62 @@
  */
 package net.runelite.client.plugins.banktags;
 
-import net.runelite.client.plugins.banktags.tabs.TagTab;
+import javax.annotation.Nullable;
+import net.runelite.client.plugins.banktags.tabs.Layout;
 
+/**
+ * API for the bank tags plugin
+ *
+ * @see TagManager
+ * @see net.runelite.client.plugins.banktags.tabs.TabManager
+ * @see net.runelite.client.plugins.banktags.tabs.LayoutManager
+ */
 public interface BankTagsService
 {
 	/**
-	 * Open the given tag tab.
-	 * @param tagTab
+	 * Bank tag is allowed to be modified
+	 */
+	int OPTION_ALLOW_MODIFICATIONS = 1;
+	/**
+	 * Option to hide the tag name from the "Remove-tag" menu option.
+	 */
+	int OPTION_HIDE_REMOVE_TAG_NAME = 2;
+
+	/**
+	 * Open the given bank tag. The tag may have an associated {@link net.runelite.client.plugins.banktags.tabs.TagTab},
+	 * but this isn't required. If the tag has an associated {@link net.runelite.client.plugins.banktags.tabs.Layout},
+	 * the layout will be applied.
+	 *
+	 * @param tag the tag name
+	 * @param options
+	 * @see #OPTION_ALLOW_MODIFICATIONS
+	 * @see #OPTION_HIDE_REMOVE_TAG_NAME
+	 */
+	void openBankTag(String tag, int options);
+
+	/**
+	 * Close the currently open {@link BankTag}.
+	 */
+	void closeBankTag();
+
+	/**
+	 * Get the currently active bank tag
+	 * @return
+	 */
+	@Nullable
+	String getActiveTag();
+
+	/**
+	 * Get the currently open {@link BankTag}
+	 * @return
 	 */
-	void openTagTab(TagTab tagTab);
+	@Nullable
+	BankTag getActiveBankTag();
 
 	/**
-	 * Open the given bank tag.
-	 * @param bankTag
+	 * Get the currently active {@link Layout}
+	 * @return
 	 */
-	void openBankTag(BankTag bankTag);
+	@Nullable
+	Layout getActiveLayout();
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/AutoLayout.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/AutoLayout.java
index b511b7672..4dfe7af9c 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/AutoLayout.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/AutoLayout.java
@@ -27,5 +27,5 @@ package net.runelite.client.plugins.banktags.tabs;
 
 public interface AutoLayout
 {
-	Layout generateLayout(TagTab tab);
+	Layout generateLayout(Layout previous);
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/Layout.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/Layout.java
index f4cab6500..1ac4778e7 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/Layout.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/Layout.java
@@ -26,25 +26,30 @@
 package net.runelite.client.plugins.banktags.tabs;
 
 import java.util.Arrays;
+import lombok.Getter;
 import lombok.NonNull;
 
 public class Layout
 {
+	@Getter
+	private final String tag;
 	private int[] layout;
-	boolean dirty = true;
 
-	public Layout()
+	public Layout(String tag)
 	{
+		this.tag = tag;
 		this.layout = new int[0];
 	}
 
-	public Layout(@NonNull int[] layout)
+	public Layout(String tag, @NonNull int[] layout)
 	{
+		this.tag = tag;
 		this.layout = layout;
 	}
 
 	public Layout(Layout other)
 	{
+		tag = other.tag;
 		layout = other.layout.clone();
 	}
 
@@ -83,7 +88,6 @@ public class Layout
 		}
 
 		layout[pos] = itemId;
-		dirty = true;
 	}
 
 	public void addItem(int itemId)
@@ -93,8 +97,6 @@ public class Layout
 
 	public void addItemAfter(int itemId, int pos)
 	{
-		dirty = true;
-
 		int i;
 		for (i = pos; i < layout.length; ++i)
 		{
@@ -116,7 +118,6 @@ public class Layout
 			if (layout[i] == itemId)
 			{
 				layout[i] = -1;
-				dirty = true;
 			}
 		}
 	}
@@ -129,7 +130,6 @@ public class Layout
 		}
 
 		layout[pos] = -1;
-		dirty = true;
 	}
 
 	void swap(int sidx, int tidx)
@@ -137,7 +137,6 @@ public class Layout
 		int sid = layout[sidx];
 		layout[sidx] = layout[tidx];
 		layout[tidx] = sid;
-		dirty = true;
 	}
 
 	void insert(int sidx, int tidx)
@@ -169,7 +168,6 @@ public class Layout
 			System.arraycopy(layout, tidx, layout, tidx + 1, i - tidx);
 			layout[tidx] = sid;
 		}
-		dirty = true;
 	}
 
 	public int count(int itemId)
@@ -198,6 +196,5 @@ public class Layout
 			Arrays.fill(n, layout.length, size, -1);
 		}
 		layout = n;
-		dirty = true;
 	}
 }
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 c7d452b24..5afe3a3dd 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
@@ -25,7 +25,6 @@
  */
 package net.runelite.client.plugins.banktags.tabs;
 
-import com.google.common.util.concurrent.Runnables;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -34,6 +33,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import javax.inject.Inject;
 import javax.inject.Singleton;
 import lombok.NonNull;
@@ -57,6 +57,7 @@ import net.runelite.api.ScriptID;
 import net.runelite.api.VarClientInt;
 import net.runelite.api.Varbits;
 import net.runelite.api.events.MenuEntryAdded;
+import net.runelite.api.events.MenuOptionClicked;
 import net.runelite.api.events.ScriptPreFired;
 import net.runelite.api.widgets.ComponentID;
 import net.runelite.api.widgets.ItemQuantityMode;
@@ -64,6 +65,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.config.ConfigManager;
 import net.runelite.client.eventbus.EventBus;
 import net.runelite.client.eventbus.Subscribe;
 import net.runelite.client.game.ItemManager;
@@ -79,6 +81,9 @@ import static net.runelite.client.plugins.banktags.BankTagsPlugin.BANK_ITEM_STAR
 import static net.runelite.client.plugins.banktags.BankTagsPlugin.BANK_ITEM_WIDTH;
 import static net.runelite.client.plugins.banktags.BankTagsPlugin.BANK_ITEM_X_PADDING;
 import static net.runelite.client.plugins.banktags.BankTagsPlugin.BANK_ITEM_Y_PADDING;
+import static net.runelite.client.plugins.banktags.BankTagsPlugin.CONFIG_GROUP;
+import static net.runelite.client.plugins.banktags.BankTagsPlugin.TAG_LAYOUT_PREFIX;
+import net.runelite.client.plugins.banktags.BankTagsService;
 import static net.runelite.client.plugins.banktags.tabs.TabInterface.DUPLICATE_ITEM;
 import static net.runelite.client.plugins.banktags.tabs.TabInterface.REMOVE_LAYOUT;
 import net.runelite.client.util.Text;
@@ -92,29 +97,27 @@ public class LayoutManager
 	private final BankTagsPlugin plugin;
 	private final ChatboxPanelManager chatboxPanelManager;
 	private final BankSearch bankSearch;
-	private final TabManager tabManager;
-	private final TabInterface tabInterface;
 	private final ChatMessageManager chatMessageManager;
 	private final PotionStorage potionStorage;
 	private final EventBus eventBus;
+	private final ConfigManager configManager;
 
 	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,
-		PotionStorage potionStorage, EventBus eventBus)
+		BankSearch bankSearch, ChatMessageManager chatMessageManager,
+		PotionStorage potionStorage, EventBus eventBus, ConfigManager configManager)
 	{
 		this.client = client;
 		this.itemManager = itemManager;
 		this.plugin = plugin;
 		this.chatboxPanelManager = chatboxPanelManager;
 		this.bankSearch = bankSearch;
-		this.tabManager = tabManager;
-		this.tabInterface = tabInterface;
 		this.chatMessageManager = chatMessageManager;
 		this.potionStorage = potionStorage;
 		this.eventBus = eventBus;
+		this.configManager = configManager;
 
 		registerAutoLayout(plugin, "Default", new DefaultLayout());
 	}
@@ -131,6 +134,45 @@ public class LayoutManager
 		eventBus.unregister(potionStorage);
 	}
 
+	@Nullable
+	public Layout loadLayout(String tag)
+	{
+		String layoutStr = configManager.getConfiguration(CONFIG_GROUP, TAG_LAYOUT_PREFIX + Text.standardize(tag));
+		if (layoutStr != null)
+		{
+			List<String> layoutList = Text.fromCSV(layoutStr);
+			int[] layout = new int[layoutList.size()];
+			for (int i = 0; i < layoutList.size(); ++i)
+			{
+				layout[i] = Integer.parseInt(layoutList.get(i));
+			}
+
+			return new Layout(tag, layout);
+		}
+		return null;
+	}
+
+	public void saveLayout(Layout layout)
+	{
+		String tag = layout.getTag();
+		int[] l = layout.getLayout();
+		StringBuilder sb = new StringBuilder(l.length * 5);
+		for (int i = 0; i < l.length; ++i)
+		{
+			if (i > 0)
+			{
+				sb.append(',');
+			}
+			sb.append(l[i]);
+		}
+		configManager.setConfiguration(CONFIG_GROUP, TAG_LAYOUT_PREFIX + Text.standardize(tag), sb.toString());
+	}
+
+	public void removeLayout(String tag)
+	{
+		configManager.unsetConfiguration(CONFIG_GROUP, TAG_LAYOUT_PREFIX + Text.standardize(tag));
+	}
+
 	private void layout(Layout l)
 	{
 		ItemContainer bank = client.getItemContainer(InventoryID.BANK);
@@ -282,7 +324,7 @@ public class LayoutManager
 
 		if (modified)
 		{
-			tabManager.save();
+			saveLayout(l);
 		}
 	}
 
@@ -325,9 +367,9 @@ public class LayoutManager
 				c.setItemQuantity(Integer.MAX_VALUE);
 				c.setItemQuantityMode(ItemQuantityMode.NEVER);
 
-				// TabInterface rewrites these to RUNELITE types and adds handlers
-				if (tabInterface.isActive())
+				if ((plugin.getOptions() & BankTagsService.OPTION_ALLOW_MODIFICATIONS) != 0)
 				{
+					// TabInterface rewrites these to RUNELITE types and adds handlers
 					c.setAction(7 - 1, DUPLICATE_ITEM);
 					c.setAction(8 - 1, REMOVE_LAYOUT);
 				}
@@ -442,7 +484,7 @@ public class LayoutManager
 			l.insert(sidx, tidx);
 		}
 
-		tabManager.save();
+		saveLayout(l);
 		bankSearch.layoutBank();
 	}
 
@@ -544,7 +586,7 @@ public class LayoutManager
 		{
 			resetWidgets();
 
-			BankTag activeTag = plugin.getActiveTag();
+			BankTag activeTag = plugin.getActiveBankTag();
 			if (activeTag != null)
 			{
 				// Since the script vm isn't reentrant, we can't call into POTIONSTORE_DOSES/POTIONSTORE_WITHDRAW_DOSES
@@ -553,7 +595,7 @@ public class LayoutManager
 				// them by the time the inv transmit listener runs.
 				potionStorage.cachePotions = true;
 
-				Layout layout = activeTag.layout();
+				Layout layout = plugin.getActiveLayout();
 				if (layout != null)
 				{
 					layout(layout);
@@ -586,8 +628,7 @@ public class LayoutManager
 		}
 	}
 
-	@Subscribe(priority = -1) // run after TabInterface sets up the Duplicate/Remove layout menus
-	private void onMenuEntryAdded(MenuEntryAdded event)
+	void onMenuEntryAdded(MenuEntryAdded event, TabInterface tabInterface)
 	{
 		if (event.getActionParam1() == ComponentID.BANK_CONTENT_CONTAINER && event.getOption().equals(TabInterface.DISABLE_LAYOUT))
 		{
@@ -600,8 +641,8 @@ public class LayoutManager
 					.setType(MenuAction.RUNELITE_HIGH_PRIORITY)
 					.onClick(e ->
 					{
-						TagTab tab = tabManager.find(Text.removeTags(e.getTarget()));
-						if (tab != tabInterface.getActiveTab())
+						String tag = Text.standardize(e.getTarget());
+						if (!tag.equals(tabInterface.getActiveTag()))
 						{
 							chatMessageManager.queue(QueuedMessage.builder()
 								.type(ChatMessageType.CONSOLE)
@@ -610,30 +651,26 @@ public class LayoutManager
 							return;
 						}
 
-						Layout old = tab.getLayout();
-						Layout new_ = autoLayout.autoLayout.generateLayout(tab);
-						tab.setLayout(new_);
-						bankSearch.layoutBank();
-						tab.setLayout(old);
-						tabManager.save();
+						Layout old = plugin.getActiveLayout();
+						Layout new_ = autoLayout.autoLayout.generateLayout(old);
+						plugin.openTag(tag, new_);
 
 						chatboxPanelManager.openTextMenuInput("Tab laid out using the '" + autoLayout.getName() + "' layout.")
 							.option("1. Keep", () ->
-							{
-								tab.setLayout(new_);
-								tabManager.save();
-							})
-							.option("2. Undo", Runnables.doNothing())
+								saveLayout(new_))
+							.option("2. Undo", () ->
+								plugin.openTag(tag, old))
 							.onClose(bankSearch::layoutBank)
 							.build();
 					});
 			}
 		}
+	}
 
+	void onMenuOptionClicked(MenuOptionClicked event)
+	{
 		// Update widget index of the menu so withdraws work in laid out tabs.
-		BankTag activeTag = plugin.getActiveTag();
-		if (event.getActionParam1()  == ComponentID.BANK_ITEM_CONTAINER
-			&& activeTag != null && !tabInterface.isTagTabActive() && activeTag.layout() != null)
+		if (event.getParam1() == ComponentID.BANK_ITEM_CONTAINER && plugin.getActiveLayout() != null)
 		{
 			MenuEntry menu = event.getMenuEntry();
 			Widget w = menu.getWidget();
@@ -690,9 +727,9 @@ public class LayoutManager
 	private class DefaultLayout implements AutoLayout
 	{
 		@Override
-		public Layout generateLayout(TagTab tab)
+		public Layout generateLayout(Layout previous)
 		{
-			Layout l = new Layout(tab.getLayout());
+			Layout l = new Layout(previous);
 			List<Integer> removed = new ArrayList<>();
 
 			// Equipment
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
index 532ec72eb..fd0237836 100644
--- 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
@@ -95,7 +95,7 @@ class PotionStorage
 			if (layout)
 			{
 				layout = false;
-				BankTag activeTag = plugin.getActiveTag();
+				BankTag activeTag = plugin.getActiveBankTag();
 				if (activeTag != null)
 				{
 					bankSearch.layoutBank();
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 d66e683a9..328ed842c 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
@@ -88,6 +88,7 @@ import net.runelite.client.plugins.banktags.BankTagsConfig;
 import net.runelite.client.plugins.banktags.BankTagsPlugin;
 import static net.runelite.client.plugins.banktags.BankTagsPlugin.TAG_SEARCH;
 import static net.runelite.client.plugins.banktags.BankTagsPlugin.VAR_TAG_SUFFIX;
+import net.runelite.client.plugins.banktags.BankTagsService;
 import net.runelite.client.plugins.banktags.TagManager;
 import net.runelite.client.ui.JagexColors;
 import net.runelite.client.util.ColorUtil;
@@ -112,7 +113,7 @@ public class TabInterface
 	private static final String REMOVE_TAG = "Remove-tag";
 	private static final String TAG_GEAR = "Tag-equipment";
 	private static final String TAG_INVENTORY = "Tag-inventory";
-	private static final String TAB_MENU_KEY = "tagtabs";
+	private static final String TAGTABS = "tagtabs";
 	private static final String OPEN_TAB_MENU = "View tag tabs";
 	static final String ENABLE_LAYOUT = "Enable layout";
 	static final String DISABLE_LAYOUT = "Disable layout";
@@ -150,6 +151,7 @@ public class TabInterface
 	private final ItemManager itemManager;
 	private final TagManager tagManager;
 	private final TabManager tabManager;
+	private final LayoutManager layoutManager;
 	private final ChatboxPanelManager chatboxPanelManager;
 	private final BankTagsConfig config;
 	private final BankSearch bankSearch;
@@ -158,7 +160,9 @@ public class TabInterface
 
 	private boolean enabled;
 	@Getter
-	private TagTab activeTab;
+	private String activeTag;
+	private Layout activeLayout;
+	private int activeOptions;
 	@Getter
 	private boolean tagTabActive;
 	private int tagTabFirstChildIdx = -1;
@@ -180,6 +184,7 @@ public class TabInterface
 		final ItemManager itemManager,
 		final TagManager tagManager,
 		final TabManager tabManager,
+		final LayoutManager layoutManager,
 		final ChatboxPanelManager chatboxPanelManager,
 		final BankTagsConfig config,
 		final BankSearch bankSearch,
@@ -193,6 +198,7 @@ public class TabInterface
 		this.itemManager = itemManager;
 		this.tagManager = tagManager;
 		this.tabManager = tabManager;
+		this.layoutManager = layoutManager;
 		this.chatboxPanelManager = chatboxPanelManager;
 		this.config = config;
 		this.bankSearch = bankSearch;
@@ -200,11 +206,6 @@ public class TabInterface
 		this.chatMessageManager = chatMessageManager;
 	}
 
-	public boolean isActive()
-	{
-		return activeTab != null;
-	}
-
 	@Subscribe
 	public void onScriptPreFired(ScriptPreFired event)
 	{
@@ -246,7 +247,7 @@ public class TabInterface
 		}
 		else if (event.getScriptId() == ScriptID.BANKMAIN_SEARCH_TOGGLE)
 		{
-			if (activeTab != null || tagTabActive)
+			if (activeTag != null || tagTabActive)
 			{
 				// close the active tab when search is pressed
 				closeTag(false);
@@ -270,10 +271,10 @@ public class TabInterface
 				Widget bankTitle = client.getWidget(ComponentID.BANK_TITLE_BAR);
 				bankTitle.setText("Tag tab tab");
 			}
-			else if (activeTab != null)
+			else if (activeTag != null)
 			{
 				Widget bankTitle = client.getWidget(ComponentID.BANK_TITLE_BAR);
-				bankTitle.setText("Tag tab <col=ff0000>" + activeTab.getTag() + "</col>");
+				bankTitle.setText("Tag tab <col=ff0000>" + activeTag + "</col>");
 			}
 
 			// Recompute scroll size. Only required for tag tab tab since it doesn't show real items.
@@ -295,8 +296,10 @@ public class TabInterface
 		{
 			enabled = false;
 			upButton = downButton = newTab = scrollComponent = parent = null;
-			activeTab = null;
-			plugin.open(null);
+			activeTag = null;
+			activeLayout = null;
+			activeOptions = 0;
+			plugin.openTag(null, null);
 			tagTabActive = false;
 			tagTabFirstChildIdx = -1;
 		}
@@ -346,9 +349,8 @@ public class TabInterface
 			// the server will resync the last opened vanilla tab when the bank is opened
 			client.setVarbit(Varbits.CURRENT_BANK_TAB, 0);
 			var tab = config.tab();
-			activeTab = tabManager.find(tab);
-			tagTabActive = TAB_MENU_KEY.equals(tab);
-			plugin.open(activeTab);
+			var layout = layoutManager.loadLayout(tab);
+			plugin.openTag(tab, layout);
 		}
 
 		// Move equipment button to the titlebar
@@ -391,8 +393,10 @@ public class TabInterface
 	public void deinit()
 	{
 		enabled = false;
-		activeTab = null;
-		plugin.open(null);
+		activeTag = null;
+		activeLayout = null;
+		activeOptions = 0;
+		plugin.openTag(null, null);
 
 		upButton = downButton = newTab = scrollComponent = null;
 
@@ -422,11 +426,11 @@ public class TabInterface
 
 		if (!Strings.isNullOrEmpty(event.getMenuTarget()))
 		{
-			if (activeTab != null && Text.removeTags(event.getMenuTarget()).equals(activeTab.getTag()))
+			if (activeTag != null && Text.removeTags(event.getMenuTarget()).equals(activeTag))
 			{
 				for (Integer item : items)
 				{
-					tagManager.addTag(item, activeTab.getTag(), false);
+					tagManager.addTag(item, activeTag, false);
 				}
 
 				reloadActiveTab();
@@ -498,7 +502,7 @@ public class TabInterface
 					rebuildTabs();
 					rebuildTagTabTab();
 
-					if (activeTab != null && tab.getTag().equals(activeTab.getTag()))
+					if (tab.getTag().equals(activeTag))
 					{
 						bankSearch.reset(true);
 					}
@@ -513,7 +517,7 @@ public class TabInterface
 				break;
 			case NEWTAB_OP_OPEN_TAB_MENU:
 				client.setVarbit(Varbits.CURRENT_BANK_TAB, 0);
-				openNamedTag(TAB_MENU_KEY, true);
+				openTag(TAGTABS, null, 0, true);
 				break;
 		}
 	}
@@ -559,7 +563,7 @@ public class TabInterface
 
 		if (dataIter.hasNext())
 		{
-			Layout l = new Layout();
+			Layout l = new Layout(name);
 			while (dataIter.hasNext())
 			{
 				final int idx = Integer.parseInt(dataIter.next());
@@ -568,7 +572,7 @@ public class TabInterface
 				tagManager.addTag(itemId, name, false);
 			}
 
-			tab.setLayout(l);
+			layoutManager.saveLayout(l);
 		}
 
 		return tab;
@@ -596,8 +600,7 @@ public class TabInterface
 		name = sb.toString();
 
 		TagTab tab = tabManager.load(name);
-		Layout l = new Layout();
-		tab.setLayout(l);
+		Layout l = new Layout(name);
 
 		while (dataIter.hasNext())
 		{
@@ -622,6 +625,8 @@ public class TabInterface
 			tagManager.addTag(itemId, name, itemId < 0);
 		}
 
+		layoutManager.saveLayout(l);
+
 		return tab;
 	}
 
@@ -642,15 +647,16 @@ public class TabInterface
 
 				Widget clicked = event.getSource();
 
-				TagTab tab = tabManager.find(Text.removeTags(clicked.getName()));
+				String tag = Text.removeTags(clicked.getName());
 
-				if (tab.equals(activeTab))
+				if (tag.equals(activeTag))
 				{
 					closeTag(true);
 				}
 				else
 				{
-					openTag(tab, true);
+					Layout layout = layoutManager.loadLayout(tag);
+					plugin.openTag(tag, layout);
 				}
 
 				client.playSoundEffect(SoundEffectID.UI_BOOP);
@@ -677,23 +683,26 @@ public class TabInterface
 			}
 			case TAB_OP_LAYOUT:
 			{
-				TagTab tab = tabManager.find(Text.removeTags(event.getSource().getName()));
-				if (tab == null)
-				{
-					return;
-				}
+				String tag = Text.removeTags(event.getSource().getName());
+				Layout layout = layoutManager.loadLayout(tag);
 
-				if (!tab.hasLayout())
+				if (layout == null)
 				{
-					tab.setLayout(new Layout());
-					sendChatMessage("Tag tab '" + tab.getTag() + "' is now in layout mode. You may reorder the items without changing their order in the bank.");
+					layout = new Layout(tag);
+					layoutManager.saveLayout(layout);
+					sendChatMessage("Tag tab '" + tag + "' is now in layout mode. You may reorder the items without changing their order in the bank.");
 				}
 				else
 				{
-					tab.setLayout(null);
-					sendChatMessage("Tag tab '" + tab.getTag() + "' is no longer in layout mode");
+					layoutManager.removeLayout(tag);
+					layout = null;
+					sendChatMessage("Tag tab '" + tag + "' is no longer in layout mode");
+				}
+
+				if (tag.equals(activeTag))
+				{
+					plugin.openTag(tag, layout);
 				}
-				tabManager.save();
 
 				bankSearch.layoutBank();
 				break;
@@ -714,7 +723,9 @@ public class TabInterface
 				break;
 			case TAB_OP_EXPORT_TAB:
 				final List<String> data = new ArrayList<>();
-				final TagTab tagTab = tabManager.find(Text.removeTags(event.getOpbase()));
+				final String tag = Text.removeTags(event.getOpbase());
+				final TagTab tagTab = tabManager.find(tag);
+				final Layout layout = layoutManager.loadLayout(tag);
 				data.add("banktags");
 				data.add("1");
 				data.add(tagTab.getTag());
@@ -722,22 +733,22 @@ public class TabInterface
 
 				for (Integer item : tagManager.getItemsForTag(tagTab.getTag()))
 				{
-					if (!tagTab.hasLayout() || tagTab.getLayout().count(item) == 0)
+					if (layout == null || layout.count(item) == 0)
 					{
 						data.add(String.valueOf(item));
 					}
 				}
 
-				if (tagTab.hasLayout())
+				if (layout != null)
 				{
 					data.add("layout");
-					int[] layout = tagTab.getLayout().getLayout();
-					for (int idx = 0; idx < layout.length; ++idx)
+					int[] l = layout.getLayout();
+					for (int idx = 0; idx < l.length; ++idx)
 					{
-						if (layout[idx] != -1)
+						if (l[idx] != -1)
 						{
 							data.add(String.valueOf(idx));
-							data.add(String.valueOf(layout[idx]));
+							data.add(String.valueOf(l[idx]));
 						}
 					}
 				}
@@ -756,11 +767,11 @@ public class TabInterface
 	@Subscribe
 	private void onMenuEntryAdded(MenuEntryAdded event)
 	{
-		if (activeTab != null
+		if ((activeOptions & BankTagsService.OPTION_ALLOW_MODIFICATIONS) != 0
 			&& event.getActionParam1() == ComponentID.BANK_ITEM_CONTAINER
 			&& event.getOption().equals("Examine"))
 		{
-			if (activeTab.hasLayout())
+			if (activeLayout != null)
 			{
 				client.createMenuEntry(-1)
 					.setParam0(event.getActionParam0())
@@ -773,7 +784,7 @@ public class TabInterface
 					.onClick(this::opDuplicateItem);
 			}
 
-			if (activeTab.hasLayout() && activeTab.getLayout().count(itemManager.canonicalize(event.getItemId())) > 1)
+			if (activeLayout != null && activeLayout.count(itemManager.canonicalize(event.getItemId())) > 1)
 			{
 				client.createMenuEntry(-1)
 					.setParam0(event.getActionParam0())
@@ -790,25 +801,25 @@ public class TabInterface
 					.setParam0(event.getActionParam0())
 					.setParam1(event.getActionParam1())
 					.setTarget(event.getTarget())
-					.setOption(REMOVE_TAG + " (" + activeTab.getTag() + ")")
+					.setOption((activeOptions & BankTagsService.OPTION_HIDE_REMOVE_TAG_NAME) == 0 ? REMOVE_TAG + " (" + activeTag + ")" : REMOVE_TAG)
 					.setType(MenuAction.RUNELITE)
 					.setIdentifier(event.getIdentifier())
 					.setItemId(event.getItemId())
 					.onClick(e ->
 					{
 						final int itemId = e.getItemId();
-						if (activeTab.hasLayout())
+						if (activeLayout != null)
 						{
-							activeTab.getLayout().removeItem(itemId);
-							tabManager.save();
+							activeLayout.removeItem(itemId);
+							layoutManager.saveLayout(activeLayout);
 						}
-						tagManager.removeTag(itemId, activeTab.getTag());
+						tagManager.removeTag(itemId, activeTag);
 						bankSearch.layoutBank(); // re-layout to filter the removed item out
 					});
 			}
 		}
 		// Duplicate/Remove on layout placeholders
-		else if (activeTab != null
+		else if (activeTag != null
 			&& event.getActionParam1() == ComponentID.BANK_ITEM_CONTAINER
 			&& event.getOption().equals(DUPLICATE_ITEM))
 		{
@@ -816,7 +827,7 @@ public class TabInterface
 			event.getMenuEntry().setType(MenuAction.RUNELITE_LOW_PRIORITY);
 			event.getMenuEntry().onClick(this::opDuplicateItem);
 		}
-		if (activeTab != null
+		if (activeTag != null
 			&& event.getActionParam1() == ComponentID.BANK_ITEM_CONTAINER
 			&& event.getOption().equals(REMOVE_LAYOUT))
 		{
@@ -828,9 +839,9 @@ public class TabInterface
 		{
 			createMenuEntry(event, TAG_INVENTORY, event.getTarget());
 
-			if (activeTab != null)
+			if (activeTag != null && (activeOptions & BankTagsService.OPTION_ALLOW_MODIFICATIONS) != 0)
 			{
-				createMenuEntry(event, TAG_INVENTORY, ColorUtil.wrapWithColorTag(activeTab.getTag(), HILIGHT_COLOR));
+				createMenuEntry(event, TAG_INVENTORY, ColorUtil.wrapWithColorTag(activeTag, HILIGHT_COLOR));
 			}
 		}
 		else if (event.getActionParam1() == ComponentID.BANK_DEPOSIT_EQUIPMENT
@@ -838,30 +849,32 @@ public class TabInterface
 		{
 			createMenuEntry(event, TAG_GEAR, event.getTarget());
 
-			if (activeTab != null)
+			if (activeTag != null && (activeOptions & BankTagsService.OPTION_ALLOW_MODIFICATIONS) != 0)
 			{
-				createMenuEntry(event, TAG_GEAR, ColorUtil.wrapWithColorTag(activeTab.getTag(), HILIGHT_COLOR));
+				createMenuEntry(event, TAG_GEAR, ColorUtil.wrapWithColorTag(activeTag, HILIGHT_COLOR));
 			}
 		}
+
+		layoutManager.onMenuEntryAdded(event, this);
 	}
 
 	private void opDuplicateItem(MenuEntry e)
 	{
 		int id = itemManager.canonicalize(e.getItemId());
 		log.debug("Duplicate item {} at {}", itemManager.getItemComposition(id).getName(), e.getParam0());
-		activeTab.getLayout().addItemAfter(id, e.getParam0());
-		tabManager.save();
+		activeLayout.addItemAfter(id, e.getParam0());
+		layoutManager.saveLayout(activeLayout);
 		bankSearch.layoutBank();
 	}
 
 	private void opRemoveLayout(MenuEntry e)
 	{
-		activeTab.getLayout().removeItemAtPos(e.getParam0());
-		tabManager.save();
+		activeLayout.removeItemAtPos(e.getParam0());
+		layoutManager.saveLayout(activeLayout);
 		bankSearch.layoutBank();
 	}
 
-	@Subscribe
+	@Subscribe(priority = -1)
 	public void onMenuOptionClicked(MenuOptionClicked event)
 	{
 		// Close the chatbox input when clicking on things in the bank, to mimic how actions like withdrawing
@@ -888,6 +901,8 @@ public class TabInterface
 		{
 			handleDeposit(event, event.getParam1() == ComponentID.BANK_DEPOSIT_INVENTORY);
 		}
+
+		layoutManager.onMenuOptionClicked(event);
 	}
 
 	@Subscribe
@@ -903,8 +918,9 @@ public class TabInterface
 
 		// Returning early or nulling the drag release listener has no effect. Hence, we need to
 		// null the draggedOnWidget instead.
-		if (draggedWidget.getId() == ComponentID.BANK_ITEM_CONTAINER && activeTab != null && !activeTab.hasLayout()
-			&& config.preventTagTabDrags())
+		if (draggedWidget.getId() == ComponentID.BANK_ITEM_CONTAINER && activeTag != null
+			&& (activeLayout == null && config.preventTagTabDrags()
+			|| (activeOptions & BankTagsService.OPTION_ALLOW_MODIFICATIONS) == 0))
 		{
 			client.setDraggedOnWidget(null);
 		}
@@ -994,9 +1010,9 @@ public class TabInterface
 	{
 		w.setAction(TAB_OP_OPEN_TAG, VIEW_TAB);
 		w.setAction(TAB_OP_CHANGE_ICON, CHANGE_ICON);
-		if (!TAB_MENU_KEY.equals(tab.getTag()))
+		if (!TAGTABS.equals(tab.getTag()))
 		{
-			w.setAction(TAB_OP_LAYOUT, tab.hasLayout() ? DISABLE_LAYOUT : ENABLE_LAYOUT);
+			w.setAction(TAB_OP_LAYOUT, activeLayout != null ? DISABLE_LAYOUT : ENABLE_LAYOUT);
 		}
 		w.setAction(TAB_OP_EXPORT_TAB, EXPORT_TAB);
 		w.setAction(TAB_OP_RENAME_TAB, RENAME_TAB);
@@ -1025,7 +1041,7 @@ public class TabInterface
 
 	private void deleteTab(String tag)
 	{
-		if (activeTab != null && activeTab.getTag().equals(tag))
+		if (tag.equals(activeTag))
 		{
 			closeTag(true);
 		}
@@ -1033,6 +1049,8 @@ public class TabInterface
 		tabManager.remove(tag);
 		tabManager.save();
 
+		layoutManager.removeLayout(tag);
+
 		repositionButtons();
 		rebuildTabs();
 		rebuildTagTabTab();
@@ -1050,12 +1068,27 @@ public class TabInterface
 					if (tabManager.find(newTag) == null)
 					{
 						TagTab tagTab = tabManager.find(oldTag);
+						Layout layout = layoutManager.loadLayout(oldTag);
+
+						tabManager.remove(oldTag); // remove the icon
+						layoutManager.removeLayout(oldTag); // remove the layout
 
-						tabManager.remove(oldTag); // remove the icon/layout of the old tag
 						tagTab.setTag(newTag);
+
 						tabManager.add(tagTab);
 						tabManager.save();
 
+						if (activeTag.equals(oldTag))
+						{
+							activeTag = newTag;
+						}
+
+						if (layout != null)
+						{
+							Layout newLayout = new Layout(newTag, layout.getLayout());
+							layoutManager.saveLayout(newLayout);
+						}
+
 						tagManager.renameTag(oldTag, newTag); // rename tag on items
 
 						rebuildTabs();
@@ -1070,12 +1103,11 @@ public class TabInterface
 								clientThread.invoke(() ->
 								{
 									tagManager.renameTag(oldTag, newTag);
-									final String activeTag = activeTab != null ? activeTab.getTag() : "";
 									deleteTab(oldTag);
 
-									if (activeTag.equals(oldTag))
+									if (oldTag.equals(activeTag))
 									{
-										openNamedTag(newTag, true);
+										plugin.openBankTag(newTag);
 									}
 									else
 									{
@@ -1124,25 +1156,13 @@ public class TabInterface
 		layoutTabs();
 	}
 
-	private void openNamedTag(String name, boolean relayout)
+	public void openTag(String tag, Layout layout, int options, boolean relayout)
 	{
-		activeTab = tabManager.find(name);
-		tagTabActive = TAB_MENU_KEY.equals(name);
-		plugin.open(activeTab);
-		config.tab(name);
-
-		if (relayout)
-		{
-			bankSearch.reset(true);
-		}
-	}
-
-	private void openTag(TagTab tab, boolean relayout)
-	{
-		activeTab = tab;
-		tagTabActive = tab != null && TAB_MENU_KEY.equals(tab.getTag());
-		plugin.open(activeTab);
-		config.tab(tab != null ? tab.getTag() : "");
+		activeTag = tag;
+		activeLayout = layout;
+		activeOptions = options;
+		tagTabActive = TAGTABS.equals(tag);
+		config.tab(tag);
 
 		if (relayout)
 		{
@@ -1152,9 +1172,11 @@ public class TabInterface
 
 	public void closeTag(boolean relayout)
 	{
-		activeTab = null;
+		activeTag = null;
+		activeLayout = null;
+		activeOptions = 0;
 		tagTabActive = false;
-		plugin.open(null);
+		plugin.openTag(null, null);
 		config.tab("");
 
 		if (relayout)
@@ -1165,9 +1187,9 @@ public class TabInterface
 
 	public void reloadActiveTab()
 	{
-		if (activeTab != null)
+		if (activeTag != null)
 		{
-			bankSearch.reset(true);
+			plugin.openBankTag(activeTag);
 		}
 	}
 
@@ -1235,7 +1257,7 @@ public class TabInterface
 		for (TagTab tab : tabs)
 		{
 			Widget background = createGraphic(parent, ColorUtil.wrapWithColorTag(tab.getTag(), HILIGHT_COLOR),
-				(activeTab == tab ? TabSprites.TAB_BACKGROUND_ACTIVE : TabSprites.TAB_BACKGROUND).getSpriteId(),
+				(tab.getTag().equals(activeTag) ? TabSprites.TAB_BACKGROUND_ACTIVE : TabSprites.TAB_BACKGROUND).getSpriteId(),
 				-1, TAB_WIDTH, TAB_HEIGHT, MARGIN, -1);
 			addTabActions(tab, background);
 
@@ -1270,7 +1292,9 @@ public class TabInterface
 		int y = scrollComponent.getOriginalY();
 		y += MARGIN;
 
-		for (int i = tabScrollOffset; i < tabScrollOffset + tabCount && i * 2 + 1 < children.length - TAGTAB_CHILD_OFFSET; ++i)
+		for (int i = tabScrollOffset;
+			i < tabScrollOffset + tabCount && i * 2 + 1 < children.length - TAGTAB_CHILD_OFFSET && children[TAGTAB_CHILD_OFFSET + i * 2] != null;
+			++i)
 		{
 			Widget background = children[TAGTAB_CHILD_OFFSET + i * 2];
 			background.setOriginalY(y);
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java
index 59359aa4e..63272535f 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java
@@ -38,7 +38,6 @@ import net.runelite.api.ItemID;
 import net.runelite.client.config.ConfigManager;
 import static net.runelite.client.plugins.banktags.BankTagsPlugin.CONFIG_GROUP;
 import static net.runelite.client.plugins.banktags.BankTagsPlugin.TAG_ICON_PREFIX;
-import static net.runelite.client.plugins.banktags.BankTagsPlugin.TAG_LAYOUT_PREFIX;
 import static net.runelite.client.plugins.banktags.BankTagsPlugin.TAG_TABS_CONFIG;
 import net.runelite.client.util.Text;
 import org.apache.commons.lang3.math.NumberUtils;
@@ -61,10 +60,6 @@ public class TabManager
 		if (!contains(tagTab.getTag()))
 		{
 			tabs.add(tagTab);
-			if (tagTab.hasLayout())
-			{
-				tagTab.getLayout().dirty = true;
-			}
 		}
 	}
 
@@ -94,19 +89,6 @@ public class TabManager
 			String item = configManager.getConfiguration(CONFIG_GROUP, TAG_ICON_PREFIX + tag);
 			int itemid = NumberUtils.toInt(item, ItemID.SPADE);
 			tagTab = new TagTab(itemid, tag);
-
-			String layoutStr = configManager.getConfiguration(CONFIG_GROUP, TAG_LAYOUT_PREFIX + tag);
-			if (layoutStr != null)
-			{
-				List<String> layoutList = Text.fromCSV(layoutStr);
-				int[] layout = new int[layoutList.size()];
-				for (int i = 0; i < layoutList.size(); ++i)
-				{
-					layout[i] = Integer.parseInt(layoutList.get(i));
-				}
-
-				tagTab.setLayout(new Layout(layout));
-			}
 		}
 
 		return tagTab;
@@ -115,19 +97,6 @@ public class TabManager
 	private void save(TagTab tab)
 	{
 		setIcon(tab.getTag(), tab.getIconItemId());
-
-		if (tab.hasLayout())
-		{
-			Layout layout = tab.getLayout();
-			if (layout.dirty)
-			{
-				setLayout(tab.getTag(), layout.getLayout());
-			}
-		}
-		else
-		{
-			removeLayout(tab.getTag());
-		}
 	}
 
 	void swap(String tagToMove, String tagDestination)
@@ -160,7 +129,6 @@ public class TabManager
 		{
 			tabs.remove(tagTab);
 			removeIcon(tag);
-			removeLayout(tag);
 		}
 	}
 
@@ -185,25 +153,6 @@ public class TabManager
 		configManager.setConfiguration(CONFIG_GROUP, TAG_ICON_PREFIX + Text.standardize(tag), itemId);
 	}
 
-	private void removeLayout(String tag)
-	{
-		configManager.unsetConfiguration(CONFIG_GROUP, TAG_LAYOUT_PREFIX + Text.standardize(tag));
-	}
-
-	private void setLayout(String tag, int[] layout)
-	{
-		StringBuilder sb = new StringBuilder(layout.length * 5);
-		for (int i = 0; i < layout.length; ++i)
-		{
-			if (i > 0)
-			{
-				sb.append(',');
-			}
-			sb.append(layout[i]);
-		}
-		configManager.setConfiguration(CONFIG_GROUP, TAG_LAYOUT_PREFIX + Text.standardize(tag), sb.toString());
-	}
-
 	int size()
 	{
 		return tabs.size();
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java
index f85af47ba..7755924a3 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java
@@ -30,7 +30,7 @@ import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
 
 /**
- * A bank tag tab.
+ * A bank tag tab. This represents one of the tabs on the left hand side of the bank.
  *
  * @see TabManager#add(TagTab)
  * @see TabManager#remove(String)
@@ -42,16 +42,10 @@ public class TagTab
 {
 	private String tag;
 	private int iconItemId;
-	private Layout layout;
 
 	TagTab(int iconItemId, String tag)
 	{
 		this.iconItemId = iconItemId;
 		this.tag = tag;
 	}
-
-	public boolean hasLayout()
-	{
-		return layout != null;
-	}
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsConfig.java
index 4ed53b575..ed389c593 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsConfig.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsConfig.java
@@ -189,6 +189,28 @@ public interface ChatCommandsConfig extends Config
 		return true;
 	}
 
+	@ConfigItem(
+		position = 14,
+		keyName = "ca",
+		name = "CA Command",
+		description = "Configures whether the Combat Achievements command is enabled<br> !ca"
+	)
+	default boolean ca()
+	{
+		return true;
+	}
+
+	@ConfigItem(
+		position = 15,
+		keyName = "clog",
+		name = "CLOG Command",
+		description = "Configures whether the Collection Log command is enabled<br> !clog"
+	)
+	default boolean clog()
+	{
+		return true;
+	}
+
 	@ConfigItem(
 		position = 20,
 		keyName = "clearSingleWord",
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java
index 178e83e96..88596b367 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java
@@ -142,6 +142,8 @@ public class ChatCommandsPlugin extends Plugin
 	private static final String LEAGUE_POINTS_COMMAND = "!lp";
 	private static final String SOUL_WARS_ZEAL_COMMAND = "!sw";
 	private static final String PET_LIST_COMMAND = "!pets";
+	private static final String CA_COMMAND = "!ca";
+	private static final String CLOG_COMMAND = "!clog";
 
 	@VisibleForTesting
 	static final int ADV_LOG_EXPLOITS_TEXT_INDEX = 1;
@@ -223,6 +225,8 @@ public class ChatCommandsPlugin extends Plugin
 		chatCommandManager.registerCommandAsync(DUEL_ARENA_COMMAND, this::duelArenaLookup, this::duelArenaSubmit);
 		chatCommandManager.registerCommandAsync(SOUL_WARS_ZEAL_COMMAND, this::soulWarsZealLookup);
 		chatCommandManager.registerCommandAsync(PET_LIST_COMMAND, this::petListLookup, this::petListSubmit);
+		chatCommandManager.registerCommandAsync(CA_COMMAND, this::caLookup, this::caSubmit);
+		chatCommandManager.registerCommandAsync(CLOG_COMMAND, this::clogLookup, this::clogSubmit);
 
 		clientThread.invoke(() ->
 		{
@@ -261,6 +265,8 @@ public class ChatCommandsPlugin extends Plugin
 		chatCommandManager.unregisterCommand(DUEL_ARENA_COMMAND);
 		chatCommandManager.unregisterCommand(SOUL_WARS_ZEAL_COMMAND);
 		chatCommandManager.unregisterCommand(PET_LIST_COMMAND);
+		chatCommandManager.unregisterCommand(CA_COMMAND);
+		chatCommandManager.unregisterCommand(CLOG_COMMAND);
 	}
 
 	@Provides
@@ -1390,6 +1396,145 @@ public class ChatCommandsPlugin extends Plugin
 		return true;
 	}
 
+	private void caLookup(ChatMessage chatMessage, String message)
+	{
+		if (!config.ca())
+		{
+			return;
+		}
+
+		ChatMessageType type = chatMessage.getType();
+
+		final String player;
+		if (type.equals(ChatMessageType.PRIVATECHATOUT))
+		{
+			player = client.getLocalPlayer().getName();
+		}
+		else
+		{
+			player = Text.sanitize(chatMessage.getName());
+		}
+
+		int num;
+		try
+		{
+			num = chatClient.getKc(player, "Combat Achievements");
+		}
+		catch (IOException ex)
+		{
+			log.debug("unable to lookup combat achievements");
+			return;
+		}
+
+		String response = new ChatMessageBuilder()
+			.append(ChatColorType.NORMAL)
+			.append("Combat Achievements: ")
+			.append(ChatColorType.HIGHLIGHT)
+			.append(Integer.toString(num))
+			.build();
+
+		log.debug("Setting response {}", response);
+		final MessageNode messageNode = chatMessage.getMessageNode();
+		messageNode.setRuneLiteFormatMessage(response);
+		client.refreshChat();
+	}
+
+	private boolean caSubmit(ChatInput chatInput, String value)
+	{
+		final int tasks = client.getVarbitValue(Varbits.COMBAT_TASK_EASY) +
+			client.getVarbitValue(Varbits.COMBAT_TASK_MEDIUM) +
+			client.getVarbitValue(Varbits.COMBAT_TASK_HARD) +
+			client.getVarbitValue(Varbits.COMBAT_TASK_ELITE) +
+			client.getVarbitValue(Varbits.COMBAT_TASK_MASTER) +
+			client.getVarbitValue(Varbits.COMBAT_TASK_GRANDMASTER);
+		final String playerName = client.getLocalPlayer().getName();
+
+		executor.execute(() ->
+		{
+			try
+			{
+				chatClient.submitKc(playerName, "Combat Achievements", tasks);
+			}
+			catch (Exception ex)
+			{
+				log.warn("unable to submit combat achievements", ex);
+			}
+			finally
+			{
+				chatInput.resume();
+			}
+		});
+
+		return true;
+	}
+
+	private void clogLookup(ChatMessage chatMessage, String message)
+	{
+		if (!config.clog())
+		{
+			return;
+		}
+
+		ChatMessageType type = chatMessage.getType();
+
+		final String player;
+		if (type.equals(ChatMessageType.PRIVATECHATOUT))
+		{
+			player = client.getLocalPlayer().getName();
+		}
+		else
+		{
+			player = Text.sanitize(chatMessage.getName());
+		}
+
+		int num;
+		try
+		{
+			num = chatClient.getKc(player, "Collections Logged");
+		}
+		catch (IOException ex)
+		{
+			log.debug("unable to lookup clog");
+			return;
+		}
+
+		String response = new ChatMessageBuilder()
+			.append(ChatColorType.NORMAL)
+			.append("Collections Logged: ")
+			.append(ChatColorType.HIGHLIGHT)
+			.append(Integer.toString(num))
+			.build();
+
+		log.debug("Setting response {}", response);
+		final MessageNode messageNode = chatMessage.getMessageNode();
+		messageNode.setRuneLiteFormatMessage(response);
+		client.refreshChat();
+	}
+
+	private boolean clogSubmit(ChatInput chatInput, String value)
+	{
+		final int clog = client.getVarpValue(VarPlayer.CLOG_LOGGED);
+		final String playerName = client.getLocalPlayer().getName();
+
+		executor.execute(() ->
+		{
+			try
+			{
+				chatClient.submitKc(playerName, "Collections Logged", clog);
+			}
+			catch (Exception ex)
+			{
+				log.warn("unable to submit clog", ex);
+			}
+			finally
+			{
+				chatInput.resume();
+			}
+		});
+
+		return true;
+	}
+
 	/**
 	 * Looks up the item price and changes the original message to the
 	 * response.
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 3d5a1a269..2e708e1ed 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
@@ -35,6 +35,7 @@ import javax.annotation.Nullable;
 import lombok.AccessLevel;
 import lombok.Getter;
 import net.runelite.api.Client;
+import net.runelite.api.EquipmentInventorySlot;
 import static net.runelite.api.EquipmentInventorySlot.AMMO;
 import static net.runelite.api.EquipmentInventorySlot.AMULET;
 import static net.runelite.api.EquipmentInventorySlot.BODY;
@@ -125,9 +126,9 @@ public class EmoteClue extends ClueScroll implements LocationClueScroll
 		new EmoteClue("Headbang in the Fight Arena pub. Equip a pirate bandana, a dragonstone necklace and and a magic longbow.", "Fight Arena pub", OUTSIDE_THE_BAR_BY_THE_FIGHT_ARENA, new WorldPoint(2568, 3149, 0), HEADBANG, any("Any pirate bandana", item(PIRATE_BANDANA), item(PIRATE_BANDANA_7124), item(PIRATE_BANDANA_7130), item(PIRATE_BANDANA_7136)), item(DRAGON_NECKLACE), item(MAGIC_LONGBOW)),
 		new EmoteClue("Do a jig at the barrows chest. Beware of double agents! Equip any full barrows set.", "Barrows chest", BARROWS_CHEST, new WorldPoint(3551, 9694, 0), DOUBLE_AGENT_141, JIG, any("Any full barrows set", all(any("Ahrim's hood", item(AHRIMS_HOOD), range(AHRIMS_HOOD_100, AHRIMS_HOOD_0)), any("Ahrim's staff", item(AHRIMS_STAFF), range(AHRIMS_STAFF_100, AHRIMS_STAFF_0)), any("Ahrim's robetop", item(AHRIMS_ROBETOP), range(AHRIMS_ROBETOP_100, AHRIMS_ROBETOP_0)), any("Ahrim's robeskirt", item(AHRIMS_ROBESKIRT), range(AHRIMS_ROBESKIRT_100, AHRIMS_ROBESKIRT_0))), all(any("Dharok's helm", item(DHAROKS_HELM), range(DHAROKS_HELM_100, DHAROKS_HELM_0)), any("Dharok's greataxe", item(DHAROKS_GREATAXE), range(DHAROKS_GREATAXE_100, DHAROKS_GREATAXE_0)), any("Dharok's platebody", item(DHAROKS_PLATEBODY), range(DHAROKS_PLATEBODY_100, DHAROKS_PLATEBODY_0)), any("Dharok's platelegs", item(DHAROKS_PLATELEGS), range(DHAROKS_PLATELEGS_100, DHAROKS_PLATELEGS_0))), all(any("Guthan's helm", item(GUTHANS_HELM), range(GUTHANS_HELM_100, GUTHANS_HELM_0)), any("Guthan's warspear", item(GUTHANS_WARSPEAR), range(GUTHANS_WARSPEAR_100, GUTHANS_WARSPEAR_0)), any("Guthan's platebody", item(GUTHANS_PLATEBODY), range(GUTHANS_PLATEBODY_100, GUTHANS_PLATEBODY_0)), any("Guthan's chainskirt", item(GUTHANS_CHAINSKIRT), range(GUTHANS_CHAINSKIRT_100, GUTHANS_CHAINSKIRT_0))), all(any("Karil's coif", item(KARILS_COIF), range(KARILS_COIF_100, KARILS_COIF_0)), any("Karil's crossbow", item(KARILS_CROSSBOW), range(KARILS_CROSSBOW_100, KARILS_CROSSBOW_0)), any("Karil's leathertop", item(KARILS_LEATHERTOP), range(KARILS_LEATHERTOP_100, KARILS_LEATHERTOP_0)), any("Karil's leatherskirt", item(KARILS_LEATHERSKIRT), range(KARILS_LEATHERSKIRT_100, KARILS_LEATHERSKIRT_0))), all(any("Torag's helm", item(TORAGS_HELM), range(TORAGS_HELM_100, TORAGS_HELM_0)), any("Torag's hammers", item(TORAGS_HAMMERS), range(TORAGS_HAMMERS_100, TORAGS_HAMMERS_0)), any("Torag's platebody", item(TORAGS_PLATEBODY), range(TORAGS_PLATEBODY_100, TORAGS_PLATEBODY_0)), any("Torag's platelegs", item(TORAGS_PLATELEGS), range(TORAGS_PLATELEGS_100, TORAGS_PLATELEGS_0))), all(any("Verac's helm", item(VERACS_HELM), range(VERACS_HELM_100, VERACS_HELM_0)), any("Verac's flail", item(VERACS_FLAIL), range(VERACS_FLAIL_100, VERACS_FLAIL_0)), any("Verac's brassard", item(VERACS_BRASSARD), range(VERACS_BRASSARD_100, VERACS_BRASSARD_0)), any("Verac's plateskirt", item(VERACS_PLATESKIRT), range(VERACS_PLATESKIRT_100, VERACS_PLATESKIRT_0))))),
 		new EmoteClue("Jig at Jiggig. Beware of double agents! Equip a Rune spear, rune platelegs and any rune heraldic helm.", "Jiggig", IN_THE_MIDDLE_OF_JIGGIG, new WorldPoint(2477, 3047, 0), DOUBLE_AGENT_108, JIG, range("Any rune heraldic helm", RUNE_HELM_H1, RUNE_HELM_H5), item(RUNE_SPEAR), item(RUNE_PLATELEGS)),
-		new EmoteClue("Cheer at the games room. Have nothing equipped at all when you do.", "Burthorpe Games Room", null, new WorldPoint(2207, 4952, 0), CHEER, emptySlot("Nothing at all", HEAD, CAPE, AMULET, WEAPON, BODY, SHIELD, LEGS, GLOVES, BOOTS, RING, AMMO)),
-		new EmoteClue("Panic on the pier where you catch the Fishing trawler. Have nothing equipped at all when you do.", "Fishing trawler", null, new WorldPoint(2676, 3169, 0), PANIC, emptySlot("Nothing at all", HEAD, CAPE, AMULET, WEAPON, BODY, SHIELD, LEGS, GLOVES, BOOTS, RING, AMMO)),
-		new EmoteClue("Panic in the heart of the Haunted Woods. Beware of double agents! Have no items equipped when you do.", "Haunted Woods (ALQ)", null, new WorldPoint(3611, 3492, 0), DOUBLE_AGENT_108, PANIC, emptySlot("Nothing at all", HEAD, CAPE, AMULET, WEAPON, BODY, SHIELD, LEGS, GLOVES, BOOTS, RING, AMMO)),
+		new EmoteClue("Cheer at the games room. Have nothing equipped at all when you do.", "Burthorpe Games Room", null, new WorldPoint(2207, 4952, 0), CHEER, emptySlot("Nothing at all", EquipmentInventorySlot.values())),
+		new EmoteClue("Panic on the pier where you catch the Fishing trawler. Have nothing equipped at all when you do.", "Fishing trawler", null, new WorldPoint(2676, 3169, 0), PANIC, emptySlot("Nothing at all", EquipmentInventorySlot.values())),
+		new EmoteClue("Panic in the heart of the Haunted Woods. Beware of double agents! Have no items equipped when you do.", "Haunted Woods (ALQ)", null, new WorldPoint(3611, 3492, 0), DOUBLE_AGENT_108, PANIC, emptySlot("Nothing at all", EquipmentInventorySlot.values())),
 		new EmoteClue("Show your anger towards the Statue of Saradomin in Ellamaria's garden. Beware of double agents! Equip a zamorak godsword.", "Varrock Castle", BY_THE_BEAR_CAGE_IN_VARROCK_PALACE_GARDENS, new WorldPoint(3230, 3478, 0), DOUBLE_AGENT_141, ANGRY, any("Zamorak godsword", item(ZAMORAK_GODSWORD), item(ZAMORAK_GODSWORD_OR))),
 		new EmoteClue("Show your anger at the Wise old man. Beware of double agents! Equip an abyssal whip, a legend's cape and some spined chaps.", "Draynor Village", BEHIND_MISS_SCHISM_IN_DRAYNOR_VILLAGE, new WorldPoint(3088, 3254, 0), DOUBLE_AGENT_141, ANGRY, any("Abyssal whip", item(ABYSSAL_WHIP), item(VOLCANIC_ABYSSAL_WHIP), item(FROZEN_ABYSSAL_WHIP), item(ABYSSAL_WHIP_OR), item(ABYSSAL_TENTACLE), item(ABYSSAL_TENTACLE_OR)), item(CAPE_OF_LEGENDS), item(SPINED_CHAPS)),
 		new EmoteClue("Beckon by a collection of crystalline maple trees. Beware of double agents! Equip Bryophyta's staff and a nature tiara.", "North of Prifddinas", CRYSTALLINE_MAPLE_TREES, new WorldPoint(2211, 3427, 0), DOUBLE_AGENT_141, BECKON, range("Bryophyta's staff", BRYOPHYTAS_STAFF_UNCHARGED, BRYOPHYTAS_STAFF), item(NATURE_TIARA)),
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 a6c935603..1b49f18a9 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
@@ -482,8 +482,8 @@ public class DevToolsPlugin extends Plugin
 				player.getPlayerComposition().getEquipmentIds()[KitType.ARMS.getIndex()] = -1;
 				player.getPlayerComposition().getEquipmentIds()[KitType.LEGS.getIndex()] = ItemID.GHOSTLY_ROBE_6108 + PlayerComposition.ITEM_OFFSET;
 				player.getPlayerComposition().getEquipmentIds()[KitType.HAIR.getIndex()] = -1;
-				player.getPlayerComposition().getEquipmentIds()[KitType.HANDS.getIndex()] = ItemID.GHOSTLY_GLOVES;
-				player.getPlayerComposition().getEquipmentIds()[KitType.BOOTS.getIndex()] = ItemID.GHOSTLY_BOOTS;
+				player.getPlayerComposition().getEquipmentIds()[KitType.HANDS.getIndex()] = ItemID.GHOSTLY_GLOVES + PlayerComposition.ITEM_OFFSET;
+				player.getPlayerComposition().getEquipmentIds()[KitType.BOOTS.getIndex()] = ItemID.GHOSTLY_BOOTS + PlayerComposition.ITEM_OFFSET;
 				player.getPlayerComposition().setHash();
 				break;
 			}
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 2c089ba73..06b1f7e21 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
@@ -136,9 +136,8 @@ public class SceneOverlay extends Overlay
 			boolean first = true;
 			for (int z = startZ; z <= endZ; z += Constants.CHUNK_SIZE)
 			{
-				Point p = Perspective.localToCanvas(client,
-					x << Perspective.LOCAL_COORD_BITS, z << Perspective.LOCAL_COORD_BITS,
-					client.getPlane());
+				LocalPoint lp = new LocalPoint(x << Perspective.LOCAL_COORD_BITS, z << Perspective.LOCAL_COORD_BITS);
+				Point p = Perspective.localToCanvas(client, lp, client.getPlane());
 				if (p != null)
 				{
 					if (first)
@@ -158,9 +157,8 @@ public class SceneOverlay extends Overlay
 			boolean first = true;
 			for (int x = startX; x <= endX; x += Constants.CHUNK_SIZE)
 			{
-				Point p = Perspective.localToCanvas(client,
-					x << Perspective.LOCAL_COORD_BITS, z << Perspective.LOCAL_COORD_BITS,
-					client.getPlane());
+				LocalPoint lp = new LocalPoint(x << Perspective.LOCAL_COORD_BITS, z << Perspective.LOCAL_COORD_BITS);
+				Point p = Perspective.localToCanvas(client, lp, client.getPlane());
 				if (p != null)
 				{
 					if (first)
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java
index b5f9d3bc9..cdca494a9 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java
@@ -93,6 +93,7 @@ import net.runelite.client.events.ConfigChanged;
 import net.runelite.client.events.SessionClose;
 import net.runelite.client.events.SessionOpen;
 import net.runelite.client.game.ItemManager;
+import net.runelite.client.game.ItemStats;
 import net.runelite.client.input.KeyManager;
 import net.runelite.client.input.MouseManager;
 import net.runelite.client.plugins.Plugin;
@@ -106,7 +107,6 @@ import net.runelite.client.util.OSType;
 import net.runelite.client.util.QuantityFormatter;
 import net.runelite.client.util.Text;
 import net.runelite.http.api.ge.GrandExchangeTrade;
-import net.runelite.http.api.item.ItemStats;
 import net.runelite.http.api.worlds.WorldType;
 import org.apache.commons.lang3.time.DurationFormatUtils;
 
@@ -858,7 +858,7 @@ public class GrandExchangePlugin extends Plugin
 
 		if (buy && config.enableGELimits())
 		{
-			final ItemStats itemStats = itemManager.getItemStats(itemId, false);
+			final ItemStats itemStats = itemManager.getItemStats(itemId);
 
 			// If we have item buy limit, append it
 			if (itemStats != null && itemStats.getGeLimit() > 0)
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java
index 2a4e7c5dd..dfa96efc3 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java
@@ -43,12 +43,12 @@ import net.runelite.api.ItemComposition;
 import net.runelite.client.callback.ClientThread;
 import net.runelite.client.config.RuneLiteConfig;
 import net.runelite.client.game.ItemManager;
+import net.runelite.client.game.ItemStats;
 import net.runelite.client.ui.ColorScheme;
 import net.runelite.client.ui.components.IconTextField;
 import net.runelite.client.ui.components.PluginErrorPanel;
 import net.runelite.client.util.AsyncBufferedImage;
 import net.runelite.http.api.item.ItemPrice;
-import net.runelite.http.api.item.ItemStats;
 
 /**
  * This panel holds the search section of the Grand Exchange Plugin.
@@ -215,7 +215,7 @@ class GrandExchangeSearchPanel extends JPanel
 				break;
 			}
 
-			ItemStats itemStats = itemManager.getItemStats(itemId, false);
+			ItemStats itemStats = itemManager.getItemStats(itemId);
 
 			int itemPrice = useActivelyTradedPrice ? itemManager.getWikiPrice(item) : item.getPrice();
 			int itemLimit = itemStats != null ? itemStats.getGeLimit() : 0;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java
index 244423082..cb5aaf3c4 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java
@@ -121,7 +121,7 @@ public class HiscorePanel extends PluginPanel
 
 	private static final HiscoreEndpoint[] ENDPOINTS = {
 		HiscoreEndpoint.NORMAL, HiscoreEndpoint.IRONMAN, HiscoreEndpoint.HARDCORE_IRONMAN, HiscoreEndpoint.ULTIMATE_IRONMAN,
-		HiscoreEndpoint.DEADMAN, HiscoreEndpoint.PURE, HiscoreEndpoint.LEVEL_3_SKILLER, HiscoreEndpoint.TOURNAMENT
+		HiscoreEndpoint.DEADMAN, HiscoreEndpoint.PURE, HiscoreEndpoint.LEVEL_3_SKILLER, HiscoreEndpoint.LEAGUE
 	};
 
 	private final HiscorePlugin plugin;
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 eab1a0a39..d1aac0702 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
@@ -187,6 +187,9 @@ public class ItemStatChanges
 		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);
+		add(combo(food(1), boost(MAGIC, 1), boost(MINING, 1), new BoostedStatBoost(ATTACK, false, perc(.05, -3)), new BoostedStatBoost(DEFENCE, false, perc(.05, -3))), STEAMFORGE_BREW);
+		add(combo(food(16), boost(HUNTER, 1), dec(ATTACK, 5), dec(SLAYER, 1)), BLACKBIRD_RED);
+		add(combo(food(16), boost(RUNECRAFT, 1), dec(ATTACK, 5), dec(FIREMAKING, 1)), METZTONALLI_WHITE);
 
 		// Sq'irk Juice
 		add(heal(RUN_ENERGY, 5), WINTER_SQIRKJUICE);
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatOverlay.java
index 117744335..d0c1e5171 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatOverlay.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatOverlay.java
@@ -40,7 +40,9 @@ import net.runelite.api.widgets.ComponentID;
 import net.runelite.api.widgets.InterfaceID;
 import net.runelite.api.widgets.Widget;
 import net.runelite.api.widgets.WidgetUtil;
+import net.runelite.client.game.ItemEquipmentStats;
 import net.runelite.client.game.ItemManager;
+import net.runelite.client.game.ItemStats;
 import net.runelite.client.plugins.itemstats.potions.PotionDuration;
 import net.runelite.client.ui.JagexColors;
 import net.runelite.client.ui.overlay.Overlay;
@@ -48,8 +50,6 @@ import net.runelite.client.ui.overlay.tooltip.Tooltip;
 import net.runelite.client.ui.overlay.tooltip.TooltipManager;
 import net.runelite.client.util.ColorUtil;
 import net.runelite.client.util.QuantityFormatter;
-import net.runelite.http.api.item.ItemEquipmentStats;
-import net.runelite.http.api.item.ItemStats;
 import org.apache.commons.lang3.time.DurationFormatUtils;
 
 public class ItemStatOverlay extends Overlay
@@ -188,7 +188,7 @@ public class ItemStatOverlay extends Overlay
 
 		if (config.equipmentStats())
 		{
-			final ItemStats stats = itemManager.getItemStats(itemId, false);
+			final ItemStats stats = itemManager.getItemStats(itemId);
 
 			if (stats != null)
 			{
@@ -275,7 +275,7 @@ public class ItemStatOverlay extends Overlay
 	private ItemStats getItemStatsFromContainer(ItemContainer container, int slotID)
 	{
 		final Item item = container.getItem(slotID);
-		return item != null ? itemManager.getItemStats(item.getId(), false) : null;
+		return item != null ? itemManager.getItemStats(item.getId()) : null;
 	}
 
 	@VisibleForTesting
@@ -303,7 +303,7 @@ public class ItemStatOverlay extends Overlay
 					{
 						// Account for speed change when two handed weapon gets removed
 						// shield - (2h - unarmed) == shield - 2h + unarmed
-						other = otherEquip.isTwoHanded() ? other.subtract(UNARMED) : null;
+						other = otherEquip.isTwoHanded() ? subtract(other, UNARMED) : null;
 					}
 				}
 			}
@@ -323,7 +323,7 @@ public class ItemStatOverlay extends Overlay
 			}
 		}
 
-		final ItemStats subtracted = s.subtract(other).subtract(offHand);
+		final ItemStats subtracted = subtract(subtract(s, other), offHand);
 		final ItemEquipmentStats e = subtracted.getEquipment();
 
 		final StringBuilder b = new StringBuilder();
@@ -370,6 +370,49 @@ public class ItemStatOverlay extends Overlay
 		return b.toString();
 	}
 
+	private static ItemStats subtract(ItemStats one, ItemStats two)
+	{
+		if (two == null)
+		{
+			return one;
+		}
+
+		final double newWeight = one.getWeight() - two.getWeight();
+		final ItemEquipmentStats newEquipment;
+
+		if (two.getEquipment() != null)
+		{
+			final ItemEquipmentStats equipment = one.getEquipment() != null
+				? one.getEquipment()
+				: ItemEquipmentStats.builder().build();
+
+			newEquipment = ItemEquipmentStats.builder()
+				.slot(equipment.getSlot())
+				.astab(equipment.getAstab() - two.getEquipment().getAstab())
+				.aslash(equipment.getAslash() - two.getEquipment().getAslash())
+				.acrush(equipment.getAcrush() - two.getEquipment().getAcrush())
+				.amagic(equipment.getAmagic() - two.getEquipment().getAmagic())
+				.arange(equipment.getArange() - two.getEquipment().getArange())
+				.dstab(equipment.getDstab() - two.getEquipment().getDstab())
+				.dslash(equipment.getDslash() - two.getEquipment().getDslash())
+				.dcrush(equipment.getDcrush() - two.getEquipment().getDcrush())
+				.dmagic(equipment.getDmagic() - two.getEquipment().getDmagic())
+				.drange(equipment.getDrange() - two.getEquipment().getDrange())
+				.str(equipment.getStr() - two.getEquipment().getStr())
+				.rstr(equipment.getRstr() - two.getEquipment().getRstr())
+				.mdmg(equipment.getMdmg() - two.getEquipment().getMdmg())
+				.prayer(equipment.getPrayer() - two.getEquipment().getPrayer())
+				.aspeed(equipment.getAspeed() - two.getEquipment().getAspeed())
+				.build();
+		}
+		else
+		{
+			newEquipment = one.getEquipment();
+		}
+
+		return new ItemStats(one.isEquipable(), newWeight, 0, newEquipment);
+	}
+
 	private String buildStatChangeString(StatChange c)
 	{
 		StringBuilder b = new StringBuilder();
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatPlugin.java
index a7b8fbad2..4a9d7fad0 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatPlugin.java
@@ -56,15 +56,15 @@ 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.game.ItemEquipmentStats;
 import net.runelite.client.game.ItemManager;
+import net.runelite.client.game.ItemStats;
 import net.runelite.client.plugins.Plugin;
 import net.runelite.client.plugins.PluginDescriptor;
 import net.runelite.client.ui.FontManager;
 import net.runelite.client.ui.JagexColors;
 import net.runelite.client.ui.overlay.OverlayManager;
 import net.runelite.client.util.QuantityFormatter;
-import net.runelite.http.api.item.ItemEquipmentStats;
-import net.runelite.http.api.item.ItemStats;
 
 @PluginDescriptor(
 	name = "Item Stats",
@@ -166,7 +166,7 @@ public class ItemStatPlugin extends Plugin
 
 	private void createItemInformation(int id)
 	{
-		final ItemStats itemStats = itemManager.getItemStats(id, false);
+		final ItemStats itemStats = itemManager.getItemStats(id);
 
 		if (itemStats == null || !itemStats.isEquipable())
 		{
@@ -314,14 +314,14 @@ public class ItemStatPlugin extends Plugin
 
 		yPos += TEXT_HEIGHT + 8;
 
-		final Map<String, Integer> miscStats = ImmutableMap.of(
+		final Map<String, Object> miscStats = ImmutableMap.of(
 			"Strength", equipmentStats.getStr(),
 			"Ranged Strength", equipmentStats.getRstr(),
 			"Magic Damage", equipmentStats.getMdmg(),
 			"Prayer Bonus", equipmentStats.getPrayer()
 		);
 
-		for (final Map.Entry<String, Integer> miscStat : miscStats.entrySet())
+		for (final Map.Entry<String, Object> miscStat : miscStats.entrySet())
 		{
 			final String name = miscStat.getKey();
 			final String value = miscStat.getValue().toString();
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java
index 317a5a0e4..a86d85b8a 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java
@@ -274,6 +274,9 @@ public class LootTrackerPlugin extends Plugin
 	private static final String ORE_PACK_VM_EVENT = "Ore Pack (Volcanic Mine)";
 
 	private static final String WINTERTODT_SUPPLY_CRATE_EVENT = "Supply crate (Wintertodt)";
+	private static final String WINTERTODT_REWARD_CART_EVENT = "Reward cart (Wintertodt)";
+	private static final String WINTERTODT_LOOT_STRING = "You found some loot: ";
+	private static final int WINTERTODT_REGION = 6461;
 
 	private static final String BAG_FULL_OF_GEMS_PERCY_EVENT = "Bag full of gems (Percy)";
 	private static final String BAG_FULL_OF_GEMS_BELONA_EVENT = "Bag full of gems (Belona)";
@@ -1154,6 +1157,12 @@ public class LootTrackerPlugin extends Plugin
 			return;
 		}
 
+		if (regionID == WINTERTODT_REGION && message.startsWith(WINTERTODT_LOOT_STRING))
+		{
+			onInvChange(collectInvItems(LootRecordType.EVENT, WINTERTODT_REWARD_CART_EVENT, client.getBoostedSkillLevel(Skill.FIREMAKING)));
+			return;
+		}
+
 		if (message.equals(IMPLING_CATCH_MESSAGE))
 		{
 			onInvChange(collectInvItems(LootRecordType.EVENT, client.getLocalPlayer().getInteracting().getName()));
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerPlugin.java
index fb2ed7643..729fcfedb 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerPlugin.java
@@ -51,12 +51,12 @@ import net.runelite.client.eventbus.EventBus;
 import net.runelite.client.eventbus.Subscribe;
 import net.runelite.client.events.ConfigChanged;
 import net.runelite.client.game.ItemManager;
+import net.runelite.client.game.ItemStats;
 import net.runelite.client.game.SpriteManager;
 import net.runelite.client.plugins.Plugin;
 import net.runelite.client.plugins.PluginDescriptor;
 import net.runelite.client.ui.overlay.OverlayManager;
 import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
-import net.runelite.http.api.item.ItemStats;
 
 @PluginDescriptor(
 	name = "Prayer",
@@ -238,7 +238,7 @@ public class PrayerPlugin extends Plugin
 		int total = 0;
 		for (Item item : items)
 		{
-			ItemStats is = itemManager.getItemStats(item.getId(), false);
+			ItemStats is = itemManager.getItemStats(item.getId());
 			if (is != null && is.getEquipment() != null)
 			{
 				total += is.getEquipment().getPrayer();
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 28c338696..b12fc4a75 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,7 +49,6 @@ 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.PreMapLoad;
 import net.runelite.client.callback.ClientThread;
@@ -140,7 +139,7 @@ public class RoofRemovalPlugin extends Plugin
 	@Subscribe
 	public void onPreMapLoad(PreMapLoad preMapLoad)
 	{
-		performRoofRemoval(preMapLoad.getWorldView(), preMapLoad.getScene());
+		performRoofRemoval(preMapLoad.getScene());
 	}
 
 	@Subscribe
@@ -202,9 +201,9 @@ public class RoofRemovalPlugin extends Plugin
 		}
 	}
 
-	private void performRoofRemoval(WorldView wv, Scene scene)
+	private void performRoofRemoval(Scene scene)
 	{
-		applyRoofOverrides(wv, scene);
+		applyRoofOverrides(scene);
 
 		Stopwatch sw = Stopwatch.createStarted();
 		scene.buildRoofs();
@@ -247,13 +246,13 @@ public class RoofRemovalPlugin extends Plugin
 		}
 	}
 
-	private void applyRoofOverrides(WorldView wv, Scene scene)
+	private void applyRoofOverrides(Scene scene)
 	{
 		Stopwatch sw = Stopwatch.createStarted();
 		boolean regionsHaveOverrides = false;
 
 		outer:
-		for (int regionID : wv.getMapRegions())
+		for (int regionID : scene.getMapRegions())
 		{
 			if (configOverrideRegions.contains(regionID))
 			{
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 f56a58e47..04ddba15a 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
@@ -91,7 +91,7 @@ public enum HerbloreAction implements ItemSkillAction
 	DWARF_WEED(ItemID.DWARF_WEED, 70, 13.8f),
 	RANGING_POTION_3(ItemID.RANGING_POTION3, 72, 162.5f),
 	AQUALUX_AMALGAM(ItemID.AQUALUX_AMALGAM, 72, 290),
-	WEAPON_POISON_PLUS(ItemID.WEAPON_POISON_5937, 73, 165),
+	WEAPON_POISON_PLUS(ItemID.WEAPON_POISON_5937, 73, 190),
 	DIVINE_RANGING_POTION_4(ItemID.DIVINE_RANGING_POTION4, 74, 2),
 	TORSTOL(ItemID.TORSTOL, 75, 15),
 	MEGALITE_LIQUID(ItemID.MEGALITE_LIQUID, 75, 315),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/PaymentTracker.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/PaymentTracker.java
index 95113c3f4..eca59b17c 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/PaymentTracker.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/PaymentTracker.java
@@ -53,6 +53,8 @@ import net.runelite.client.plugins.timetracking.TimeTrackingConfig;
 public class PaymentTracker
 {
 	private static final Set<String> PAYMENT_TEXT = ImmutableSet.of(
+		"That'll do nicely, sir. Leave it with me - I'll make sure<br>that patch grows for you.",
+		"That'll do nicely, madam. Leave it with me - I'll make<br>sure that patch grows for you.",
 		"That'll do nicely. Leave it with me - I'll make sure that<br>patch grows for you.",
 		"That'll do nicely, iknami. Leave it with me - I'll make<br>sure that patch grows for you."
 	);
diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/TooltipComponent.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/TooltipComponent.java
index cd917c38a..b18149497 100644
--- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/TooltipComponent.java
+++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/TooltipComponent.java
@@ -38,7 +38,7 @@ import net.runelite.api.IndexedSprite;
 @Setter
 public class TooltipComponent implements LayoutableRenderableEntity
 {
-	private static final Pattern BR = Pattern.compile("</br>");
+	private static final Pattern BR = Pattern.compile("</?br>");
 	private static final int OFFSET = 4;
 	private static final int MOD_ICON_WIDTH = 13; // they are generally 13px wide
 
diff --git a/runelite-client/src/main/resources/item_variations.json b/runelite-client/src/main/resources/item_variations.json
index 2a43036c3..d49e6a558 100644
--- a/runelite-client/src/main/resources/item_variations.json
+++ b/runelite-client/src/main/resources/item_variations.json
@@ -4639,6 +4639,10 @@
     6057,
     6058
   ],
+  "scarecrow": [
+    6059,
+    30236
+  ],
   "spirit tree": [
     6063,
     20635
@@ -8997,6 +9001,10 @@
     19659,
     19662
   ],
+  "arclight": [
+    19675,
+    30305
+  ],
   "clue nest": [
     19712,
     19714,
@@ -11169,15 +11177,18 @@
   ],
   "torva full helm": [
     26376,
-    26382
+    26382,
+    30302
   ],
   "torva platebody": [
     26378,
-    26384
+    26384,
+    30303
   ],
   "torva platelegs": [
     26380,
-    26386
+    26386,
+    30304
   ],
   "blood essence": [
     26390,
@@ -12340,5 +12351,152 @@
     30140,
     30143,
     30146
+  ],
+  "beige pumpkin": [
+    30162,
+    30169,
+    30170,
+    30171,
+    30172,
+    30173,
+    30174,
+    30175,
+    30176,
+    30177,
+    30237,
+    30239,
+    30241,
+    30242,
+    30243,
+    30244,
+    30245,
+    30246,
+    30247
+  ],
+  "white pumpkin": [
+    30163,
+    30178,
+    30179,
+    30180,
+    30181,
+    30182,
+    30183,
+    30184,
+    30185,
+    30186,
+    30248,
+    30249,
+    30250,
+    30251,
+    30252,
+    30253,
+    30254,
+    30255,
+    30256
+  ],
+  "yellow pumpkin": [
+    30164,
+    30187,
+    30188,
+    30189,
+    30190,
+    30191,
+    30192,
+    30193,
+    30194,
+    30195,
+    30257,
+    30258,
+    30259,
+    30260,
+    30261,
+    30262,
+    30263,
+    30264,
+    30265
+  ],
+  "orange pumpkin": [
+    30165,
+    30196,
+    30197,
+    30198,
+    30199,
+    30200,
+    30201,
+    30202,
+    30203,
+    30204,
+    30266,
+    30267,
+    30268,
+    30269,
+    30270,
+    30271,
+    30272,
+    30273,
+    30274
+  ],
+  "red pumpkin": [
+    30166,
+    30205,
+    30206,
+    30207,
+    30208,
+    30209,
+    30210,
+    30211,
+    30212,
+    30213,
+    30275,
+    30276,
+    30277,
+    30278,
+    30279,
+    30280,
+    30281,
+    30282,
+    30283
+  ],
+  "dark green pumpkin": [
+    30167,
+    30214,
+    30215,
+    30216,
+    30217,
+    30218,
+    30219,
+    30220,
+    30221,
+    30222,
+    30284,
+    30285,
+    30286,
+    30287,
+    30288,
+    30289,
+    30290,
+    30291,
+    30292
+  ],
+  "powder grey pumpkin": [
+    30168,
+    30223,
+    30224,
+    30225,
+    30226,
+    30227,
+    30228,
+    30229,
+    30230,
+    30231,
+    30293,
+    30294,
+    30295,
+    30296,
+    30297,
+    30298,
+    30299,
+    30300,
+    30301
   ]
 }
\ No newline at end of file
diff --git a/runelite-jshell/pom.xml b/runelite-jshell/pom.xml
index 61f866178..ec09e2887 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.41-SNAPSHOT</version>
+        <version>1.10.43-SNAPSHOT</version>
     </parent>
 
     <artifactId>jshell</artifactId>
diff --git a/runelite-maven-plugin/pom.xml b/runelite-maven-plugin/pom.xml
index a3cb2f0d7..d0f7bcff4 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.41-SNAPSHOT</version>
+		<version>1.10.43-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>runelite-maven-plugin</artifactId>


Test patch:
diff --git a/runelite-client/src/test/java/net/runelite/client/game/ItemStatsTest.java b/runelite-client/src/test/java/net/runelite/client/game/ItemStatsTest.java
new file mode 100644
index 000000000..2e83aba6b
--- /dev/null
+++ b/runelite-client/src/test/java/net/runelite/client/game/ItemStatsTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.game;
+
+import net.runelite.api.EquipmentInventorySlot;
+import org.junit.Test;
+
+public class ItemStatsTest
+{
+	private static final ItemStats WEAPON = new ItemStats(true, 0, 0,
+		ItemEquipmentStats.builder()
+			.slot(EquipmentInventorySlot.WEAPON.getSlotIdx())
+			.build());
+
+	@Test
+	public void testMigratedApiDoesNotThrow()
+	{
+		new ItemStats(true, 0, 0, null).toHttpApiFormat();
+		WEAPON.toHttpApiFormat();
+	}
+}
diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/itemstats/ItemStatOverlayTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/itemstats/ItemStatOverlayTest.java
index f9bc39ba8..d9c9d2fbb 100644
--- a/runelite-client/src/test/java/net/runelite/client/plugins/itemstats/ItemStatOverlayTest.java
+++ b/runelite-client/src/test/java/net/runelite/client/plugins/itemstats/ItemStatOverlayTest.java
@@ -33,10 +33,10 @@ import net.runelite.api.Client;
 import net.runelite.api.EquipmentInventorySlot;
 import net.runelite.api.InventoryID;
 import net.runelite.api.ItemContainer;
+import net.runelite.client.game.ItemEquipmentStats;
 import net.runelite.client.game.ItemManager;
+import net.runelite.client.game.ItemStats;
 import net.runelite.client.util.Text;
-import net.runelite.http.api.item.ItemEquipmentStats;
-import net.runelite.http.api.item.ItemStats;
 import org.apache.commons.lang3.StringUtils;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/prayer/PrayerPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/prayer/PrayerPluginTest.java
index 81499a18f..641907d96 100644
--- a/runelite-client/src/test/java/net/runelite/client/plugins/prayer/PrayerPluginTest.java
+++ b/runelite-client/src/test/java/net/runelite/client/plugins/prayer/PrayerPluginTest.java
@@ -38,16 +38,15 @@ import net.runelite.api.Skill;
 import net.runelite.api.events.ItemContainerChanged;
 import net.runelite.client.chat.ChatMessageManager;
 import net.runelite.client.config.ConfigManager;
+import net.runelite.client.game.ItemEquipmentStats;
 import net.runelite.client.game.ItemManager;
+import net.runelite.client.game.ItemStats;
 import net.runelite.client.ui.overlay.OverlayManager;
 import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
-import net.runelite.http.api.item.ItemEquipmentStats;
-import net.runelite.http.api.item.ItemStats;
 import static org.junit.Assert.assertEquals;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import org.mockito.Mock;
 import static org.mockito.Mockito.mock;
@@ -106,7 +105,7 @@ public class PrayerPluginTest
 	{
 		ItemContainer itemContainer = mock(ItemContainer.class);
 		when(itemContainer.getItems()).thenReturn(new Item[]{new Item(-1, 1)});
-		when(itemManager.getItemStats(anyInt(), anyBoolean())).thenReturn(HIGH_PRAYER_BONUS_WEAPON);
+		when(itemManager.getItemStats(anyInt())).thenReturn(HIGH_PRAYER_BONUS_WEAPON);
 
 		when(client.isPrayerActive(Prayer.PRESERVE)).thenReturn(true);
 		when(client.getBoostedSkillLevel(Skill.PRAYER)).thenReturn(99);
@@ -149,7 +148,7 @@ public class PrayerPluginTest
 	{
 		ItemContainer itemContainer = mock(ItemContainer.class);
 		when(itemContainer.getItems()).thenReturn(new Item[]{new Item(-1, 1)});
-		when(itemManager.getItemStats(anyInt(), anyBoolean())).thenReturn(HIGH_PRAYER_BONUS_WEAPON);
+		when(itemManager.getItemStats(anyInt())).thenReturn(HIGH_PRAYER_BONUS_WEAPON);
 
 		when(client.isPrayerActive(Prayer.PRESERVE)).thenReturn(true);
 		when(client.getBoostedSkillLevel(Skill.PRAYER)).thenReturn(99);