Repository: runelite/runelite
Pull request #17742 (2024-04-17 12:43:15)

timers: Wilderness boss timers off by 10 seconds
Wilderness boss timers are off since wilderness update

Adam-: https://secure.runescape.com/m=news/a=97/undead-pirates-colosseum-changes--more?oldschool=1 this blog says `Reduced respawn timers for most NPCs in the Wilderness (including bosses, but excluding Revenants) to 9 seconds.` which is 15 ticks. Is the blog wrong?
Hevaroo: > https://secure.runescape.com/m=news/a=97/undead-pirates-colosseum-changes--more?oldschool=1 this blog says `Reduced respawn timers for most NPCs in the Wilderness (including bosses, but excluding Revenants) to 9 seconds.` which is 15 ticks. Is the blog wrong?

Blog post is probably wrong. Respawn time for Spindel seems to be around 5-6 seconds, and not 9 as stated in the blog post.
DrJam: Might be 9 ticks in that case and they said seconds instead
evilgenious448: Did 100KC of Chaos fanatic last night and it was around 9 sec. So might be different for demi bosses vs cave bosses?
MarniRS: Blog stated they changed most of the respawn timers in wildy to 9 seconds but thats not the case in reality.

Link to blog;
[https://secure.runescape.com/m=news/undead-pirates-colosseum-changes--more?oldschool=1#_ga=2.2485048.902770785.1713177428-1711704415.1673028180]

I timed most bosses in wildy and these are the rates i've found (I forgot about crazy arch and chaos fanatic)
These changes are also visible on the wiki now.
The only boss that has a varying spawnrate is spindel which varied between 8-10 ticks so i took the average of 9. The rest are consistent.

artio 11 ticks
callisto 11 ticks 
calvarion 14 ticks
scorpia 14 ticks
spindel 9 ticks 
vetion 15 ticks
venenatis 10 ticks
chaos ele 14 ticks
lava dragon 14 ticks
DapperMickie: You can add new changes by pushing new commits to your branch that you've created. 
I think there should be a message explaining that on this pull request.  

Patch:
diff --git a/cache/pom.xml b/cache/pom.xml
index c5d52cd1b..f8ba078fa 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.28-SNAPSHOT</version>
+		<version>1.10.29-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>cache</artifactId>
diff --git a/pom.xml b/pom.xml
index 0740d88f0..4c3ecc48c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
 
 	<groupId>net.runelite</groupId>
 	<artifactId>runelite-parent</artifactId>
-	<version>1.10.28-SNAPSHOT</version>
+	<version>1.10.29-SNAPSHOT</version>
 	<packaging>pom</packaging>
 
 	<name>RuneLite</name>
diff --git a/runelite-api/pom.xml b/runelite-api/pom.xml
index 3c1f02a29..2d47de52f 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.28-SNAPSHOT</version>
+		<version>1.10.29-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>runelite-api</artifactId>
diff --git a/runelite-api/src/main/interfaces/interfaces.toml b/runelite-api/src/main/interfaces/interfaces.toml
index 9857d0492..5b6ff84e9 100644
--- a/runelite-api/src/main/interfaces/interfaces.toml
+++ b/runelite-api/src/main/interfaces/interfaces.toml
@@ -671,6 +671,10 @@ points_infobox=3
 [raiding_party]
 id=500
 
+[report_abuse]
+id=875
+parent=0
+
 [resizable_viewport]
 id=161
 resizable_viewport_old_school_box=15
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 bb5462df6..daecbfb1b 100644
--- a/runelite-api/src/main/java/net/runelite/api/ItemID.java
+++ b/runelite-api/src/main/java/net/runelite/api/ItemID.java
@@ -13970,19 +13970,21 @@ public final class ItemID
 	public static final int SUNFIRE_FANATIC_ARMOUR_SET = 29424;
 	public static final int HERBALISTS_NOTES = 29427;
 	public static final int CHEFS_NOTES = 29428;
-	public static final int YOLKLORE_OF_AYASTER = 29431;
 	public static final int BOOK_OF_EGG = 29433;
 	public static final int BOOK_OF_EGG_29435 = 29435;
 	public static final int EGG_PRIEST_ROBE = 29437;
 	public static final int EGG_PRIEST_ROBE_TOP = 29439;
 	public static final int EGG_PRIEST_NECKLACE = 29441;
 	public static final int EGG_PRIEST_MITRE = 29443;
-	public static final int EASTER_EGG_MINED = 29445;
-	public static final int EGGNAPPIN_SACK = 29446;
 	public static final int IMBUED_MAGE_ARENA_CAPE = 29448;
 	public static final int ZOMBIE_PIRATE_KEY = 29449;
 	public static final int TELEPORT_ANCHORING_SCROLL = 29455;
 	public static final int ADAMANT_SEEDS = 29458;
 	public static final int WILDERNESS_AGILITY_TICKET = 29460;
+	public static final int SMALL_MEAT_POUCH_OPEN = 29462;
+	public static final int LARGE_MEAT_POUCH_OPEN = 29464;
+	public static final int SMALL_FUR_POUCH_OPEN = 29466;
+	public static final int MEDIUM_FUR_POUCH_OPEN = 29468;
+	public static final int LARGE_FUR_POUCH_OPEN = 29470;
 	/* 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 e53832fcc..f23363a07 100644
--- a/runelite-api/src/main/java/net/runelite/api/NpcID.java
+++ b/runelite-api/src/main/java/net/runelite/api/NpcID.java
@@ -11286,49 +11286,6 @@ public final class NpcID
 	public static final int GUILD_HUNTER_FOX_13426 = 13426;
 	public static final int RED_SKINK = 13427;
 	public static final int RED_SKINK_13428 = 13428;
-	public static final int ANIMATED_EGG = 13429;
-	public static final int ANIMATED_EGG_13430 = 13430;
-	public static final int ANIMATED_EGG_13431 = 13431;
-	public static final int ANIMATED_EGG_13432 = 13432;
-	public static final int EASTER_BUNNY = 13433;
-	public static final int EASTER_BUNNY_13434 = 13434;
-	public static final int EASTER_BUNNY_13435 = 13435;
-	public static final int EG = 13436;
-	public static final int EGG_13437 = 13437;
-	public static final int EGGY = 13438;
-	public static final int EASTER_EGG_BIRD = 13439;
-	public static final int EASTER_EGG_BIRD_13440 = 13440;
-	public static final int EGGUS_MANUS = 13441;
-	public static final int EGGUS_MANUS_13442 = 13442;
-	public static final int EGG_PRIEST = 13443;
-	public static final int EGG_PRIEST_13444 = 13444;
-	public static final int EGG_PRIEST_13445 = 13445;
-	public static final int EGG_PRIEST_13446 = 13446;
-	public static final int EGG_PRIEST_13447 = 13447;
-	public static final int EGG_PRIEST_13448 = 13448;
-	public static final int EGG_PRIEST_13449 = 13449;
-	public static final int EGG_PRIEST_13450 = 13450;
-	public static final int EGG_PRIEST_13451 = 13451;
-	public static final int EGG_PRIEST_13452 = 13452;
-	public static final int SHIFTY_LOOKING_PRIEST = 13453;
-	public static final int PROTESTER_13454 = 13454;
-	public static final int UPSET_EASTER_EGG_HUNTER = 13455;
-	public static final int RASCALLY_CHILD = 13456;
-	public static final int DISAPPOINTED_MOTHER = 13457;
-	public static final int DISAPPOINTED_DAUGHTER = 13458;
-	public static final int DISAPPOINTED_FATHER = 13459;
-	public static final int EGG_MINE_CART = 13460;
-	public static final int EGG_MINE_CART_13461 = 13461;
-	public static final int MISBEHAVING_MINE_CART = 13462;
-	public static final int MISBEHAVING_MINE_CART_13463 = 13463;
-	public static final int MISBEHAVING_MINE_CART_13464 = 13464;
-	public static final int MISBEHAVING_MINE_CART_13465 = 13465;
-	public static final int EGG_PRIEST_13466 = 13466;
-	public static final int EGG_PRIEST_13467 = 13467;
-	public static final int EGG_PRIEST_13468 = 13468;
-	public static final int RABBIT_13469 = 13469;
-	public static final int RABBIT_13470 = 13470;
-	public static final int CHICKEN_13471 = 13471;
 	public static final int ENRAGED_BLOOD_MOON = 13485;
 	public static final int ENRAGED_BLUE_MOON = 13486;
 	public static final int ENRAGED_ECLIPSE_MOON = 13487;
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 b97374b48..4b666c304 100644
--- a/runelite-api/src/main/java/net/runelite/api/NullItemID.java
+++ b/runelite-api/src/main/java/net/runelite/api/NullItemID.java
@@ -15250,6 +15250,7 @@ public final class NullItemID
 	public static final int NULL_29426 = 29426;
 	public static final int NULL_29429 = 29429;
 	public static final int NULL_29430 = 29430;
+	public static final int NULL_29431 = 29431;
 	public static final int NULL_29432 = 29432;
 	public static final int NULL_29434 = 29434;
 	public static final int NULL_29436 = 29436;
@@ -15257,6 +15258,8 @@ public final class NullItemID
 	public static final int NULL_29440 = 29440;
 	public static final int NULL_29442 = 29442;
 	public static final int NULL_29444 = 29444;
+	public static final int NULL_29445 = 29445;
+	public static final int NULL_29446 = 29446;
 	public static final int NULL_29447 = 29447;
 	public static final int NULL_29450 = 29450;
 	public static final int NULL_29451 = 29451;
@@ -15267,5 +15270,10 @@ public final class NullItemID
 	public static final int NULL_29457 = 29457;
 	public static final int NULL_29459 = 29459;
 	public static final int NULL_29461 = 29461;
+	public static final int NULL_29463 = 29463;
+	public static final int NULL_29465 = 29465;
+	public static final int NULL_29467 = 29467;
+	public static final int NULL_29469 = 29469;
+	public static final int NULL_29471 = 29471;
 	/* 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 2f623a39a..6b64f94e0 100644
--- a/runelite-api/src/main/java/net/runelite/api/NullNpcID.java
+++ b/runelite-api/src/main/java/net/runelite/api/NullNpcID.java
@@ -2124,18 +2124,5 @@ public final class NullNpcID
 	public static final int NULL_13418 = 13418;
 	public static final int NULL_13419 = 13419;
 	public static final int NULL_13422 = 13422;
-	public static final int NULL_13472 = 13472;
-	public static final int NULL_13473 = 13473;
-	public static final int NULL_13474 = 13474;
-	public static final int NULL_13475 = 13475;
-	public static final int NULL_13476 = 13476;
-	public static final int NULL_13477 = 13477;
-	public static final int NULL_13478 = 13478;
-	public static final int NULL_13479 = 13479;
-	public static final int NULL_13480 = 13480;
-	public static final int NULL_13481 = 13481;
-	public static final int NULL_13482 = 13482;
-	public static final int NULL_13483 = 13483;
-	public static final int NULL_13484 = 13484;
 	/* 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 ab4a330ee..b2c3c967b 100644
--- a/runelite-api/src/main/java/net/runelite/api/NullObjectID.java
+++ b/runelite-api/src/main/java/net/runelite/api/NullObjectID.java
@@ -27497,7 +27497,6 @@ public final class NullObjectID
 	public static final int NULL_53043 = 53043;
 	public static final int NULL_53044 = 53044;
 	public static final int NULL_53047 = 53047;
-	public static final int NULL_53048 = 53048;
 	public static final int NULL_53051 = 53051;
 	public static final int NULL_53052 = 53052;
 	public static final int NULL_53053 = 53053;
@@ -27585,14 +27584,17 @@ public final class NullObjectID
 	public static final int NULL_53211 = 53211;
 	public static final int NULL_53212 = 53212;
 	public static final int NULL_53213 = 53213;
-	public static final int NULL_53214 = 53214;
-	public static final int NULL_53215 = 53215;
-	public static final int NULL_53216 = 53216;
-	public static final int NULL_53217 = 53217;
-	public static final int NULL_53218 = 53218;
 	public static final int NULL_53219 = 53219;
 	public static final int NULL_53220 = 53220;
 	public static final int NULL_53221 = 53221;
 	public static final int NULL_53223 = 53223;
+	public static final int NULL_53226 = 53226;
+	public static final int NULL_53227 = 53227;
+	public static final int NULL_53228 = 53228;
+	public static final int NULL_53229 = 53229;
+	public static final int NULL_53230 = 53230;
+	public static final int NULL_53231 = 53231;
+	public static final int NULL_53232 = 53232;
+	public static final int NULL_53233 = 53233;
 	/* 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 551b195c6..108182aaa 100644
--- a/runelite-api/src/main/java/net/runelite/api/ObjectID.java
+++ b/runelite-api/src/main/java/net/runelite/api/ObjectID.java
@@ -25534,7 +25534,8 @@ public final class ObjectID
 	public static final int COLLAPSED_TRAP_53024 = 53024;
 	public static final int CRYPT_STAIRCASE = 53045;
 	public static final int CRYPT_STAIRCASE_53046 = 53046;
-	public static final int UNLIT_BRAZIER = 53049;
+	public static final int UNLIT_BRAZIER = 53048;
+	public static final int UNLIT_BRAZIER_53049 = 53049;
 	public static final int BRAZIER_53050 = 53050;
 	public static final int TRAIL_OF_CHOCOLATE = 53054;
 	public static final int TRAIL_OF_CHOCOLATE_53055 = 53055;
@@ -25612,7 +25613,13 @@ public final class ObjectID
 	public static final int SKELETON_53190 = 53190;
 	public static final int SKELETON_53191 = 53191;
 	public static final int SKELETON_53192 = 53192;
+	public static final int EASTER_MINE_CART_53214 = 53214;
+	public static final int EASTER_MINE_CART_53215 = 53215;
+	public static final int EASTER_MINE_CART_53216 = 53216;
+	public static final int EASTER_MINE_CART_53217 = 53217;
+	public static final int EASTER_MINE_CART_53218 = 53218;
 	public static final int ZOMBIE_PIRATES_LOCKER = 53222;
 	public static final int AGILITY_DISPENSER = 53224;
+	public static final int SPIKES_53225 = 53225;
 	/* This file is automatically generated. Do not edit. */
 }
diff --git a/runelite-api/src/main/java/net/runelite/api/TileItem.java b/runelite-api/src/main/java/net/runelite/api/TileItem.java
index 0a92f982a..3fe30f00c 100644
--- a/runelite-api/src/main/java/net/runelite/api/TileItem.java
+++ b/runelite-api/src/main/java/net/runelite/api/TileItem.java
@@ -24,11 +24,18 @@
  */
 package net.runelite.api;
 
+import org.intellij.lang.annotations.MagicConstant;
+
 /**
  * Represents an item inside an {@link ItemLayer}.
  */
 public interface TileItem extends Renderable
 {
+	int OWNERSHIP_NONE = 0;
+	int OWNERSHIP_SELF = 1;
+	int OWNERSHIP_OTHER = 2;
+	int OWNERSHIP_GROUP = 3;
+
 	/**
 	 * @return the ID of the item
 	 * @see ItemID
@@ -38,15 +45,29 @@ public interface TileItem extends Renderable
 	int getQuantity();
 
 	/**
-	 * Get the time, in server ticks, when the item despawns, relative to the spawn time.
+	 * Get the time, in server ticks, when the item becomes visible to other players
+	 * @see Client#getTickCount()
+	 * @return
+	 */
+	int getVisibleTime();
+
+	/**
+	 * Get the time, in server ticks, when the item despawns
+	 * @see Client#getTickCount()
 	 * @return
 	 */
 	int getDespawnTime();
 
 	/**
-	 * Get the time, in server ticks, when the item becomes visible to other players, relative
-	 * to the spawn time.
+	 * Get the item ownership
 	 * @return
 	 */
-	int getVisibleTime();
+	@MagicConstant(intValues = {OWNERSHIP_NONE, OWNERSHIP_SELF, OWNERSHIP_OTHER, OWNERSHIP_GROUP})
+	int getOwnership();
+
+	/**
+	 * Test whether the item is private
+	 * @return
+	 */
+	boolean isPrivate();
 }
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 b4d3a5d98..cc14fa46d 100644
--- a/runelite-api/src/main/java/net/runelite/api/VarPlayer.java
+++ b/runelite-api/src/main/java/net/runelite/api/VarPlayer.java
@@ -34,7 +34,9 @@ package net.runelite.api;
  */
 public final class VarPlayer
 {
+	public static final int CANNON_STATE = 2;
 	public static final int CANNON_AMMO = 3;
+	public static final int CANNON_COORD = 4;
 	public static final int ATTACK_STYLE = 43;
 	public static final int QUEST_POINTS = 101;
 	/**
@@ -240,4 +242,12 @@ public final class VarPlayer
 	 * Assigned slayer task location. The mapping of value to name can be found in {@link EnumID#SLAYER_TASK_LOCATION}
 	 */
 	public static final int SLAYER_TASK_LOCATION = 2096;
+
+	/**
+	 * Determines whether the woodcutting group bonus should be displayed on the buff bar or not.
+	 * 96 = displayed (including the woodcutting guild).
+	 * 0 = not displayed (after login until cutting a tree except normal trees or trees grown through farming).
+	 * -1 = not displayed (including normal trees or trees grown through farming).
+	 */
+	public static final int BUFF_BAR_WC_GROUP_BONUS = 4007;
 }
diff --git a/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java b/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java
index 707604f2c..077e004b1 100644
--- a/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java
+++ b/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java
@@ -543,4 +543,12 @@ public class WorldPoint
 	{
 		return new WorldArea(this, 1, 1);
 	}
+
+	/**
+	 * Create a WorldPoint from a packed Jagex coordinate
+	 */
+	public static WorldPoint fromCoord(int c)
+	{
+		return new WorldPoint((c >>> 14) & 0x3FFF, c & 0x3FFF, (c >>> 28) & 0x3);
+	}
 }
diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml
index aa4931d48..c29a8a6b7 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.28-SNAPSHOT</version>
+		<version>1.10.29-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>client</artifactId>
diff --git a/runelite-client/src/main/java/net/runelite/client/config/Keybind.java b/runelite-client/src/main/java/net/runelite/client/config/Keybind.java
index eba500b85..4ba1ada45 100644
--- a/runelite-client/src/main/java/net/runelite/client/config/Keybind.java
+++ b/runelite-client/src/main/java/net/runelite/client/config/Keybind.java
@@ -125,9 +125,17 @@ public class Keybind
 			keyCode = KeyEvent.VK_UNDEFINED;
 		}
 
-		if (e.getID() == KeyEvent.KEY_RELEASED && keyCode != KeyEvent.VK_UNDEFINED)
+		if (e.getID() == KeyEvent.KEY_RELEASED)
 		{
-			return this.keyCode == keyCode;
+			if (keyCode != KeyEvent.VK_UNDEFINED)
+			{
+				return this.keyCode == keyCode;
+			}
+			else
+			{
+				assert mf != null;
+				return this.keyCode == keyCode && (this.modifiers & modifiers) == this.modifiers && ((mf & this.modifiers) == mf);
+			}
 		}
 
 		if (ignoreModifiers && keyCode != KeyEvent.VK_UNDEFINED)
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/Boss.java b/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/Boss.java
index 665ba5e93..f47b75856 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/Boss.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/Boss.java
@@ -40,19 +40,19 @@ enum Boss
 	KRIL_TSUTSAROTH(NpcID.KRIL_TSUTSAROTH, 90, ChronoUnit.SECONDS, ItemID.PET_KRIL_TSUTSAROTH),
 	KREEARRA(NpcID.KREEARRA, 90, ChronoUnit.SECONDS, ItemID.PET_KREEARRA),
 	COMMANDER_ZILYANA(NpcID.COMMANDER_ZILYANA, 90, ChronoUnit.SECONDS, ItemID.PET_ZILYANA),
-	CALLISTO(NpcID.CALLISTO_6609, 29, RSTimeUnit.GAME_TICKS, ItemID.CALLISTO_CUB),
-	ARTIO(NpcID.ARTIO, 29, RSTimeUnit.GAME_TICKS, ItemID.CALLISTO_CUB),
-	CHAOS_ELEMENTAL(NpcID.CHAOS_ELEMENTAL, 15, ChronoUnit.SECONDS, ItemID.PET_CHAOS_ELEMENTAL),
-	CHAOS_FANATIC(NpcID.CHAOS_FANATIC, 30, ChronoUnit.SECONDS, ItemID.ANCIENT_STAFF),
+	CALLISTO(NpcID.CALLISTO_6609, 11, RSTimeUnit.GAME_TICKS, ItemID.CALLISTO_CUB),
+	ARTIO(NpcID.ARTIO, 11, RSTimeUnit.GAME_TICKS, ItemID.CALLISTO_CUB),
+	CHAOS_ELEMENTAL(NpcID.CHAOS_ELEMENTAL, 14, RSTimeUnit.GAME_TICKS, ItemID.PET_CHAOS_ELEMENTAL),
+	CHAOS_FANATIC(NpcID.CHAOS_FANATIC, 14, RSTimeUnit.GAME_TICKS, ItemID.ANCIENT_STAFF),
 	CRAZY_ARCHAEOLOGIST(NpcID.CRAZY_ARCHAEOLOGIST, 30, ChronoUnit.SECONDS, ItemID.FEDORA),
 	KING_BLACK_DRAGON(NpcID.KING_BLACK_DRAGON, 9, ChronoUnit.SECONDS, ItemID.PRINCE_BLACK_DRAGON),
-	SCORPIA(NpcID.SCORPIA, 16, RSTimeUnit.GAME_TICKS, ItemID.SCORPIAS_OFFSPRING),
+	SCORPIA(NpcID.SCORPIA, 14, RSTimeUnit.GAME_TICKS, ItemID.SCORPIAS_OFFSPRING),
 	SCURRIUS(NpcID.SCURRIUS, 29, RSTimeUnit.GAME_TICKS, ItemID.SCURRY),
 	SCURRIUS_PRIVATE(NpcID.SCURRIUS_7222, 29, RSTimeUnit.GAME_TICKS, ItemID.SCURRY),
-	VENENATIS(NpcID.VENENATIS_6610, 28, RSTimeUnit.GAME_TICKS, ItemID.VENENATIS_SPIDERLING),
-	SPINDEL(NpcID.SPINDEL, 28, RSTimeUnit.GAME_TICKS, ItemID.VENENATIS_SPIDERLING),
-	VETION(NpcID.VETION_6612, 33, RSTimeUnit.GAME_TICKS, ItemID.VETION_JR),
-	CALVARION(NpcID.CALVARION_11994, 33, RSTimeUnit.GAME_TICKS, ItemID.VETION_JR),
+	VENENATIS(NpcID.VENENATIS_6610, 10, RSTimeUnit.GAME_TICKS, ItemID.VENENATIS_SPIDERLING),
+	SPINDEL(NpcID.SPINDEL, 9, RSTimeUnit.GAME_TICKS, ItemID.VENENATIS_SPIDERLING),
+	VETION(NpcID.VETION_6612, 15, RSTimeUnit.GAME_TICKS, ItemID.VETION_JR),
+	CALVARION(NpcID.CALVARION_11994, 14, RSTimeUnit.GAME_TICKS, ItemID.VETION_JR),
 	DAGANNOTH_PRIME(NpcID.DAGANNOTH_PRIME, 90, ChronoUnit.SECONDS, ItemID.PET_DAGANNOTH_PRIME),
 	DAGANNOTH_REX(NpcID.DAGANNOTH_REX, 90, ChronoUnit.SECONDS, ItemID.PET_DAGANNOTH_REX),
 	DAGANNOTH_SUPREME(NpcID.DAGANNOTH_SUPREME, 90, ChronoUnit.SECONDS, ItemID.PET_DAGANNOTH_SUPREME),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonPlugin.java
index bb602c735..88362c9d5 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonPlugin.java
@@ -31,29 +31,19 @@ import java.util.List;
 import javax.inject.Inject;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
-import net.runelite.api.AnimationID;
 import net.runelite.api.ChatMessageType;
 import net.runelite.api.Client;
-import net.runelite.api.GameObject;
 import net.runelite.api.GameState;
 import net.runelite.api.InventoryID;
 import net.runelite.api.Item;
 import net.runelite.api.ItemID;
-import net.runelite.api.MenuAction;
-import net.runelite.api.ObjectID;
-import net.runelite.api.Player;
 import net.runelite.api.VarPlayer;
-import net.runelite.api.coords.LocalPoint;
 import net.runelite.api.coords.WorldArea;
 import net.runelite.api.coords.WorldPoint;
 import net.runelite.api.events.ChatMessage;
-import net.runelite.api.events.GameObjectSpawned;
 import net.runelite.api.events.GameStateChanged;
 import net.runelite.api.events.ItemContainerChanged;
-import net.runelite.api.events.MenuOptionClicked;
 import net.runelite.api.events.VarbitChanged;
-import net.runelite.api.widgets.ComponentID;
-import net.runelite.api.widgets.Widget;
 import net.runelite.client.Notifier;
 import net.runelite.client.callback.ClientThread;
 import net.runelite.client.config.ConfigManager;
@@ -77,8 +67,6 @@ public class CannonPlugin extends Plugin
 
 	private CannonCounter counter;
 	private boolean cannonBallNotificationSent;
-	private WorldPoint clickedCannonLocation;
-	private boolean firstCannonLoad;
 
 	@Getter
 	private int cballsLeft;
@@ -92,9 +80,6 @@ public class CannonPlugin extends Plugin
 	@Getter
 	private int cannonWorld = -1;
 
-	@Getter
-	private GameObject cannon;
-
 	@Getter
 	private List<WorldPoint> spotPoints = new ArrayList<>();
 
@@ -247,65 +232,40 @@ public class CannonPlugin extends Plugin
 	}
 
 	@Subscribe
-	public void onGameObjectSpawned(GameObjectSpawned event)
+	public void onVarbitChanged(VarbitChanged varbitChanged)
 	{
-		GameObject gameObject = event.getGameObject();
-
-		Player localPlayer = client.getLocalPlayer();
-		if ((gameObject.getId() == ObjectID.CANNON_BASE || gameObject.getId() == ObjectID.CANNON_BASE_43029) && !cannonPlaced)
+		if (varbitChanged.getVarpId() == VarPlayer.CANNON_AMMO)
 		{
-			if (localPlayer.getWorldLocation().distanceTo(gameObject.getWorldLocation()) <= 2
-				&& localPlayer.getAnimation() == AnimationID.BURYING_BONES)
+			int old = cballsLeft;
+			cballsLeft = varbitChanged.getValue();
+
+			if (cballsLeft > old)
 			{
-				cannonPosition = buildCannonWorldArea(gameObject.getWorldLocation());
-				cannonWorld = client.getWorld();
-				cannon = gameObject;
+				cannonBallNotificationSent = false;
 			}
-		}
-	}
-
-	@Subscribe
-	public void onMenuOptionClicked(MenuOptionClicked event)
-	{
-		if (cannonPosition != null || (event.getId() != ObjectID.DWARF_MULTICANNON && event.getId() != ObjectID.DWARF_MULTICANNON_43027))
-		{
-			return;
-		}
 
-		// Check if cannonballs are being used on the cannon
-		if (event.getMenuAction() == MenuAction.WIDGET_TARGET_ON_GAME_OBJECT
-			&& client.getSelectedWidget() != null
-			&& client.getSelectedWidget().getId() == ComponentID.INVENTORY_CONTAINER)
-		{
-			final Widget selected = client.getSelectedWidget();
-			final int itemId = selected.getItemId();
-			if (itemId != ItemID.CANNONBALL && itemId != ItemID.GRANITE_CANNONBALL)
+			if (!cannonBallNotificationSent && cballsLeft > 0 && config.lowWarningThreshold() >= cballsLeft)
 			{
-				return;
+				notifier.notify(config.showCannonNotifications(), String.format("Your cannon has %d cannon balls remaining!", cballsLeft));
+				cannonBallNotificationSent = true;
 			}
 		}
-		// Check for the Fire option being selected on the cannon.
-		else if (event.getMenuAction() != MenuAction.GAME_OBJECT_FIRST_OPTION)
+		else if (varbitChanged.getVarpId() == VarPlayer.CANNON_COORD)
 		{
-			return;
+			WorldPoint c = WorldPoint.fromCoord(varbitChanged.getValue());
+			cannonPosition = buildCannonWorldArea(c);
 		}
-
-		// Store the click location as a WorldPoint to avoid issues with scene loads
-		clickedCannonLocation = WorldPoint.fromScene(client, event.getParam0(), event.getParam1(), client.getPlane());
-		log.debug("Updated cannon location: {}", clickedCannonLocation);
-	}
-
-	@Subscribe
-	public void onVarbitChanged(VarbitChanged varbitChanged)
-	{
-		if (varbitChanged.getVarpId() == VarPlayer.CANNON_AMMO)
+		else if (varbitChanged.getVarpId() == VarPlayer.CANNON_STATE)
 		{
-			cballsLeft = varbitChanged.getValue();
+			cannonPlaced = varbitChanged.getValue() == 4;
 
-			if (!cannonBallNotificationSent && cballsLeft > 0 && config.lowWarningThreshold() >= cballsLeft)
+			if (cannonPlaced)
 			{
-				notifier.notify(config.showCannonNotifications(), String.format("Your cannon has %d cannon balls remaining!", cballsLeft));
-				cannonBallNotificationSent = true;
+				addCounter();
+			}
+			else
+			{
+				removeCounter();
 			}
 		}
 	}
@@ -320,58 +280,12 @@ public class CannonPlugin extends Plugin
 
 		if (event.getMessage().equals("You add the furnace."))
 		{
-			cannonPlaced = true;
-			addCounter();
-			firstCannonLoad = true;
-		}
-		else if (event.getMessage().contains("You pick up the cannon")
-			|| event.getMessage().contains("Your cannon has decayed. Speak to Nulodion to get a new one!")
-			|| event.getMessage().contains("Your cannon has been destroyed!"))
-		{
-			cannonPlaced = false;
-			removeCounter();
-			cannonPosition = null;
-		}
-		else if (event.getMessage().startsWith("You load the cannon with"))
-		{
-			// Set the cannon's position and object if the player's animation was interrupted during setup
-			if (cannonPosition == null && clickedCannonLocation != null)
-			{
-				// There is a window of 1 tick where the player can add the furnace, click on another cannon, and then
-				// the initial cannon load message arrives. This can cause the client to confuse the other cannon with
-				// the player's, so ignore that first message when deciding the cannon's location.
-				if (firstCannonLoad)
-				{
-					firstCannonLoad = false;
-				}
-				else
-				{
-					LocalPoint lp = LocalPoint.fromWorld(client, clickedCannonLocation);
-					if (lp != null)
-					{
-						GameObject[] objects = client.getScene().getTiles()[client.getPlane()][lp.getSceneX()][lp.getSceneY()].getGameObjects();
-						if (objects.length > 0 && client.getLocalPlayer().getWorldLocation().distanceTo(objects[0].getWorldLocation()) <= 2)
-						{
-							cannonPlaced = true;
-							cannonWorld = client.getWorld();
-							cannon = objects[0];
-							cannonPosition = buildCannonWorldArea(cannon.getWorldLocation());
-						}
-					}
-				}
-				clickedCannonLocation = null;
-			}
-
-			cannonBallNotificationSent = false;
+			cannonWorld = client.getWorld();
 		}
 		else if (event.getMessage().contains("Your cannon is out of ammo!"))
 		{
 			notifier.notify(config.showCannonNotifications(), "Your cannon is out of ammo!");
 		}
-		else if (event.getMessage().equals("This isn't your cannon!") || event.getMessage().equals("This is not your cannon."))
-		{
-			clickedCannonLocation = null;
-		}
 	}
 
 	Color getStateColor()
@@ -414,6 +328,6 @@ public class CannonPlugin extends Plugin
 
 	private static WorldArea buildCannonWorldArea(WorldPoint worldPoint)
 	{
-		return new WorldArea(worldPoint.getX() - 1, worldPoint.getY() - 1, 3, 3, worldPoint.getPlane());
+		return new WorldArea(worldPoint.getX(), worldPoint.getY(), 3, 3, worldPoint.getPlane());
 	}
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java
index d50e114cc..c62e666c9 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java
@@ -209,7 +209,7 @@ public class CoordinateClue extends ClueScroll implements LocationClueScroll
 		.put(new WorldPoint(3369, 3894, 0), new CoordinateClueInfo("Wilderness. Fountain of Rune.", ARMADYLEAN_OR_BANDOSIAN_GUARD))
 		.put(new WorldPoint(2065, 3923, 0), new CoordinateClueInfo("Outside the western wall on Lunar Isle.", ARMADYLEAN_OR_BANDOSIAN_GUARD))
 		.put(new WorldPoint(3188, 3933, 0), new CoordinateClueInfo("Wilderness. Resource Area.", ARMADYLEAN_OR_BANDOSIAN_GUARD))
-		.put(new WorldPoint(2997, 3953, 0), new CoordinateClueInfo("Wilderness. Inside Agility Training Area.", ARMADYLEAN_OR_BANDOSIAN_GUARD))
+		.put(new WorldPoint(3043, 3940, 0), new CoordinateClueInfo("Wilderness. South of Pirates' Hideout.", ARMADYLEAN_OR_BANDOSIAN_GUARD))
 		.put(new WorldPoint(3380, 3963, 0), new CoordinateClueInfo("Wilderness. North of Volcano.", ARMADYLEAN_OR_BANDOSIAN_GUARD))
 		.put(new WorldPoint(3051, 3736, 0), new CoordinateClueInfo("East of the Wilderness Obelisk in 28 Wilderness.", ARMADYLEAN_OR_BANDOSIAN_GUARD))
 		.put(new WorldPoint(2316, 3814, 0), new CoordinateClueInfo("West of Neitiznot, near the bridge.", ARMADYLEAN_OR_BANDOSIAN_GUARD))
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/NotificationPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/NotificationPanel.java
index 23a559cf2..d1098df24 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/config/NotificationPanel.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/NotificationPanel.java
@@ -343,7 +343,7 @@ class NotificationPanel extends PluginPanel
 					saveNotification(n.withFlash((FlashNotification) comboboxFlash.getSelectedItem()));
 				}
 			});
-			item("Flash", "Flashes the game frame as a notification", combobox(FlashNotification.class, notif.getFlash()));
+			item("Flash", "Flashes the game frame as a notification", comboboxFlash);
 
 			var colorpickerFlashColor = createColorPicker("Flash color", notif.getFlashColor(), c ->
 			{
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuIntBuffer.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuIntBuffer.java
index 1e85111c1..4ea829e7a 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuIntBuffer.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuIntBuffer.java
@@ -42,6 +42,14 @@ class GpuIntBuffer
 		buffer.put(x).put(y).put(z).put(c);
 	}
 
+	void put(float x, float y, float z, int w)
+	{
+		buffer.put(Float.floatToIntBits(x))
+			.put(Float.floatToIntBits(y))
+			.put(Float.floatToIntBits(z))
+			.put(w);
+	}
+
 	void flip()
 	{
 		buffer.flip();
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java
index 4e7f7afdb..2021b9fe0 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java
@@ -183,8 +183,8 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 	private int vaoUiHandle;
 	private int vboUiHandle;
 
-	private int fboSceneHandle;
-	private int rboSceneHandle;
+	private int fboScene;
+	private int rboColorBuffer;
 
 	private final GLBuffer sceneVertexBuffer = new GLBuffer("scene vertex buffer");
 	private final GLBuffer sceneUvBuffer = new GLBuffer("scene tex buffer");
@@ -286,7 +286,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 		{
 			try
 			{
-				fboSceneHandle = rboSceneHandle = -1; // AA FBO
+				fboScene = rboColorBuffer = -1;
 				targetBufferOffset = 0;
 				unorderedModels = smallModels = largeModels = 0;
 
@@ -465,7 +465,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 				shutdownProgram();
 				shutdownVao();
 				shutdownBuffers();
-				shutdownAAFbo();
+				shutdownFbo();
 			}
 
 			// this must shutdown after the clgl buffers are freed
@@ -663,11 +663,15 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 
 		GL43C.glEnableVertexAttribArray(0);
 		GL43C.glBindBuffer(GL43C.GL_ARRAY_BUFFER, tmpOutBuffer.glBufferId);
-		GL43C.glVertexAttribIPointer(0, 4, GL43C.GL_INT, 0, 0);
+		GL43C.glVertexAttribPointer(0, 3, GL43C.GL_FLOAT, false, 16, 0);
 
 		GL43C.glEnableVertexAttribArray(1);
+		GL43C.glBindBuffer(GL43C.GL_ARRAY_BUFFER, tmpOutBuffer.glBufferId);
+		GL43C.glVertexAttribIPointer(1, 1, GL43C.GL_INT, 16, 12);
+
+		GL43C.glEnableVertexAttribArray(2);
 		GL43C.glBindBuffer(GL43C.GL_ARRAY_BUFFER, tmpOutUvBuffer.glBufferId);
-		GL43C.glVertexAttribPointer(1, 4, GL43C.GL_FLOAT, false, 0, 0);
+		GL43C.glVertexAttribPointer(2, 4, GL43C.GL_FLOAT, false, 0, 0);
 
 		// Create temp VAO
 		vaoTemp = GL43C.glGenVertexArrays();
@@ -675,11 +679,15 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 
 		GL43C.glEnableVertexAttribArray(0);
 		GL43C.glBindBuffer(GL43C.GL_ARRAY_BUFFER, tmpVertexBuffer.glBufferId);
-		GL43C.glVertexAttribIPointer(0, 4, GL43C.GL_INT, 0, 0);
+		GL43C.glVertexAttribPointer(0, 3, GL43C.GL_FLOAT, false, 16, 0);
 
 		GL43C.glEnableVertexAttribArray(1);
+		GL43C.glBindBuffer(GL43C.GL_ARRAY_BUFFER, tmpVertexBuffer.glBufferId);
+		GL43C.glVertexAttribIPointer(1, 1, GL43C.GL_INT, 16, 12);
+
+		GL43C.glEnableVertexAttribArray(2);
 		GL43C.glBindBuffer(GL43C.GL_ARRAY_BUFFER, tmpUvBuffer.glBufferId);
-		GL43C.glVertexAttribPointer(1, 4, GL43C.GL_FLOAT, false, 0, 0);
+		GL43C.glVertexAttribPointer(2, 4, GL43C.GL_FLOAT, false, 0, 0);
 
 		// Create UI VAO
 		vaoUiHandle = GL43C.glGenVertexArrays();
@@ -798,22 +806,11 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 	{
 		initGlBuffer(uniformBuffer);
 
-		IntBuffer uniformBuf = GpuIntBuffer.allocateDirect(8 + 2048 * 4);
-		uniformBuf.put(new int[8]); // uniform block
-		final int[] pad = new int[2];
-		for (int i = 0; i < 2048; i++)
-		{
-			uniformBuf.put(Perspective.SINE[i]);
-			uniformBuf.put(Perspective.COSINE[i]);
-			uniformBuf.put(pad); // ivec2 alignment in std140 is 16 bytes
-		}
-		uniformBuf.flip();
-
-		updateBuffer(uniformBuffer, GL43C.GL_UNIFORM_BUFFER, uniformBuf, GL43C.GL_DYNAMIC_DRAW, CL12.CL_MEM_READ_ONLY);
+		updateBuffer(uniformBuffer, GL43C.GL_UNIFORM_BUFFER, 8 * Integer.BYTES, GL43C.GL_DYNAMIC_DRAW, CL12.CL_MEM_READ_ONLY);
 		GL43C.glBindBuffer(GL43C.GL_UNIFORM_BUFFER, 0);
 	}
 
-	private void initAAFbo(int width, int height, int aaSamples)
+	private void initFbo(int width, int height, int aaSamples)
 	{
 		final GraphicsConfiguration graphicsConfiguration = clientUI.getGraphicsConfiguration();
 		final AffineTransform transform = graphicsConfiguration.getDefaultTransform();
@@ -821,15 +818,24 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 		width = getScaledValue(transform.getScaleX(), width);
 		height = getScaledValue(transform.getScaleY(), height);
 
+		if (aaSamples > 0)
+		{
+			GL43C.glEnable(GL43C.GL_MULTISAMPLE);
+		}
+		else
+		{
+			GL43C.glDisable(GL43C.GL_MULTISAMPLE);
+		}
+
 		// Create and bind the FBO
-		fboSceneHandle = GL43C.glGenFramebuffers();
-		GL43C.glBindFramebuffer(GL43C.GL_FRAMEBUFFER, fboSceneHandle);
+		fboScene = GL43C.glGenFramebuffers();
+		GL43C.glBindFramebuffer(GL43C.GL_FRAMEBUFFER, fboScene);
 
 		// Create color render buffer
-		rboSceneHandle = GL43C.glGenRenderbuffers();
-		GL43C.glBindRenderbuffer(GL43C.GL_RENDERBUFFER, rboSceneHandle);
+		rboColorBuffer = GL43C.glGenRenderbuffers();
+		GL43C.glBindRenderbuffer(GL43C.GL_RENDERBUFFER, rboColorBuffer);
 		GL43C.glRenderbufferStorageMultisample(GL43C.GL_RENDERBUFFER, aaSamples, GL43C.GL_RGBA, width, height);
-		GL43C.glFramebufferRenderbuffer(GL43C.GL_FRAMEBUFFER, GL43C.GL_COLOR_ATTACHMENT0, GL43C.GL_RENDERBUFFER, rboSceneHandle);
+		GL43C.glFramebufferRenderbuffer(GL43C.GL_FRAMEBUFFER, GL43C.GL_COLOR_ATTACHMENT0, GL43C.GL_RENDERBUFFER, rboColorBuffer);
 
 		int status = GL43C.glCheckFramebufferStatus(GL43C.GL_FRAMEBUFFER);
 		if (status != GL43C.GL_FRAMEBUFFER_COMPLETE)
@@ -842,18 +848,18 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 		GL43C.glBindRenderbuffer(GL43C.GL_RENDERBUFFER, 0);
 	}
 
-	private void shutdownAAFbo()
+	private void shutdownFbo()
 	{
-		if (fboSceneHandle != -1)
+		if (fboScene != -1)
 		{
-			GL43C.glDeleteFramebuffers(fboSceneHandle);
-			fboSceneHandle = -1;
+			GL43C.glDeleteFramebuffers(fboScene);
+			fboScene = -1;
 		}
 
-		if (rboSceneHandle != -1)
+		if (rboColorBuffer != -1)
 		{
-			GL43C.glDeleteRenderbuffers(rboSceneHandle);
-			rboSceneHandle = -1;
+			GL43C.glDeleteRenderbuffers(rboColorBuffer);
+			rboColorBuffer = -1;
 		}
 	}
 
@@ -877,7 +883,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 		// viewport buffer.
 		targetBufferOffset = 0;
 
-		// UBO. Only the first 32 bytes get modified here, the rest is the constant sin/cos table.
+		// UBO.
 		// We can reuse the vertex buffer since it isn't used yet.
 		vertexBuffer.clear();
 		vertexBuffer.ensureCapacity(32);
@@ -893,8 +899,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 			.put(Float.floatToIntBits((float) cameraZ));
 		uniformBuf.flip();
 
-		GL43C.glBindBuffer(GL43C.GL_UNIFORM_BUFFER, uniformBuffer.glBufferId);
-		GL43C.glBufferSubData(GL43C.GL_UNIFORM_BUFFER, 0, uniformBuf);
+		updateBuffer(uniformBuffer, GL43C.GL_UNIFORM_BUFFER, uniformBuf, GL43C.GL_DYNAMIC_DRAW, CL12.CL_MEM_READ_ONLY);
 		GL43C.glBindBuffer(GL43C.GL_UNIFORM_BUFFER, 0);
 
 		GL43C.glBindBufferBase(GL43C.GL_UNIFORM_BUFFER, 0, uniformBuffer.glBufferId);
@@ -1146,14 +1151,9 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 
 		prepareInterfaceTexture(canvasWidth, canvasHeight);
 
-		// Setup anti-aliasing
-		final AntiAliasingMode antiAliasingMode = config.antiAliasingMode();
-		final boolean aaEnabled = antiAliasingMode != AntiAliasingMode.DISABLED;
-
-		if (aaEnabled)
+		// Setup FBO and anti-aliasing
 		{
-			GL43C.glEnable(GL43C.GL_MULTISAMPLE);
-
+			final AntiAliasingMode antiAliasingMode = config.antiAliasingMode();
 			final Dimension stretchedDimensions = client.getStretchedDimensions();
 
 			final int stretchedCanvasWidth = client.isStretchedEnabled() ? stretchedDimensions.width : canvasWidth;
@@ -1164,7 +1164,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 				|| lastStretchedCanvasHeight != stretchedCanvasHeight
 				|| lastAntiAliasingMode != antiAliasingMode)
 			{
-				shutdownAAFbo();
+				shutdownFbo();
 
 				// Bind default FBO to check whether anti-aliasing is forced
 				GL43C.glBindFramebuffer(GL43C.GL_FRAMEBUFFER, awtContext.getFramebuffer(false));
@@ -1175,22 +1175,16 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 
 				log.debug("AA samples: {}, max samples: {}, forced samples: {}", samples, maxSamples, forcedAASamples);
 
-				initAAFbo(stretchedCanvasWidth, stretchedCanvasHeight, samples);
+				initFbo(stretchedCanvasWidth, stretchedCanvasHeight, samples);
 
 				lastStretchedCanvasWidth = stretchedCanvasWidth;
 				lastStretchedCanvasHeight = stretchedCanvasHeight;
+				lastAntiAliasingMode = antiAliasingMode;
 			}
 
-			GL43C.glBindFramebuffer(GL43C.GL_DRAW_FRAMEBUFFER, fboSceneHandle);
-		}
-		else
-		{
-			GL43C.glDisable(GL43C.GL_MULTISAMPLE);
-			shutdownAAFbo();
+			GL43C.glBindFramebuffer(GL43C.GL_DRAW_FRAMEBUFFER, fboScene);
 		}
 
-		lastAntiAliasingMode = antiAliasingMode;
-
 		// Clear scene
 		int sky = client.getSkyboxColor();
 		GL43C.glClearColor((sky >> 16 & 0xFF) / 255f, (sky >> 8 & 0xFF) / 255f, (sky & 0xFF) / 255f, 1f);
@@ -1324,7 +1318,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 			GL43C.glUseProgram(0);
 		}
 
-		if (aaEnabled)
+		// Blit FBO
 		{
 			int width = lastStretchedCanvasWidth;
 			int height = lastStretchedCanvasHeight;
@@ -1335,7 +1329,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 			width = getScaledValue(transform.getScaleX(), width);
 			height = getScaledValue(transform.getScaleY(), height);
 
-			GL43C.glBindFramebuffer(GL43C.GL_READ_FRAMEBUFFER, fboSceneHandle);
+			GL43C.glBindFramebuffer(GL43C.GL_READ_FRAMEBUFFER, fboScene);
 			GL43C.glBindFramebuffer(GL43C.GL_DRAW_FRAMEBUFFER, awtContext.getFramebuffer(false));
 			GL43C.glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
 				GL43C.GL_COLOR_BUFFER_BIT, GL43C.GL_NEAREST);
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/SceneUploader.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/SceneUploader.java
index 6aaf9acca..f47b12788 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/SceneUploader.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/SceneUploader.java
@@ -229,7 +229,7 @@ class SceneUploader
 	}
 
 	int upload(Scene scene, SceneTilePaint tile, int tileZ, int tileX, int tileY, GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer,
-		int lx, int lz, boolean padUvs)
+		int lx, int lz, boolean stream)
 	{
 		tileX += GpuPlugin.SCENE_OFFSET;
 		tileY += GpuPlugin.SCENE_OFFSET;
@@ -277,15 +277,15 @@ class SceneUploader
 		final int lz3 = lz + Perspective.LOCAL_TILE_SIZE;
 		final int hsl3 = nwColor;
 
-		vertexBuffer.put(lx2, ly2, lz2, hsl2);
-		vertexBuffer.put(lx3, ly3, lz3, hsl3);
-		vertexBuffer.put(lx1, ly1, lz1, hsl1);
+		vertexBuffer.put((float) lx2, ly2, lz2, hsl2);
+		vertexBuffer.put((float) lx3, ly3, lz3, hsl3);
+		vertexBuffer.put((float) lx1, ly1, lz1, hsl1);
 
-		vertexBuffer.put(lx0, ly0, lz0, hsl0);
-		vertexBuffer.put(lx1, ly1, lz1, hsl1);
-		vertexBuffer.put(lx3, ly3, lz3, hsl3);
+		vertexBuffer.put((float) lx0, ly0, lz0, hsl0);
+		vertexBuffer.put((float) lx1, ly1, lz1, hsl1);
+		vertexBuffer.put((float) lx3, ly3, lz3, hsl3);
 
-		if (padUvs || tile.getTexture() != -1)
+		if (stream || tile.getTexture() != -1)
 		{
 			int tex = tile.getTexture() + 1;
 			if (tile.isFlat())
@@ -310,7 +310,7 @@ class SceneUploader
 	}
 
 	int upload(SceneTileModel sceneTileModel, int lx, int lz,
-		GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer, boolean padUvs)
+		GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer, boolean stream)
 	{
 		final int[] faceX = sceneTileModel.getFaceX();
 		final int[] faceY = sceneTileModel.getFaceY();
@@ -362,11 +362,11 @@ class SceneUploader
 			int ly2 = vertexY[vertex2];
 			int lz2 = vertexZ[vertex2] - lz;
 
-			vertexBuffer.put(lx0, ly0, lz0, hsl0);
-			vertexBuffer.put(lx1, ly1, lz1, hsl1);
-			vertexBuffer.put(lx2, ly2, lz2, hsl2);
+			vertexBuffer.put((float) lx0, ly0, lz0, hsl0);
+			vertexBuffer.put((float) lx1, ly1, lz1, hsl1);
+			vertexBuffer.put((float) lx2, ly2, lz2, hsl2);
 
-			if (padUvs || triangleTextures != null)
+			if (stream || triangleTextures != null)
 			{
 				if (triangleTextures != null && triangleTextures[i] != -1)
 				{
@@ -508,9 +508,9 @@ class SceneUploader
 			int triangleB = indices2[face];
 			int triangleC = indices3[face];
 
-			vertexBuffer.put(vertexX[triangleA], vertexY[triangleA], vertexZ[triangleA], packAlphaPriority | color1);
-			vertexBuffer.put(vertexX[triangleB], vertexY[triangleB], vertexZ[triangleB], packAlphaPriority | color2);
-			vertexBuffer.put(vertexX[triangleC], vertexY[triangleC], vertexZ[triangleC], packAlphaPriority | color3);
+			vertexBuffer.put((float) vertexX[triangleA], vertexY[triangleA], vertexZ[triangleA], packAlphaPriority | color1);
+			vertexBuffer.put((float) vertexX[triangleB], vertexY[triangleB], vertexZ[triangleB], packAlphaPriority | color2);
+			vertexBuffer.put((float) vertexX[triangleC], vertexY[triangleC], vertexZ[triangleC], packAlphaPriority | color3);
 
 			if (faceTextures != null)
 			{
@@ -982,9 +982,9 @@ class SceneUploader
 			}
 		}
 
-		vertexBuffer.put(modelLocalX[triangleA], modelLocalY[triangleA], modelLocalZ[triangleA], packAlphaPriority | color1);
-		vertexBuffer.put(modelLocalX[triangleB], modelLocalY[triangleB], modelLocalZ[triangleB], packAlphaPriority | color2);
-		vertexBuffer.put(modelLocalX[triangleC], modelLocalY[triangleC], modelLocalZ[triangleC], packAlphaPriority | color3);
+		vertexBuffer.put((float) modelLocalX[triangleA], modelLocalY[triangleA], modelLocalZ[triangleA], packAlphaPriority | color1);
+		vertexBuffer.put((float) modelLocalX[triangleB], modelLocalY[triangleB], modelLocalZ[triangleB], packAlphaPriority | color2);
+		vertexBuffer.put((float) modelLocalX[triangleC], modelLocalY[triangleC], modelLocalZ[triangleC], packAlphaPriority | color3);
 
 		if (faceTextures != null && faceTextures[face] != -1)
 		{
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 9cbfaf667..785711b46 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
@@ -80,8 +80,6 @@ class GrandExchangeSearchPanel extends JPanel
 	/*  The error panel, this displays an error message */
 	private final PluginErrorPanel errorPanel = new PluginErrorPanel();
 
-	private final List<GrandExchangeItems> itemsList = new ArrayList<>();
-
 	@Inject
 	private GrandExchangeSearchPanel(ClientThread clientThread, ItemManager itemManager,
 		ScheduledExecutorService executor, RuneLiteConfig runeLiteConfig, GrandExchangePlugin grandExchangePlugin)
@@ -195,7 +193,7 @@ class GrandExchangeSearchPanel extends JPanel
 
 	private void processResult(List<ItemPrice> result, String lookup, boolean exactMatch)
 	{
-		itemsList.clear();
+		final List<GrandExchangeItems> itemsList = new ArrayList<>();
 
 		cardLayout.show(centerPanel, RESULTS_PANEL);
 
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java
index ea51c350b..677fa6706 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java
@@ -27,11 +27,15 @@ package net.runelite.client.plugins.grounditems;
 import java.awt.Color;
 import java.time.Duration;
 import java.time.Instant;
-import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import lombok.Builder;
 import lombok.Data;
+import static net.runelite.api.TileItem.OWNERSHIP_GROUP;
+import static net.runelite.api.TileItem.OWNERSHIP_NONE;
+import static net.runelite.api.TileItem.OWNERSHIP_OTHER;
+import static net.runelite.api.TileItem.OWNERSHIP_SELF;
 import net.runelite.api.coords.WorldPoint;
+import org.intellij.lang.annotations.MagicConstant;
 
 @Data
 @Builder
@@ -47,8 +51,9 @@ class GroundItem
 	private int gePrice;
 	private int offset;
 	private boolean tradeable;
-	@Nonnull
-	private LootType lootType;
+	@MagicConstant(intValues = {OWNERSHIP_NONE, OWNERSHIP_SELF, OWNERSHIP_OTHER, OWNERSHIP_GROUP})
+	private int ownership;
+	private boolean isPrivate;
 	@Nullable
 	private Instant spawnTime;
 	private boolean stackable;
@@ -72,7 +77,7 @@ class GroundItem
 
 	boolean isMine()
 	{
-		return lootType != LootType.UNKNOWN;
+		return ownership != OWNERSHIP_OTHER;
 	}
 
 	void reset()
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java
index 6a9c0216b..169350546 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsConfig.java
@@ -363,11 +363,11 @@ public interface GroundItemsConfig extends Config
 
 	@ConfigItem(
 		keyName = "onlyShowLoot",
-		name = "Only show loot",
-		description = "Only shows drops from NPCs and players",
+		name = "Only show own items",
+		description = "Only shows items that are yours or you can pick up",
 		position = 25
 	)
-	default boolean onlyShowLoot()
+	default boolean onlyShowOwnItems()
 	{
 		return false;
 	}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java
index eb6da70f0..38366542f 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java
@@ -168,7 +168,7 @@ public class GroundItemsOverlay extends Overlay
 		plugin.setHiddenBoxBounds(null);
 		plugin.setHighlightBoxBounds(null);
 
-		final boolean onlyShowLoot = config.onlyShowLoot();
+		final boolean onlyShowLoot = config.onlyShowOwnItems();
 		final DespawnTimerMode groundItemTimers = config.groundItemTimers();
 		final boolean outline = config.textOutline();
 
@@ -405,13 +405,24 @@ public class GroundItemsOverlay extends Overlay
 
 		final Instant now = Instant.now();
 		final Instant despawnTime = spawnTime.plus(groundItem.getDespawnTime());
-		final Instant visibleTime = spawnTime.plus(groundItem.getVisibleTime());
 
-		if (visibleTime != null && visibleTime.isAfter(now))
+		if (groundItem.isPrivate())
+		{
+			if (despawnTime.isAfter(now))
+			{
+				return PRIVATE_TIMER_COLOR;
+			}
+
+			return null;
+		}
+
+		// otherwise it is private until visibleTime, then it is public
+		final Instant visibleTime = spawnTime.plus(groundItem.getVisibleTime());
+		if (visibleTime.isAfter(now))
 		{
 			return PRIVATE_TIMER_COLOR;
 		}
-		if (despawnTime != null && despawnTime.isAfter(now))
+		if (despawnTime.isAfter(now))
 		{
 			return PUBLIC_TIMER_COLOR;
 		}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java
index 02326f703..253058dbf 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java
@@ -28,7 +28,6 @@ package net.runelite.client.plugins.grounditems;
 import com.google.common.base.MoreObjects;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.LoadingCache;
-import com.google.common.collect.EvictingQueue;
 import com.google.common.collect.HashBasedTable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Table;
@@ -47,7 +46,6 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Queue;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
@@ -76,8 +74,6 @@ import net.runelite.api.events.ItemDespawned;
 import net.runelite.api.events.ItemQuantityChanged;
 import net.runelite.api.events.ItemSpawned;
 import net.runelite.api.events.MenuEntryAdded;
-import net.runelite.api.events.MenuOptionClicked;
-import net.runelite.api.widgets.ComponentID;
 import net.runelite.client.Notifier;
 import net.runelite.client.callback.ClientThread;
 import net.runelite.client.config.ConfigManager;
@@ -194,8 +190,6 @@ public class GroundItemsPlugin extends Plugin
 	private List<PriceHighlight> priceChecks = ImmutableList.of();
 	private LoadingCache<NamedQuantity, Boolean> highlightedItems;
 	private LoadingCache<NamedQuantity, Boolean> hiddenItems;
-	private final Queue<Integer> droppedItemQueue = EvictingQueue.create(16); // recently dropped items
-	private int lastUsedItem;
 	private final Map<WorldPoint, Lootbeam> lootbeams = new HashMap<>();
 
 	@Provides
@@ -211,7 +205,6 @@ public class GroundItemsPlugin extends Plugin
 		mouseManager.registerMouseListener(mouseAdapter);
 		keyManager.registerKeyListener(hotkeyListener);
 		executor.execute(this::reset);
-		lastUsedItem = -1;
 	}
 
 	@Override
@@ -268,7 +261,7 @@ public class GroundItemsPlugin extends Plugin
 			collectedGroundItems.put(tile.getWorldLocation(), item.getId(), groundItem);
 		}
 
-		if (!config.onlyShowLoot())
+		if (!config.onlyShowOwnItems())
 		{
 			notifyHighlightedItem(groundItem);
 		}
@@ -343,14 +336,14 @@ public class GroundItemsPlugin extends Plugin
 	public void onNpcLootReceived(NpcLootReceived npcLootReceived)
 	{
 		Collection<ItemStack> items = npcLootReceived.getItems();
-		lootReceived(items, LootType.PVM);
+		lootReceived(items);
 	}
 
 	@Subscribe
 	public void onPlayerLootReceived(PlayerLootReceived playerLootReceived)
 	{
 		Collection<ItemStack> items = playerLootReceived.getItems();
-		lootReceived(items, LootType.PVP);
+		lootReceived(items);
 	}
 
 	@Subscribe
@@ -402,7 +395,7 @@ public class GroundItemsPlugin extends Plugin
 		}).toArray(MenuEntry[]::new));
 	}
 
-	private void lootReceived(Collection<ItemStack> items, LootType lootType)
+	private void lootReceived(Collection<ItemStack> items)
 	{
 		for (ItemStack itemStack : items)
 		{
@@ -410,9 +403,7 @@ public class GroundItemsPlugin extends Plugin
 			GroundItem groundItem = collectedGroundItems.get(location, itemStack.getId());
 			if (groundItem != null)
 			{
-				groundItem.setLootType(lootType);
-
-				if (config.onlyShowLoot())
+				if (config.onlyShowOwnItems())
 				{
 					notifyHighlightedItem(groundItem);
 				}
@@ -434,8 +425,8 @@ public class GroundItemsPlugin extends Plugin
 		final ItemComposition itemComposition = itemManager.getItemComposition(itemId);
 		final int realItemId = itemComposition.getNote() != -1 ? itemComposition.getLinkedNoteId() : itemId;
 		final int alchPrice = itemComposition.getHaPrice();
-		final boolean dropped = tile.getWorldLocation().equals(client.getLocalPlayer().getWorldLocation()) && droppedItemQueue.remove(itemId);
-		final boolean table = itemId == lastUsedItem && tile.getItemLayer().getHeight() > 0;
+		final int despawnTime = item.getDespawnTime() - client.getTickCount();
+		final int visibleTime = item.getVisibleTime() - client.getTickCount();
 
 		final GroundItem groundItem = GroundItem.builder()
 			.id(itemId)
@@ -446,11 +437,12 @@ public class GroundItemsPlugin extends Plugin
 			.haPrice(alchPrice)
 			.height(tile.getItemLayer().getHeight())
 			.tradeable(itemComposition.isTradeable())
-			.lootType(dropped ? LootType.DROPPED : (table ? LootType.TABLE : LootType.UNKNOWN))
+			.ownership(item.getOwnership())
+			.isPrivate(item.isPrivate())
 			.spawnTime(Instant.now())
 			.stackable(itemComposition.isStackable())
-			.despawnTime(Duration.of(item.getDespawnTime(), RSTimeUnit.GAME_TICKS))
-			.visibleTime(Duration.of(item.getVisibleTime(), RSTimeUnit.GAME_TICKS))
+			.despawnTime(Duration.of(despawnTime, RSTimeUnit.GAME_TICKS))
+			.visibleTime(Duration.of(visibleTime, RSTimeUnit.GAME_TICKS))
 			.build();
 
 		// Update item price in case it is coins
@@ -770,24 +762,6 @@ public class GroundItemsPlugin extends Plugin
 		}
 	}
 
-	@Subscribe
-	public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked)
-	{
-		if (menuOptionClicked.isItemOp() && menuOptionClicked.getMenuOption().equals("Drop"))
-		{
-			int itemId = menuOptionClicked.getItemId();
-			// Keep a queue of recently dropped items to better detect
-			// item spawns that are drops
-			droppedItemQueue.add(itemId);
-		}
-		else if (menuOptionClicked.getMenuAction() == MenuAction.WIDGET_TARGET_ON_GAME_OBJECT
-			&& client.getSelectedWidget() != null
-			&& client.getSelectedWidget().getId() == ComponentID.INVENTORY_CONTAINER)
-		{
-			lastUsedItem = client.getSelectedWidget().getItemId();
-		}
-	}
-
 	private void handleLootbeam(WorldPoint worldPoint)
 	{
 		/*
@@ -805,7 +779,7 @@ public class GroundItemsPlugin extends Plugin
 		Collection<GroundItem> groundItems = collectedGroundItems.row(worldPoint).values();
 		for (GroundItem groundItem : groundItems)
 		{
-			if ((config.onlyShowLoot() && !groundItem.isMine()))
+			if ((config.onlyShowOwnItems() && !groundItem.isMine()))
 			{
 				continue;
 			}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/LootType.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/LootType.java
deleted file mode 100644
index 604815732..000000000
--- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/LootType.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (c) 2019, Adam <Adam@sigterm.info>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this
- *    list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- *    this list of conditions and the following disclaimer in the documentation
- *    and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package net.runelite.client.plugins.grounditems;
-
-enum LootType
-{
-	UNKNOWN,
-	TABLE,
-	DROPPED,
-	PVP,
-	PVM;
-}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java
index 5e711f4ea..79361c1d9 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java
@@ -52,6 +52,7 @@ import net.runelite.api.events.GraphicChanged;
 import net.runelite.api.events.HitsplatApplied;
 import net.runelite.api.events.InteractingChanged;
 import net.runelite.api.events.NpcChanged;
+import net.runelite.api.events.VarbitChanged;
 import net.runelite.client.Notifier;
 import net.runelite.client.config.ConfigManager;
 import net.runelite.client.eventbus.Subscribe;
@@ -103,6 +104,7 @@ public class IdleNotifierPlugin extends Plugin
 	private Instant sixHourWarningTime;
 	private boolean ready;
 	private boolean lastInteractWasCombat;
+	private static final int BUFF_BAR_NOT_DISPLAYED = -1;
 
 	@Provides
 	IdleNotifierConfig provideConfig(ConfigManager configManager)
@@ -575,6 +577,17 @@ public class IdleNotifierPlugin extends Plugin
 		}
 	}
 
+	@Subscribe
+	public void onVarbitChanged(VarbitChanged event)
+	{
+		if (event.getVarpId() == VarPlayer.BUFF_BAR_WC_GROUP_BONUS && event.getValue() == BUFF_BAR_NOT_DISPLAYED)
+		{
+			resetTimers();
+			lastAnimation = WOODCUTTING_RUNE;
+			lastAnimating = Instant.now();
+		}
+	}
+
 	private void checkNpcInteraction(final NPC target)
 	{
 		final NPCComposition npcComposition = target.getComposition();
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingPlugin.java
index 64b8c0f3a..01d83b07d 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/keyremapping/KeyRemappingPlugin.java
@@ -111,6 +111,10 @@ public class KeyRemappingPlugin extends Plugin
 		return configManager.getConfig(KeyRemappingConfig.class);
 	}
 
+	/**
+	 * Check if something other than the chatbox is accepting key input.
+	 * @return
+	 */
 	boolean chatboxFocused()
 	{
 		Widget chatboxParent = client.getWidget(ComponentID.CHATBOX_PARENT);
@@ -119,10 +123,22 @@ public class KeyRemappingPlugin extends Plugin
 			return false;
 		}
 
-		// the search box on the world map can be focused, and chat input goes there, even
-		// though the chatbox still has its key listener.
+		// If the search box on the world map is open and focused, ~keypress_permit blocks the keypress
 		Widget worldMapSearch = client.getWidget(ComponentID.WORLD_MAP_SEARCH);
-		return worldMapSearch == null || client.getVarcIntValue(VarClientInt.WORLD_MAP_SEARCH_FOCUSED) != 1;
+		if (worldMapSearch != null && client.getVarcIntValue(VarClientInt.WORLD_MAP_SEARCH_FOCUSED) == 1)
+		{
+			return false;
+		}
+
+		// The report interface blocks input due to 162:54 being hidden, however player/npc dialog and
+		// options do this too, and so we can't disable remapping just due to 162:54 being hidden.
+		Widget report = client.getWidget(ComponentID.REPORT_ABUSE_PARENT);
+		if (report != null)
+		{
+			return false;
+		}
+
+		return true;
 	}
 
 	/**
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 5ff0061cc..3bf802401 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
@@ -181,6 +181,7 @@ public class LootTrackerPlugin extends Plugin
 		put(7827, "Dark Chest").
 		put(13117, "Rogues' Chest").
 		put(13156, "Chest (Ancient Vault)").
+		put(12348, "Muddy Chest").
 		build();
 
 	// Chests opened with keys from slayer tasks
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java
index a41428b61..82745513e 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java
@@ -427,7 +427,7 @@ public interface MenuEntrySwapperConfig extends Config
 	@ConfigItem(
 		keyName = "teleportSubmenus",
 		name = "Teleport submenus",
-		description = "Use submenus for max, construction, and diary cape teleports",
+		description = "Use submenus for max, construction, hunter, and diary cape teleports",
 		section = itemSection
 	)
 	default boolean teleportSubmenus()
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java
index 0cb4bc19c..124777483 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java
@@ -65,7 +65,6 @@ import net.runelite.api.annotations.Component;
 import net.runelite.api.events.ClientTick;
 import net.runelite.api.events.MenuEntryAdded;
 import net.runelite.api.events.MenuOpened;
-import net.runelite.api.events.PostItemComposition;
 import net.runelite.api.events.PostMenuSort;
 import net.runelite.api.widgets.ComponentID;
 import net.runelite.api.widgets.InterfaceID;
@@ -78,7 +77,6 @@ import net.runelite.client.chat.ChatMessageManager;
 import net.runelite.client.chat.QueuedMessage;
 import net.runelite.client.config.ConfigManager;
 import net.runelite.client.eventbus.Subscribe;
-import net.runelite.client.events.ConfigChanged;
 import net.runelite.client.game.ItemManager;
 import net.runelite.client.game.ItemVariationMapping;
 import net.runelite.client.game.NpcUtil;
@@ -166,6 +164,7 @@ public class MenuEntrySwapperPlugin extends Plugin
 	private final Multimap<String, Swap> swaps = LinkedHashMultimap.create();
 	private final ArrayListMultimap<String, Integer> optionIndexes = ArrayListMultimap.create();
 	private final Multimap<Integer, TeleportSwap> teleportSwaps = HashMultimap.create();
+	private boolean lastShift, curShift;
 
 	@Provides
 	MenuEntrySwapperConfig provideConfig(ConfigManager configManager)
@@ -422,24 +421,6 @@ public class MenuEntrySwapperPlugin extends Plugin
 		swaps.put(option, new Swap(alwaysTrue(), targetPredicate, swappedOption, enabled, false));
 	}
 
-	@Subscribe
-	public void onConfigChanged(ConfigChanged event)
-	{
-		if (event.getGroup().equals(MenuEntrySwapperConfig.GROUP) && event.getKey().equals("shiftClickCustomization"))
-		{
-			clientThread.invoke(this::resetItemCompositionCache);
-		}
-		else if (event.getGroup().equals(SHIFTCLICK_CONFIG_GROUP) && event.getKey().startsWith(ITEM_KEY_PREFIX))
-		{
-			clientThread.invoke(this::resetItemCompositionCache);
-		}
-	}
-
-	private void resetItemCompositionCache()
-	{
-		client.getItemCompositionCache().reset();
-	}
-
 	private Integer getItemSwapConfig(boolean shift, int itemId)
 	{
 		itemId = ItemVariationMapping.map(itemId);
@@ -898,27 +879,30 @@ public class MenuEntrySwapperPlugin extends Plugin
 						}
 					}
 
-					var subSwaps = teleportSwaps.get(itemComposition.getId())
-						.stream()
-						.filter(ts -> ts.worn)
-						.collect(Collectors.toList());
-					for (TeleportSwap top : subSwaps)
+					if (config.teleportSubmenus())
 					{
-						for (TeleportSub sub : top.subs)
+						var subSwaps = teleportSwaps.get(itemComposition.getId())
+							.stream()
+							.filter(ts -> ts.worn)
+							.collect(Collectors.toList());
+						for (TeleportSwap top : subSwaps)
 						{
-							if (leftClickOp == null || leftClickOp != sub.option.hashCode())
-							{
-								leftClickMenus.add(client.createMenuEntry(idx)
-									.setOption(sub.option)
-									.setType(MenuAction.RUNELITE)
-									.onClick(wornItemConsumer(itemComposition, sub.option, sub.option.hashCode(), false)));
-							}
-							if (shiftClickOp == null || shiftClickOp != sub.option.hashCode())
+							for (TeleportSub sub : top.subs)
 							{
-								shiftClickMenus.add(client.createMenuEntry(idx)
-									.setOption(sub.option)
-									.setType(MenuAction.RUNELITE)
-									.onClick(wornItemConsumer(itemComposition, sub.option, sub.option.hashCode(), true)));
+								if (leftClickOp == null || leftClickOp != sub.option.hashCode())
+								{
+									leftClickMenus.add(client.createMenuEntry(idx)
+										.setOption(sub.option)
+										.setType(MenuAction.RUNELITE)
+										.onClick(wornItemConsumer(itemComposition, sub.option, sub.option.hashCode(), false)));
+								}
+								if (shiftClickOp == null || shiftClickOp != sub.option.hashCode())
+								{
+									shiftClickMenus.add(client.createMenuEntry(idx)
+										.setOption(sub.option)
+										.setType(MenuAction.RUNELITE)
+										.onClick(wornItemConsumer(itemComposition, sub.option, sub.option.hashCode(), true)));
+								}
 							}
 						}
 					}
@@ -1081,27 +1065,30 @@ public class MenuEntrySwapperPlugin extends Plugin
 					}
 				}
 
-				var subSwaps = teleportSwaps.get(itemComposition.getId())
-					.stream()
-					.filter(ts -> ts.held)
-					.collect(Collectors.toList());
-				for (TeleportSwap top : subSwaps)
+				if (config.teleportSubmenus())
 				{
-					for (TeleportSub sub : top.subs)
+					var subSwaps = teleportSwaps.get(itemComposition.getId())
+						.stream()
+						.filter(ts -> ts.held)
+						.collect(Collectors.toList());
+					for (TeleportSwap top : subSwaps)
 					{
-						if (leftClickOp == null || leftClickOp != sub.option.hashCode())
-						{
-							leftClickMenus.add(client.createMenuEntry(idx)
-								.setOption(sub.option)
-								.setType(MenuAction.RUNELITE)
-								.onClick(heldItemConsumer(itemComposition, sub.option, sub.option.hashCode(), false)));
-						}
-						if (shiftClickOp == null || shiftClickOp != sub.option.hashCode())
+						for (TeleportSub sub : top.subs)
 						{
-							shiftClickMenus.add(client.createMenuEntry(idx)
-								.setOption(sub.option)
-								.setType(MenuAction.RUNELITE)
-								.onClick(heldItemConsumer(itemComposition, sub.option, sub.option.hashCode(), true)));
+							if (leftClickOp == null || leftClickOp != sub.option.hashCode())
+							{
+								leftClickMenus.add(client.createMenuEntry(idx)
+									.setOption(sub.option)
+									.setType(MenuAction.RUNELITE)
+									.onClick(heldItemConsumer(itemComposition, sub.option, sub.option.hashCode(), false)));
+							}
+							if (shiftClickOp == null || shiftClickOp != sub.option.hashCode())
+							{
+								shiftClickMenus.add(client.createMenuEntry(idx)
+									.setOption(sub.option)
+									.setType(MenuAction.RUNELITE)
+									.onClick(heldItemConsumer(itemComposition, sub.option, sub.option.hashCode(), true)));
+							}
 						}
 					}
 				}
@@ -1474,36 +1461,11 @@ public class MenuEntrySwapperPlugin extends Plugin
 		final String target = Text.removeTags(menuEntry.getTarget()).toLowerCase();
 
 		final Widget w = menuEntry.getParent() != null ? menuEntry.getParent().getWidget() : menuEntry.getWidget();
-		// Custom shift-click item swap
-		if (shiftModifier() && w != null && WidgetUtil.componentToInterface(w.getId()) == InterfaceID.INVENTORY)
-		{
-			if (config.shiftClickCustomization() && !option.equals("use"))
-			{
-				Integer customOption = getItemSwapConfig(true, w.getItemId());
-
-				// Special case use shift click due to items not actually containing a "Use" option, making
-				// the client unable to perform the swap itself.
-				if (customOption != null && customOption == -1)
-				{
-					swap(menuEntries, "use", target, index, true);
-				}
-				else if (customOption != null && menuEntry.getParent() != null && menuEntry.getOption().hashCode() == customOption)
-				{
-					swap(optionIndexes, menuEntries, index, menuEntries.length - 1);
-					menuEntry.setParent(null);
-				}
-			}
-
-			// don't perform swaps on items when shift is held; instead prefer the client menu swap, which
-			// we may have overwrote
-			return;
-		}
-
-		// Custom left-click item swap
+		// Custom item swap
 		if (w != null && WidgetUtil.componentToInterface(w.getId()) == InterfaceID.INVENTORY
-			&& config.leftClickCustomization())
+			&& (lastShift ? config.shiftClickCustomization() : config.leftClickCustomization()))
 		{
-			Integer swapIndex = getItemSwapConfig(false, w.getItemId());
+			Integer swapIndex = getItemSwapConfig(lastShift, w.getItemId());
 			if (swapIndex != null)
 			{
 				if (swapIndex == -1)
@@ -1643,6 +1605,9 @@ public class MenuEntrySwapperPlugin extends Plugin
 	@Subscribe
 	public void onClientTick(ClientTick clientTick)
 	{
+		lastShift = curShift;
+		curShift = shiftModifier();
+
 		if (client.isMenuOpen())
 		{
 			return;
@@ -1758,25 +1723,6 @@ public class MenuEntrySwapperPlugin extends Plugin
 		}
 	}
 
-	@Subscribe
-	public void onPostItemComposition(PostItemComposition event)
-	{
-		if (!config.shiftClickCustomization())
-		{
-			// since shift-click is done by the client we have to check if our shift click customization is on
-			// prior to altering the item shift click action index.
-			return;
-		}
-
-		ItemComposition itemComposition = event.getItemComposition();
-		Integer option = getItemSwapConfig(true, itemComposition.getId());
-
-		if (option != null && option >= 0 && option < itemComposition.getInventoryActions().length)
-		{
-			itemComposition.setShiftClickActionIndex(option);
-		}
-	}
-
 	private boolean swap(MenuEntry[] menuEntries, String option, String target, int index, boolean strict)
 	{
 		// find option to swap with
@@ -2096,15 +2042,20 @@ public class MenuEntrySwapperPlugin extends Plugin
 				pauseresume(ComponentID.ADVENTURE_LOG_OPTIONS, 6); // POH Portals
 				pauseresume(ComponentID.ADVENTURE_LOG_OPTIONS, 5);
 			})
-			.addSub("Yanille", () ->
+			.addSub("Brimhaven", () ->
 			{
 				pauseresume(ComponentID.ADVENTURE_LOG_OPTIONS, 6); // POH Portals
 				pauseresume(ComponentID.ADVENTURE_LOG_OPTIONS, 6);
 			})
-			.addSub("Prifddinas", () ->
+			.addSub("Yanille", () ->
 			{
 				pauseresume(ComponentID.ADVENTURE_LOG_OPTIONS, 6); // POH Portals
 				pauseresume(ComponentID.ADVENTURE_LOG_OPTIONS, 7);
+			})
+			.addSub("Prifddinas", () ->
+			{
+				pauseresume(ComponentID.ADVENTURE_LOG_OPTIONS, 6); // POH Portals
+				pauseresume(ComponentID.ADVENTURE_LOG_OPTIONS, 8);
 			});
 		// endregion
 
@@ -2144,6 +2095,15 @@ public class MenuEntrySwapperPlugin extends Plugin
 			.addSub("Elder Gnome child", () -> pauseresume(ComponentID.ADVENTURE_LOG_OPTIONS, 14))
 			.addSub("Twiggy O'Korn", () -> pauseresume(ComponentID.ADVENTURE_LOG_OPTIONS, 15));
 		// endregion
+
+		// region Hunter cape
+		teleportSwap("Teleport", ItemID.HUNTER_CAPE, ItemID.HUNTER_CAPET)
+			.worn()
+			.held()
+			.addSub("Carnivorous Chinchompas", () -> pauseresume(ComponentID.DIALOG_OPTION_OPTIONS, 1))
+			.addSub("Black Chinchompas", () -> pauseresume(ComponentID.DIALOG_OPTION_OPTIONS, 2))
+			.addSub("Hunter Guild", () -> pauseresume(ComponentID.DIALOG_OPTION_OPTIONS, 3));
+		// endregion
 	}
 
 	private TeleportSwap teleportSwap(String option, int... items)
@@ -2186,7 +2146,7 @@ public class MenuEntrySwapperPlugin extends Plugin
 				final String target = me.getTarget();
 				for (TeleportSub sub : swap.subs)
 				{
-					client.createMenuEntry(-1 - off++)
+					client.createMenuEntry(-2 - off++)
 						.setParam0(p0)
 						.setParam1(p1)
 						.setOption(sub.option)
@@ -2224,7 +2184,7 @@ public class MenuEntrySwapperPlugin extends Plugin
 				final String target = me.getTarget();
 				for (TeleportSub sub : swap.subs)
 				{
-					client.createMenuEntry(-1 - off++)
+					client.createMenuEntry(-2 - off++)
 						.setParam0(p0)
 						.setParam1(p1)
 						.setOption(sub.option)
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningPlugin.java
index f9ad5c4c9..cfa9a4d25 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningPlugin.java
@@ -35,6 +35,7 @@ import javax.inject.Inject;
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
 import net.runelite.api.AnimationID;
 import net.runelite.api.ChatMessageType;
 import net.runelite.api.Client;
@@ -42,26 +43,9 @@ import net.runelite.api.Constants;
 import net.runelite.api.GameObject;
 import net.runelite.api.GameState;
 import net.runelite.api.HintArrowType;
-import static net.runelite.api.ObjectID.BARRONITE_ROCKS;
-import static net.runelite.api.ObjectID.BARRONITE_ROCKS_41548;
-import static net.runelite.api.ObjectID.DEPLETED_VEIN;
-import static net.runelite.api.ObjectID.DEPLETED_VEIN_26665;
-import static net.runelite.api.ObjectID.DEPLETED_VEIN_26666;
-import static net.runelite.api.ObjectID.DEPLETED_VEIN_26667;
-import static net.runelite.api.ObjectID.DEPLETED_VEIN_26668;
-import static net.runelite.api.ObjectID.EMPTY_WALL;
-import static net.runelite.api.ObjectID.GOLD_VEIN;
-import static net.runelite.api.ObjectID.GOLD_VEIN_5990;
-import static net.runelite.api.ObjectID.GOLD_VEIN_5991;
-import static net.runelite.api.ObjectID.ORE_VEIN;
-import static net.runelite.api.ObjectID.ORE_VEIN_26662;
-import static net.runelite.api.ObjectID.ORE_VEIN_26663;
-import static net.runelite.api.ObjectID.ORE_VEIN_26664;
-import static net.runelite.api.ObjectID.ROCKS_41549;
-import static net.runelite.api.ObjectID.ROCKS_41550;
+import static net.runelite.api.ObjectID.*;
 import net.runelite.api.Player;
 import net.runelite.api.ScriptID;
-import net.runelite.api.WallObject;
 import net.runelite.api.coords.WorldPoint;
 import net.runelite.api.events.AnimationChanged;
 import net.runelite.api.events.ChatMessage;
@@ -70,7 +54,6 @@ import net.runelite.api.events.GameObjectSpawned;
 import net.runelite.api.events.GameStateChanged;
 import net.runelite.api.events.GameTick;
 import net.runelite.api.events.ScriptPreFired;
-import net.runelite.api.events.WallObjectSpawned;
 import net.runelite.client.config.ConfigManager;
 import net.runelite.client.eventbus.Subscribe;
 import net.runelite.client.plugins.Plugin;
@@ -78,6 +61,7 @@ import net.runelite.client.plugins.PluginDependency;
 import net.runelite.client.plugins.PluginDescriptor;
 import net.runelite.client.plugins.xptracker.XpTrackerPlugin;
 import net.runelite.client.ui.overlay.OverlayManager;
+import net.runelite.client.util.RSTimeUnit;
 
 @PluginDescriptor(
 	name = "Mining",
@@ -86,6 +70,7 @@ import net.runelite.client.ui.overlay.OverlayManager;
 	enabledByDefault = false
 )
 @PluginDependency(XpTrackerPlugin.class)
+@Slf4j
 public class MiningPlugin extends Plugin
 {
 	private static final Pattern MINING_PATTERN = Pattern.compile(
@@ -118,7 +103,6 @@ public class MiningPlugin extends Plugin
 
 	@Getter(AccessLevel.PACKAGE)
 	private final List<RockRespawn> respawns = new ArrayList<>();
-	private boolean recentlyLoggedIn;
 
 	@Getter
 	@Nullable
@@ -151,17 +135,9 @@ public class MiningPlugin extends Plugin
 	@Subscribe
 	public void onGameStateChanged(GameStateChanged event)
 	{
-		switch (event.getGameState())
+		if (event.getGameState() == GameState.HOPPING)
 		{
-			case HOPPING:
-				respawns.clear();
-				break;
-			case LOGGED_IN:
-				// After login rocks that are depleted will be changed,
-				// so wait for the next game tick before watching for
-				// rocks to deplete
-				recentlyLoggedIn = true;
-				break;
+			respawns.clear();
 		}
 	}
 
@@ -201,7 +177,6 @@ public class MiningPlugin extends Plugin
 	public void onGameTick(GameTick gameTick)
 	{
 		clearExpiredRespawns();
-		recentlyLoggedIn = false;
 
 		if (session == null || session.getLastMined() == null)
 		{
@@ -250,29 +225,13 @@ public class MiningPlugin extends Plugin
 	@Subscribe
 	public void onGameObjectDespawned(GameObjectDespawned event)
 	{
-		if (client.getGameState() != GameState.LOGGED_IN || recentlyLoggedIn)
-		{
-			return;
-		}
-
 		final GameObject object = event.getGameObject();
-		final int region = client.getLocalPlayer().getWorldLocation().getRegionID();
 
-		Rock rock = Rock.getRock(object.getId());
-		if (rock != null)
+		if (object.getId() == DAEYALT_ESSENCE_39095)
 		{
 			final WorldPoint point = object.getWorldLocation();
-
-			if (rock == Rock.DAEYALT_ESSENCE)
-			{
-				respawns.removeIf(rockRespawn -> rockRespawn.getWorldPoint().equals(point));
-				clearHintArrowAt(point);
-			}
-			else
-			{
-				RockRespawn rockRespawn = new RockRespawn(rock, point, Instant.now(), (int) rock.getRespawnTime(region).toMillis(), rock.getZOffset());
-				respawns.add(rockRespawn);
-			}
+			respawns.removeIf(rockRespawn -> rockRespawn.getWorldPoint().equals(point));
+			clearHintArrowAt(point);
 		}
 	}
 
@@ -287,81 +246,16 @@ public class MiningPlugin extends Plugin
 	@Subscribe
 	public void onGameObjectSpawned(GameObjectSpawned event)
 	{
-		if (client.getGameState() != GameState.LOGGED_IN || recentlyLoggedIn)
-		{
-			return;
-		}
-
 		GameObject object = event.getGameObject();
-		Rock rock = Rock.getRock(object.getId());
 
 		// Inverse timer to track daeyalt essence active duration
-		if (rock == Rock.DAEYALT_ESSENCE)
+		if (object.getId() == DAEYALT_ESSENCE_39095)
 		{
-			final int region = client.getLocalPlayer().getWorldLocation().getRegionID();
-			RockRespawn rockRespawn = new RockRespawn(rock, object.getWorldLocation(), Instant.now(), (int) rock.getRespawnTime(region).toMillis(), rock.getZOffset());
+			RockRespawn rockRespawn = new RockRespawn(Rock.DAEYALT_ESSENCE, object.getWorldLocation(), Instant.now(),
+				(int) Duration.of(MiningRocksOverlay.DAEYALT_MAX_RESPAWN_TIME, RSTimeUnit.GAME_TICKS).toMillis(), Rock.DAEYALT_ESSENCE.getZOffset());
 			respawns.add(rockRespawn);
 			client.setHintArrow(object.getWorldLocation());
 		}
-		// If the Lovakite ore respawns before the timer is up, remove it
-		else if (rock == Rock.LOVAKITE || rock == Rock.CALCIFIED_ROCK)
-		{
-			final WorldPoint point = object.getWorldLocation();
-			respawns.removeIf(rockRespawn -> rockRespawn.getWorldPoint().equals(point));
-		}
-	}
-
-	@Subscribe
-	public void onWallObjectSpawned(WallObjectSpawned event)
-	{
-		if (client.getGameState() != GameState.LOGGED_IN)
-		{
-			return;
-		}
-
-		final WallObject object = event.getWallObject();
-		final int region = client.getLocalPlayer().getWorldLocation().getRegionID();
-
-		switch (object.getId())
-		{
-			case EMPTY_WALL:
-			{
-				Rock rock = Rock.AMETHYST;
-				RockRespawn rockRespawn = new RockRespawn(rock, object.getWorldLocation(), Instant.now(), (int) rock.getRespawnTime(region).toMillis(), rock.getZOffset());
-				respawns.add(rockRespawn);
-				break;
-			}
-			case ROCKS_41549: // Depleted barronite vein
-			case ROCKS_41550: // Depleted barronite vein
-			{
-				Rock rock = Rock.BARRONITE;
-				RockRespawn rockRespawn = new RockRespawn(rock, object.getWorldLocation(), Instant.now(), (int) rock.getRespawnTime(region).toMillis(), rock.getZOffset());
-				respawns.add(rockRespawn);
-				break;
-			}
-			case DEPLETED_VEIN: // Depleted gold vein
-			{
-				Rock rock = Rock.MINERAL_VEIN;
-				RockRespawn rockRespawn = new RockRespawn(rock, object.getWorldLocation(), Instant.now(), (int) rock.getRespawnTime(region).toMillis(), rock.getZOffset());
-				respawns.add(rockRespawn);
-				break;
-			}
-			case ORE_VEIN: // Motherlode vein
-			case ORE_VEIN_26662: // Motherlode vein
-			case ORE_VEIN_26663: // Motherlode vein
-			case ORE_VEIN_26664: // Motherlode vein
-			case BARRONITE_ROCKS: // Barronite vein
-			case BARRONITE_ROCKS_41548: // Barronite vein
-			case GOLD_VEIN: // Arzinian gold vein
-			case GOLD_VEIN_5990: // Gold vein
-			case GOLD_VEIN_5991: // Gold vein
-			{
-				// If the vein respawns before the timer is up, remove it
-				final WorldPoint point = object.getWorldLocation();
-				respawns.removeIf(rockRespawn -> rockRespawn.getWorldPoint().equals(point));
-				break;
-			}
-		}
 	}
 
 	@Subscribe
@@ -398,13 +292,46 @@ public class MiningPlugin extends Plugin
 				case DEPLETED_VEIN_26667: // Depleted motherlode vein
 				case DEPLETED_VEIN_26668: // Depleted motherlode vein
 				{
-					WorldPoint worldPoint = new WorldPoint((locCoord >>> 14) & 0x3FFF, locCoord & 0x3FFF, (locCoord >>> 28) & 0x3);
-					Rock rock = Rock.ORE_VEIN;
-					RockRespawn rockRespawn = new RockRespawn(rock, worldPoint, Instant.now(), ticks * Constants.GAME_TICK_LENGTH, rock.getZOffset());
-					respawns.add(rockRespawn);
+					addRockRespawn(Rock.MLM_ORE_VEIN, WorldPoint.fromCoord(locCoord), ticks);
+					break;
+				}
+				case DEPLETED_VEIN: // Gold vein
+				case ROCKS_51486: // Calcified rock
+				case ROCKS_51488: // Calcified rock
+				{
+					addRockRespawn(Rock.ORE_VEIN, WorldPoint.fromCoord(locCoord), ticks);
+					break;
+				}
+				case ROCKS_11390:
+				case ROCKS_11391:
+				case ROCKS_11392:
+				case ROCKS_33253: // Basalt etc
+				case EMPTY_ASH_PILE:
+				{
+					addRockRespawn(Rock.ROCK, WorldPoint.fromCoord(locCoord), ticks);
+					break;
+				}
+				// Amethyst
+				case EMPTY_WALL:
+				{
+					addRockRespawn(Rock.AMETHYST, WorldPoint.fromCoord(locCoord), ticks);
+					break;
+				}
+				// Barronite
+				case ROCKS_41549:
+				case ROCKS_41550:
+				{
+					addRockRespawn(Rock.BARRONITE, WorldPoint.fromCoord(locCoord), ticks);
 					break;
 				}
 			}
 		}
 	}
+
+	private void addRockRespawn(Rock rock, WorldPoint point, int ticks)
+	{
+		RockRespawn rockRespawn = new RockRespawn(rock, point, Instant.now(), ticks * Constants.GAME_TICK_LENGTH, rock.getZOffset());
+		respawns.add(rockRespawn);
+		log.debug("Adding respawn for rock: {} coord: {} ticks: {}", rock, point, ticks);
+	}
 }
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningRocksOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningRocksOverlay.java
index 455832c7b..84c597da7 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningRocksOverlay.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningRocksOverlay.java
@@ -45,10 +45,6 @@ class MiningRocksOverlay extends Overlay
 	private static final int DAEYALT_MIN_RESPAWN_TIME = 91; // Game ticks
 	private static final float DAEYALT_RANDOM_PERCENT_THRESHOLD = (float) DAEYALT_MIN_RESPAWN_TIME / DAEYALT_MAX_RESPAWN_TIME;
 
-	static final int LOVAKITE_ORE_MAX_RESPAWN_TIME = 65; // Game ticks
-	private static final int LOVAKITE_ORE_MIN_RESPAWN_TIME = 50; // Game ticks
-	private static final float LOVAKITE_ORE_RANDOM_PERCENT_THRESHOLD = (float) LOVAKITE_ORE_MIN_RESPAWN_TIME / LOVAKITE_ORE_MAX_RESPAWN_TIME;
-
 	private static final Color DARK_GREEN = new Color(0, 100, 0);
 	private static final int MOTHERLODE_UPPER_FLOOR_HEIGHT = -500;
 
@@ -93,7 +89,7 @@ class MiningRocksOverlay extends Overlay
 
 			// Only draw timer for veins on the same level in motherlode mine
 			LocalPoint localLocation = client.getLocalPlayer().getLocalLocation();
-			if (rock == Rock.ORE_VEIN && isUpstairsMotherlode(localLocation) != isUpstairsMotherlode(loc))
+			if (rock == Rock.MLM_ORE_VEIN && isUpstairsMotherlode(localLocation) != isUpstairsMotherlode(loc))
 			{
 				continue;
 			}
@@ -102,8 +98,7 @@ class MiningRocksOverlay extends Overlay
 			Color pieBorderColor = Color.ORANGE;
 
 			// Recolour pie during the portion of the timer where they may respawn
-			if ((rock == Rock.DAEYALT_ESSENCE && percent > DAEYALT_RANDOM_PERCENT_THRESHOLD)
-				|| (rock == Rock.LOVAKITE && percent > LOVAKITE_ORE_RANDOM_PERCENT_THRESHOLD))
+			if (rock == Rock.DAEYALT_ESSENCE && percent > DAEYALT_RANDOM_PERCENT_THRESHOLD)
 			{
 				pieFillColor = Color.GREEN;
 				pieBorderColor = DARK_GREEN;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mining/Rock.java b/runelite-client/src/main/java/net/runelite/client/plugins/mining/Rock.java
index bb5e4b4c1..03a2fb164 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/mining/Rock.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mining/Rock.java
@@ -24,121 +24,24 @@
  */
 package net.runelite.client.plugins.mining;
 
-import com.google.common.collect.ImmutableMap;
-import java.time.Duration;
-import java.util.Map;
 import lombok.AccessLevel;
 import lombok.Getter;
-import static net.runelite.api.ObjectID.*;
-import static net.runelite.client.util.RSTimeUnit.*;
 
 enum Rock
 {
-	TIN(Duration.of(4, GAME_TICKS), 0, TIN_ROCKS_11360, TIN_ROCKS_11361),
-	COPPER(Duration.of(4, GAME_TICKS), 0, COPPER_ROCKS_10943, COPPER_ROCKS_11161),
-	IRON(Duration.of(9, GAME_TICKS), 0, IRON_ROCKS, IRON_ROCKS_11365, IRON_ROCKS_36203)
-		{
-			@Override
-			Duration getRespawnTime(int region)
-			{
-				return region == MINING_GUILD ? Duration.of(4, GAME_TICKS) : super.respawnTime;
-			}
-		},
-	COAL(Duration.of(49, GAME_TICKS), 0, COAL_ROCKS_11366, COAL_ROCKS_11367, COAL_ROCKS_36204)
-		{
-			@Override
-			Duration getRespawnTime(int region)
-			{
-				switch (region)
-				{
-					case MINING_GUILD:
-						return Duration.of(24, GAME_TICKS);
-					case MISCELLANIA:
-						return Duration.of(11, GAME_TICKS);
-					default:
-						return super.respawnTime;
-				}
-			}
-		},
-	SILVER(Duration.of(100, GAME_TICKS), 0, SILVER_ROCKS, SILVER_ROCKS_11369, SILVER_ROCKS_36205),
-	SANDSTONE(Duration.of(9, GAME_TICKS), 0, SANDSTONE_ROCKS),
-	GOLD(Duration.of(100, GAME_TICKS), 0, GOLD_ROCKS, GOLD_ROCKS_11371, GOLD_ROCKS_36206),
-	GRANITE(Duration.of(9, GAME_TICKS), 0, GRANITE_ROCKS),
-	MITHRIL(Duration.of(200, GAME_TICKS), 0, MITHRIL_ROCKS, MITHRIL_ROCKS_11373, MITHRIL_ROCKS_36207)
-		{
-			@Override
-			Duration getRespawnTime(int region)
-			{
-				return region == MINING_GUILD ? Duration.of(100, GAME_TICKS) : super.respawnTime;
-			}
-		},
-	LOVAKITE(Duration.of(MiningRocksOverlay.LOVAKITE_ORE_MAX_RESPAWN_TIME, GAME_TICKS), 0, LOVAKITE_ROCKS, LOVAKITE_ROCKS_28597),
-	ADAMANTITE(Duration.of(400, GAME_TICKS), 0, ADAMANTITE_ROCKS, ADAMANTITE_ROCKS_11375, ADAMANTITE_ROCKS_36208)
-		{
-			@Override
-			Duration getRespawnTime(int region)
-			{
-				return region == MINING_GUILD || region == WILDERNESS_RESOURCE_AREA ? Duration.of(200, GAME_TICKS) : super.respawnTime;
-			}
-		},
-	RUNITE(Duration.of(1200, GAME_TICKS), 0, RUNITE_ROCKS, RUNITE_ROCKS_11377, RUNITE_ROCKS_36209)
-		{
-			@Override
-			Duration getRespawnTime(int region)
-			{
-				return region == MINING_GUILD ? Duration.of(600, GAME_TICKS) : super.respawnTime;
-			}
-		},
-	ORE_VEIN(Duration.ZERO /* unknown */, 150),
-	AMETHYST(Duration.of(125, GAME_TICKS), 120),
-	ASH_VEIN(Duration.of(50, GAME_TICKS), 0, ASH_PILE),
-	GEM_ROCK(Duration.of(99, GAME_TICKS), 0, GEM_ROCKS, GEM_ROCKS_11381),
-	URT_SALT(Duration.of(9, GAME_TICKS), 0, URT_SALT_ROCKS),
-	EFH_SALT(Duration.of(9, GAME_TICKS), 0, EFH_SALT_ROCKS),
-	TE_SALT(Duration.of(9, GAME_TICKS), 0, TE_SALT_ROCKS),
-	BASALT(Duration.of(9, GAME_TICKS), 0, BASALT_ROCKS),
-	DAEYALT_ESSENCE(Duration.of(MiningRocksOverlay.DAEYALT_MAX_RESPAWN_TIME, GAME_TICKS), 0, DAEYALT_ESSENCE_39095),
-	BARRONITE(Duration.of(89, GAME_TICKS), 140),
-	MINERAL_VEIN(Duration.of(100, GAME_TICKS), 150),
-	CALCIFIED_ROCK(Duration.of(60, GAME_TICKS), 150, CALCIFIED_ROCKS, CALCIFIED_ROCKS_51487, CALCIFIED_ROCKS_51489, CALCIFIED_ROCKS_51491);
+	ROCK(0),
+	MLM_ORE_VEIN(150),
+	ORE_VEIN(150),
+	AMETHYST(120),
+	DAEYALT_ESSENCE(0),
+	BARRONITE(140),
+	;
 
-	private static final int WILDERNESS_RESOURCE_AREA = 12605;
-	private static final int MISCELLANIA = 10044;
-	private static final int MINING_GUILD = 12183;
-	private static final Map<Integer, Rock> ROCKS;
-
-	static
-	{
-		ImmutableMap.Builder<Integer, Rock> builder = new ImmutableMap.Builder<>();
-		for (Rock rock : values())
-		{
-			for (int id : rock.ids)
-			{
-				builder.put(id, rock);
-			}
-		}
-		ROCKS = builder.build();
-	}
-
-	private final Duration respawnTime;
 	@Getter(AccessLevel.PACKAGE)
 	private final int zOffset;
-	private final int[] ids;
 
-	Rock(Duration respawnTime, int zOffset, int... ids)
+	Rock(int zOffset)
 	{
-		this.respawnTime = respawnTime;
 		this.zOffset = zOffset;
-		this.ids = ids;
-	}
-
-	Duration getRespawnTime(int region)
-	{
-		return respawnTime;
-	}
-
-	static Rock getRock(int id)
-	{
-		return ROCKS.get(id);
 	}
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mta/telekinetic/Maze.java b/runelite-client/src/main/java/net/runelite/client/plugins/mta/telekinetic/Maze.java
deleted file mode 100644
index 7e884dffc..000000000
--- a/runelite-client/src/main/java/net/runelite/client/plugins/mta/telekinetic/Maze.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (c) 2018, Jasper Ketelaar <Jasper0781@gmail.com>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this
- *    list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- *    this list of conditions and the following disclaimer in the documentation
- *    and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package net.runelite.client.plugins.mta.telekinetic;
-
-import net.runelite.api.coords.LocalPoint;
-
-public enum Maze
-{
-	MAZE_1(100, new LocalPoint(6848, 3904)),
-	MAZE_2(124, new LocalPoint(4928, 6848)),
-	MAZE_3(129, new LocalPoint(7104, 5312)),
-	MAZE_4(53, new LocalPoint(6208, 4928)),
-	MAZE_5(108, new LocalPoint(5056, 5184)),
-	MAZE_6(121, new LocalPoint(3648, 5440)),
-	MAZE_7(71, new LocalPoint(6080, 5696)),
-	MAZE_8(98, new LocalPoint(5952, 7360)),
-	MAZE_9(87, new LocalPoint(5184, 6208)),
-	MAZE_10(91, new LocalPoint(5440, 9024));
-
-	private final int walls;
-	private final LocalPoint start;
-
-	Maze(int walls, LocalPoint start)
-	{
-		this.walls = walls;
-		this.start = start;
-	}
-
-	public static Maze fromWalls(int walls)
-	{
-		for (Maze maze : values())
-		{
-			if (maze.getWalls() == walls)
-			{
-				return maze;
-			}
-		}
-
-		return null;
-	}
-
-	public int getWalls()
-	{
-		return walls;
-	}
-
-	public LocalPoint getStart()
-	{
-		return start;
-	}
-}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mta/telekinetic/TelekineticRoom.java b/runelite-client/src/main/java/net/runelite/client/plugins/mta/telekinetic/TelekineticRoom.java
index e985214fe..fb13df200 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/mta/telekinetic/TelekineticRoom.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mta/telekinetic/TelekineticRoom.java
@@ -81,7 +81,7 @@ public class TelekineticRoom extends MTARoom
 	private WorldPoint finishLocation;
 	private Rectangle bounds;
 	private NPC guardian;
-	private Maze maze;
+	private int numMazeWalls;
 
 	@Inject
 	private TelekineticRoom(MTAConfig config, Client client)
@@ -132,19 +132,17 @@ public class TelekineticRoom extends MTARoom
 	@Subscribe
 	public void onGameTick(GameTick event)
 	{
-		if (!config.telekinetic()
-				|| !inside()
-				|| client.getGameState() != GameState.LOGGED_IN)
+		if (!inside() || !config.telekinetic())
 		{
-			maze = null;
+			numMazeWalls = 0;
 			moves.clear();
 			return;
 		}
 
-		if (maze == null || telekineticWalls.size() != maze.getWalls())
+		if (telekineticWalls.size() != numMazeWalls)
 		{
 			bounds = getBounds(telekineticWalls.toArray(new WallObject[0]));
-			maze = Maze.fromWalls(telekineticWalls.size());
+			numMazeWalls = telekineticWalls.size();
 			client.clearHintArrow();
 		}
 		else if (guardian != null)
@@ -220,7 +218,7 @@ public class TelekineticRoom extends MTARoom
 	@Override
 	public void under(Graphics2D graphics2D)
 	{
-		if (inside() && maze != null && guardian != null)
+		if (inside() && numMazeWalls > 0 && guardian != null)
 		{
 			if (destination != null)
 			{
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 2e1ac43c4..244b75f1e 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
@@ -64,6 +64,7 @@ public enum HerbloreAction implements ItemSkillAction
 	SUPER_ENERGY_3(ItemID.SUPER_ENERGY3, 52, 117.5f),
 	HUNTER_POTION_3(ItemID.HUNTER_POTION3, 53, 120),
 	KWUARM(ItemID.KWUARM, 54, 11.3f),
+	IRIT_TAR(ItemID.IRIT_TAR, 55, 85),
 	SUPER_STRENGTH_3(ItemID.SUPER_STRENGTH3, 55, 125),
 	MAGIC_ESSENCE_POTION_3(ItemID.MAGIC_ESSENCE3, 57, 130),
 	SNAPDRAGON(ItemID.SNAPDRAGON, 59, 11.8f),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/spellbook/SpellbookPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/spellbook/SpellbookPlugin.java
index d45e16d3e..813643977 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/spellbook/SpellbookPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/spellbook/SpellbookPlugin.java
@@ -53,6 +53,7 @@ import net.runelite.client.chat.ChatMessageManager;
 import net.runelite.client.chat.QueuedMessage;
 import net.runelite.client.config.ConfigManager;
 import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.events.ProfileChanged;
 import net.runelite.client.menus.MenuManager;
 import net.runelite.client.menus.WidgetMenuOption;
 import net.runelite.client.plugins.Plugin;
@@ -126,6 +127,12 @@ public class SpellbookPlugin extends Plugin
 		clientThread.invokeLater(this::reinitializeSpellbook);
 	}
 
+	@Subscribe
+	public void onProfileChanged(ProfileChanged event)
+	{
+		clientThread.invokeLater(this::redrawSpellbook);
+	}
+
 	@Override
 	public void resetConfiguration()
 	{
@@ -373,11 +380,16 @@ public class SpellbookPlugin extends Plugin
 				{
 					Widget s = e.getSource();
 
-					boolean hidden = isHidden(spellbookEnum, spellObjId);
+					// Spells can be shared between spellbooks, so we can't assume spellbookEnum is the current spellbook.
+					// from ~magic_spellbook_redraw
+					int subSpellbookId = client.getEnum(EnumID.SPELLBOOKS_SUB).getIntValue(client.getVarbitValue(Varbits.SPELLBOOK));
+					int spellbookId = client.getEnum(subSpellbookId).getIntValue(client.getVarbitValue(Varbits.SPELLBOOK_SUBMENU));
+
+					boolean hidden = isHidden(spellbookId, spellObjId);
 					hidden = !hidden;
 
 					log.debug("Changing {} to hidden: {}", s.getName(), hidden);
-					setHidden(spellbookEnum, spellObjId, hidden);
+					setHidden(spellbookId, spellObjId, hidden);
 
 					s.setOpacity(hidden ? 100 : 0);
 					s.setAction(HIDE_UNHIDE_OP, hidden ? "Unhide" : "Hide");
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingOverlay.java
index 75c97a4f0..34f0f8186 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingOverlay.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingOverlay.java
@@ -31,6 +31,7 @@ import javax.inject.Inject;
 import net.runelite.api.Client;
 import static net.runelite.api.MenuAction.RUNELITE_OVERLAY;
 import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG;
+import net.runelite.api.VarPlayer;
 import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE;
 import net.runelite.client.ui.overlay.OverlayPanel;
 import net.runelite.client.ui.overlay.OverlayPosition;
@@ -40,6 +41,7 @@ import net.runelite.client.ui.overlay.components.TitleComponent;
 class WoodcuttingOverlay extends OverlayPanel
 {
 	private static final String WOODCUTTING_RESET = "Reset";
+	private static final int BUFF_BAR_DISPLAYED = 96;
 
 	private final Client client;
 	private final WoodcuttingPlugin plugin;
@@ -66,7 +68,8 @@ class WoodcuttingOverlay extends OverlayPanel
 			return null;
 		}
 
-		if (WoodcuttingPlugin.WOODCUTTING_ANIMS.contains(client.getLocalPlayer().getAnimation()))
+		if (WoodcuttingPlugin.WOODCUTTING_ANIMS.contains(client.getLocalPlayer().getAnimation())
+			|| client.getVarpValue(VarPlayer.BUFF_BAR_WC_GROUP_BONUS) == BUFF_BAR_DISPLAYED)
 		{
 			panelComponent.getChildren().add(TitleComponent.builder()
 				.text("Woodcutting")
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingPlugin.java
index 1ec3069d4..c1371b50e 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingPlugin.java
@@ -542,7 +542,7 @@ public class WoodcuttingPlugin extends Plugin
 				case NullObjectID.NULL_34637:
 				case NullObjectID.NULL_34639:
 				{
-					WorldPoint worldPoint = new WorldPoint((locCoord >>> 14) & 0x3FFF, locCoord & 0x3FFF, (locCoord >>> 28) & 0x3);
+					WorldPoint worldPoint = WorldPoint.fromCoord(locCoord);
 					GameObject gameObject = findObject(worldPoint);
 					if (gameObject == null)
 					{
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTypeFilter.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTypeFilter.java
index a5ccdcb53..f1fb9e7f7 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTypeFilter.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTypeFilter.java
@@ -36,7 +36,7 @@ enum WorldTypeFilter
 			@Override
 			boolean matches(Set<WorldType> types)
 			{
-				EnumSet<WorldType> normal = EnumSet.of(WorldType.MEMBERS, WorldType.BOUNTY, WorldType.SKILL_TOTAL, WorldType.HIGH_RISK, WorldType.LAST_MAN_STANDING);
+				EnumSet<WorldType> normal = EnumSet.of(WorldType.MEMBERS, WorldType.BOUNTY, WorldType.SKILL_TOTAL, WorldType.LAST_MAN_STANDING);
 				EnumSet<WorldType> inverse = EnumSet.complementOf(normal);
 				return Sets.intersection(types, inverse).isEmpty();
 			}
diff --git a/runelite-client/src/main/java/net/runelite/client/util/HotkeyListener.java b/runelite-client/src/main/java/net/runelite/client/util/HotkeyListener.java
index 8451ca134..d7ba5d675 100644
--- a/runelite-client/src/main/java/net/runelite/client/util/HotkeyListener.java
+++ b/runelite-client/src/main/java/net/runelite/client/util/HotkeyListener.java
@@ -81,7 +81,7 @@ public abstract class HotkeyListener implements KeyListener
 	@Override
 	public void keyReleased(KeyEvent e)
 	{
-		if (keybind.get().matches(e))
+		if (keybind.get().matches(e) && isPressed)
 		{
 			isPressed = false;
 			isConsumingTyped = false;
diff --git a/runelite-client/src/main/resources/item_variations.json b/runelite-client/src/main/resources/item_variations.json
index d0019a5d5..2eeeffae4 100644
--- a/runelite-client/src/main/resources/item_variations.json
+++ b/runelite-client/src/main/resources/item_variations.json
@@ -2533,8 +2533,7 @@
     11028,
     11029,
     11030,
-    26926,
-    29445
+    26926
   ],
   "banana": [
     1963,
@@ -11922,6 +11921,26 @@
     29248,
     29250
   ],
+  "small meat pouch": [
+    29295,
+    29462
+  ],
+  "large meat pouch": [
+    29297,
+    29464
+  ],
+  "small fur pouch": [
+    29299,
+    29466
+  ],
+  "medium fur pouch": [
+    29301,
+    29468
+  ],
+  "large fur pouch": [
+    29303,
+    29470
+  ],
   "blessed bone statuette": [
     29338,
     29340,
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/cl_types.cl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/cl_types.cl
index be1d703a0..ce9bb7111 100644
--- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/cl_types.cl
+++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/cl_types.cl
@@ -32,7 +32,6 @@ struct uniform {
   float cameraX;
   float cameraY;
   float cameraZ;
-  int4 sinCosTable[2048];
 };
 
 struct shared_data {
@@ -53,3 +52,10 @@ struct modelinfo {
   int y;        // scene position y
   int z;        // scene position z
 };
+
+struct vert {
+  float x;
+  float y;
+  float z;
+  int ahsl;
+};
\ No newline at end of file
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/common.cl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/common.cl
index 623faacbb..8b949b20c 100644
--- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/common.cl
+++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/common.cl
@@ -49,16 +49,7 @@ float3 toScreen(float3 vertex, float cameraYaw, float cameraPitch, int centerX,
 /*
  * Rotate a vertex by a given orientation in JAU
  */
-int4 rotate_vertex(__constant struct uniform *uni, int4 vertex, int orientation) {
-  int4 sinCos = uni->sinCosTable[orientation];
-  int s = sinCos.x;
-  int c = sinCos.y;
-  int x = vertex.z * s + vertex.x * c >> 16;
-  int z = vertex.z * c - vertex.x * s >> 16;
-  return (int4)(x, vertex.y, z, vertex.w);
-}
-
-float4 rotatef_vertex(float4 vertex, int orientation) {
+float4 rotate_vertex(float4 vertex, int orientation) {
   float rad = orientation * UNIT;
   float s = sin(rad);
   float c = cos(rad);
@@ -70,15 +61,15 @@ float4 rotatef_vertex(float4 vertex, int orientation) {
 /*
  * Calculate the distance to a vertex given the camera angle
  */
-int vertex_distance(int4 vertex, float cameraYaw, float cameraPitch) {
-  int yawSin = (int)(65536.0f * sin(cameraYaw));
-  int yawCos = (int)(65536.0f * cos(cameraYaw));
+float vertex_distance(float4 vertex, float cameraYaw, float cameraPitch) {
+  float yawSin = sin(cameraYaw);
+  float yawCos = cos(cameraYaw);
 
-  int pitchSin = (int)(65536.0f * sin(cameraPitch));
-  int pitchCos = (int)(65536.0f * cos(cameraPitch));
+  float pitchSin = sin(cameraPitch);
+  float pitchCos = cos(cameraPitch);
 
-  int j = vertex.z * yawCos - vertex.x * yawSin >> 16;
-  int l = vertex.y * pitchSin + j * pitchCos >> 16;
+  float j = vertex.z * yawCos - vertex.x * yawSin;
+  float l = vertex.y * pitchSin + j * pitchCos;
 
   return l;
 }
@@ -86,28 +77,29 @@ int vertex_distance(int4 vertex, float cameraYaw, float cameraPitch) {
 /*
  * Calculate the distance to a face
  */
-int face_distance(int4 vA, int4 vB, int4 vC, float cameraYaw, float cameraPitch) {
-  int dvA = vertex_distance(vA, cameraYaw, cameraPitch);
-  int dvB = vertex_distance(vB, cameraYaw, cameraPitch);
-  int dvC = vertex_distance(vC, cameraYaw, cameraPitch);
-  int faceDistance = (dvA + dvB + dvC) / 3;
-  return faceDistance;
+float face_distance(float4 vA, float4 vB, float4 vC, float cameraYaw, float cameraPitch) {
+  float dvA = vertex_distance(vA, cameraYaw, cameraPitch);
+  float dvB = vertex_distance(vB, cameraYaw, cameraPitch);
+  float dvC = vertex_distance(vC, cameraYaw, cameraPitch);
+  float faceDistance = (dvA + dvB + dvC) / 3;
+  return (int)faceDistance;
 }
 
 /*
  * Test if a face is visible (not backward facing)
  */
-bool face_visible(__constant struct uniform *uni, int4 vA, int4 vB, int4 vC, int4 position) {
+bool face_visible(__constant struct uniform *uni, float3 vA, float3 vB, float3 vC, int4 position) {
   // Move model to scene location, and account for camera offset
-  float4 cameraPos = (float4)(uni->cameraX, uni->cameraY, uni->cameraZ, 0);
+  float3 cameraPos = (float3)(uni->cameraX, uni->cameraY, uni->cameraZ);
+  float3 modelPos = convert_float3(position.xyz);
 
-  float4 lA = convert_float4(vA + position) - cameraPos;
-  float4 lB = convert_float4(vB + position) - cameraPos;
-  float4 lC = convert_float4(vC + position) - cameraPos;
+  float3 lA = vA + modelPos - cameraPos;
+  float3 lB = vB + modelPos - cameraPos;
+  float3 lC = vC + modelPos - cameraPos;
 
-  float3 sA = toScreen(lA.xyz, uni->cameraYaw, uni->cameraPitch, uni->centerX, uni->centerY, uni->zoom);
-  float3 sB = toScreen(lB.xyz, uni->cameraYaw, uni->cameraPitch, uni->centerX, uni->centerY, uni->zoom);
-  float3 sC = toScreen(lC.xyz, uni->cameraYaw, uni->cameraPitch, uni->centerX, uni->centerY, uni->zoom);
+  float3 sA = toScreen(lA, uni->cameraYaw, uni->cameraPitch, uni->centerX, uni->centerY, uni->zoom);
+  float3 sB = toScreen(lB, uni->cameraYaw, uni->cameraPitch, uni->centerX, uni->centerY, uni->zoom);
+  float3 sC = toScreen(lC, uni->cameraYaw, uni->cameraPitch, uni->centerX, uni->centerY, uni->zoom);
 
   return (sA.x - sB.x) * (sC.y - sB.y) - (sC.x - sB.x) * (sA.y - sB.y) > 0;
 }
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/common.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/common.glsl
index d9148fb34..14ceab09d 100644
--- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/common.glsl
+++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/common.glsl
@@ -28,35 +28,33 @@
 /*
  * Rotate a vertex by a given orientation in JAU
  */
-ivec4 rotate(ivec4 vertex, int orientation) {
-  ivec2 sinCos = sinCosTable[orientation];
-  int s = sinCos.x;
-  int c = sinCos.y;
-  int x = vertex.z * s + vertex.x * c >> 16;
-  int z = vertex.z * c - vertex.x * s >> 16;
-  return ivec4(x, vertex.y, z, vertex.w);
-}
-
-vec4 rotatef(vec4 vertex, int orientation) {
+vec4 rotate_vertex(vec4 vertex, int orientation) {
   float rad = orientation * UNIT;
   float s = sin(rad);
   float c = cos(rad);
-  mat4 m = mat4(c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1);
+  // clang-format off
+  mat4 m = mat4(
+    c, 0, s, 0,
+    0, 1, 0, 0,
+   -s, 0, c, 0,
+    0, 0, 0, 1
+  );
+  // clang-format on
   return vertex * m;
 }
 
 /*
  * Calculate the distance to a vertex given the camera angle
  */
-int distance(ivec4 vertex, float cameraYaw, float cameraPitch) {
-  int yawSin = int(65536.0f * sin(cameraYaw));
-  int yawCos = int(65536.0f * cos(cameraYaw));
+float distance(vec4 vertex, float cameraYaw, float cameraPitch) {
+  float yawSin = sin(cameraYaw);
+  float yawCos = cos(cameraYaw);
 
-  int pitchSin = int(65536.0f * sin(cameraPitch));
-  int pitchCos = int(65536.0f * cos(cameraPitch));
+  float pitchSin = sin(cameraPitch);
+  float pitchCos = cos(cameraPitch);
 
-  int j = vertex.z * yawCos - vertex.x * yawSin >> 16;
-  int l = vertex.y * pitchSin + j * pitchCos >> 16;
+  float j = vertex.z * yawCos - vertex.x * yawSin;
+  float l = vertex.y * pitchSin + j * pitchCos;
 
   return l;
 }
@@ -64,28 +62,28 @@ int distance(ivec4 vertex, float cameraYaw, float cameraPitch) {
 /*
  * Calculate the distance to a face
  */
-int face_distance(ivec4 vA, ivec4 vB, ivec4 vC, float cameraYaw, float cameraPitch) {
-  int dvA = distance(vA, cameraYaw, cameraPitch);
-  int dvB = distance(vB, cameraYaw, cameraPitch);
-  int dvC = distance(vC, cameraYaw, cameraPitch);
-  int faceDistance = (dvA + dvB + dvC) / 3;
-  return faceDistance;
+int face_distance(vec4 vA, vec4 vB, vec4 vC, float cameraYaw, float cameraPitch) {
+  float dvA = distance(vA, cameraYaw, cameraPitch);
+  float dvB = distance(vB, cameraYaw, cameraPitch);
+  float dvC = distance(vC, cameraYaw, cameraPitch);
+  float faceDistance = (dvA + dvB + dvC) / 3;
+  return int(faceDistance);
 }
 
 /*
  * Test if a face is visible (not backward facing)
  */
-bool face_visible(ivec4 vA, ivec4 vB, ivec4 vC, ivec4 position) {
+bool face_visible(vec3 vA, vec3 vB, vec3 vC, ivec4 position) {
   // Move model to scene location, and account for camera offset
-  vec4 cameraPos = vec4(cameraX, cameraY, cameraZ, 0);
+  vec3 cameraPos = vec3(cameraX, cameraY, cameraZ);
 
-  vec4 lA = vA + position - cameraPos;
-  vec4 lB = vB + position - cameraPos;
-  vec4 lC = vC + position - cameraPos;
+  vec3 lA = vA + position.xyz - cameraPos;
+  vec3 lB = vB + position.xyz - cameraPos;
+  vec3 lC = vC + position.xyz - cameraPos;
 
-  vec3 sA = toScreen(lA.xyz, cameraYaw, cameraPitch, centerX, centerY, zoom);
-  vec3 sB = toScreen(lB.xyz, cameraYaw, cameraPitch, centerX, centerY, zoom);
-  vec3 sC = toScreen(lC.xyz, cameraYaw, cameraPitch, centerX, centerY, zoom);
+  vec3 sA = toScreen(lA, cameraYaw, cameraPitch, centerX, centerY, zoom);
+  vec3 sB = toScreen(lB, cameraYaw, cameraPitch, centerX, centerY, zoom);
+  vec3 sC = toScreen(lC, cameraYaw, cameraPitch, centerX, centerY, zoom);
 
   return (sA.x - sB.x) * (sC.y - sB.y) - (sC.x - sB.x) * (sA.y - sB.y) > 0;
 }
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp.cl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp.cl
index f87258806..9049cd29a 100644
--- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp.cl
+++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp.cl
@@ -30,9 +30,10 @@
 #include "priority_render.cl"
 
 __kernel __attribute__((work_group_size_hint(256, 1, 1))) void computeLarge(__local struct shared_data *shared, __global const struct modelinfo *ol,
-                                                                            __global const int4 *vb, __global const int4 *tempvb, __global const float4 *texb,
-                                                                            __global const float4 *temptexb, __global int4 *vout, __global float4 *uvout,
-                                                                            __constant struct uniform *uni, read_only image3d_t tileHeightImage) {
+                                                                            __global const struct vert *vb, __global const struct vert *tempvb,
+                                                                            __global const float4 *texb, __global const float4 *temptexb,
+                                                                            __global struct vert *vout, __global float4 *uvout, __constant struct uniform *uni,
+                                                                            read_only image3d_t tileHeightImage) {
   size_t groupId = get_group_id(0);
   size_t localId = get_local_id(0) * FACE_COUNT;
   struct modelinfo minfo = ol[groupId];
@@ -51,12 +52,12 @@ __kernel __attribute__((work_group_size_hint(256, 1, 1))) void computeLarge(__lo
 
   int prio[FACE_COUNT];
   int dis[FACE_COUNT];
-  int4 v1[FACE_COUNT];
-  int4 v2[FACE_COUNT];
-  int4 v3[FACE_COUNT];
+  struct vert v1[FACE_COUNT];
+  struct vert v2[FACE_COUNT];
+  struct vert v3[FACE_COUNT];
 
   for (int i = 0; i < FACE_COUNT; i++) {
-    get_face(shared, uni, vb, tempvb, localId + i, minfo, uni->cameraYaw, uni->cameraPitch, &prio[i], &dis[i], &v1[i], &v2[i], &v3[i]);
+    get_face(shared, vb, tempvb, localId + i, minfo, uni->cameraYaw, uni->cameraPitch, &prio[i], &dis[i], &v1[i], &v2[i], &v3[i]);
   }
 
   barrier(CLK_LOCAL_MEM_FENCE);
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp.glsl
index 9354fa4f7..edf7d3ff2 100644
--- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp.glsl
+++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp.glsl
@@ -61,9 +61,9 @@ void main() {
 
   int prio[FACES_PER_THREAD];
   int dis[FACES_PER_THREAD];
-  ivec4 vA[FACES_PER_THREAD];
-  ivec4 vB[FACES_PER_THREAD];
-  ivec4 vC[FACES_PER_THREAD];
+  vert vA[FACES_PER_THREAD];
+  vert vB[FACES_PER_THREAD];
+  vert vC[FACES_PER_THREAD];
 
   for (int i = 0; i < FACES_PER_THREAD; i++) {
     get_face(localId + i, minfo, cameraYaw, cameraPitch, prio[i], dis[i], vA[i], vB[i], vC[i]);
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_common.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_common.glsl
index f751476be..39dbed12f 100644
--- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_common.glsl
+++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_common.glsl
@@ -35,7 +35,6 @@ layout(std140) uniform uniforms {
   float cameraX;
   float cameraY;
   float cameraZ;
-  ivec2 sinCosTable[2048];
 };
 
 struct modelinfo {
@@ -49,20 +48,25 @@ struct modelinfo {
   int z;        // scene position z
 };
 
+struct vert {
+  vec3 pos;
+  int ahsl;
+};
+
 layout(std430, binding = 0) readonly buffer modelbuffer_in {
   modelinfo ol[];
 };
 
 layout(std430, binding = 1) readonly buffer vertexbuffer_in {
-  ivec4 vb[];
+  vert vb[];
 };
 
 layout(std430, binding = 2) readonly buffer tempvertexbuffer_in {
-  ivec4 tempvb[];
+  vert tempvb[];
 };
 
 layout(std430, binding = 3) writeonly buffer vertex_out {
-  ivec4 vout[];
+  vert vout[];
 };
 
 layout(std430, binding = 4) writeonly buffer uv_out {
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_unordered.cl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_unordered.cl
index 1ae52ed5e..738967216 100644
--- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_unordered.cl
+++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_unordered.cl
@@ -26,9 +26,10 @@
 
 #include "cl_types.cl"
 
-__kernel __attribute__((reqd_work_group_size(6, 1, 1))) void computeUnordered(__global const struct modelinfo *ol, __global const int4 *vb,
-                                                                              __global const int4 *tempvb, __global const float4 *texb,
-                                                                              __global const float4 *temptexb, __global int4 *vout, __global float4 *uvout) {
+__kernel __attribute__((reqd_work_group_size(6, 1, 1))) void computeUnordered(__global const struct modelinfo *ol, __global const struct vert *vb,
+                                                                              __global const struct vert *tempvb, __global const float4 *texb,
+                                                                              __global const float4 *temptexb, __global struct vert *vout,
+                                                                              __global float4 *uvout) {
   size_t groupId = get_group_id(0);
   size_t localId = get_local_id(0);
   struct modelinfo minfo = ol[groupId];
@@ -44,7 +45,7 @@ __kernel __attribute__((reqd_work_group_size(6, 1, 1))) void computeUnordered(__
   }
 
   uint ssboOffset = localId;
-  int4 thisA, thisB, thisC;
+  struct vert thisA, thisB, thisC;
 
   // Grab triangle vertices from the correct buffer
   if (flags < 0) {
@@ -58,13 +59,17 @@ __kernel __attribute__((reqd_work_group_size(6, 1, 1))) void computeUnordered(__
   }
 
   uint myOffset = localId;
-  int4 pos = (int4)(minfo.x, minfo.y, minfo.z, 0);
-  float4 texPos = convert_float4(pos.wxyz);
+  float3 pos = convert_float3((int3)(minfo.x, minfo.y, minfo.z));
+  float4 texPos = (float4)(0, minfo.x, minfo.y, minfo.z);
+
+  float3 vertA = (float3)(thisA.x, thisA.y, thisA.z) + pos;
+  float3 vertB = (float3)(thisB.x, thisB.y, thisB.z) + pos;
+  float3 vertC = (float3)(thisC.x, thisC.y, thisC.z) + pos;
 
   // position vertices in scene and write to out buffer
-  vout[outOffset + myOffset * 3] = pos + thisA;
-  vout[outOffset + myOffset * 3 + 1] = pos + thisB;
-  vout[outOffset + myOffset * 3 + 2] = pos + thisC;
+  vout[outOffset + myOffset * 3] = (struct vert){vertA.x, vertA.y, vertA.z, thisA.ahsl};
+  vout[outOffset + myOffset * 3 + 1] = (struct vert){vertB.x, vertB.y, vertB.z, thisB.ahsl};
+  vout[outOffset + myOffset * 3 + 2] = (struct vert){vertC.x, vertC.y, vertC.z, thisC.ahsl};
 
   if (toffset < 0) {
     uvout[outOffset + myOffset * 3] = (float4)(0.0f, 0.0f, 0.0f, 0.0f);
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_unordered.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_unordered.glsl
index 2152091d2..a4eb42d23 100644
--- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_unordered.glsl
+++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_unordered.glsl
@@ -45,7 +45,7 @@ void main() {
   }
 
   uint ssboOffset = localId;
-  ivec4 thisA, thisB, thisC;
+  vert thisA, thisB, thisC;
 
   // Grab triangle vertices from the correct buffer
   if (flags < 0) {
@@ -59,13 +59,17 @@ void main() {
   }
 
   uint myOffset = localId;
-  ivec4 pos = ivec4(minfo.x, minfo.y, minfo.z, 0);
-  ivec4 texPos = pos.wxyz;
+  vec3 pos = vec3(minfo.x, minfo.y, minfo.z);
+  ivec4 texPos = ivec4(0, pos);
+
+  vec3 vertA = thisA.pos + pos;
+  vec3 vertB = thisB.pos + pos;
+  vec3 vertC = thisC.pos + pos;
 
   // position vertices in scene and write to out buffer
-  vout[outOffset + myOffset * 3] = pos + thisA;
-  vout[outOffset + myOffset * 3 + 1] = pos + thisB;
-  vout[outOffset + myOffset * 3 + 2] = pos + thisC;
+  vout[outOffset + myOffset * 3] = vert(vertA, thisA.ahsl);
+  vout[outOffset + myOffset * 3 + 1] = vert(vertB, thisB.ahsl);
+  vout[outOffset + myOffset * 3 + 2] = vert(vertC, thisC.ahsl);
 
   if (toffset < 0) {
     uvout[outOffset + myOffset * 3] = vec4(0);
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/geom.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/geom.glsl
index 2c5cc57bc..7fc899fab 100644
--- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/geom.glsl
+++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/geom.glsl
@@ -43,7 +43,6 @@ layout(std140) uniform uniforms {
   float cameraX;
   float cameraY;
   float cameraZ;
-  ivec2 sinCosTable[2048];
 };
 
 #include "uv.glsl"
@@ -52,7 +51,7 @@ uniform vec2 textureAnimations[128];
 uniform int tick;
 uniform mat4 projectionMatrix;
 
-in ivec3 gVertex[3];
+in vec3 gVertex[3];
 in vec4 gColor[3];
 in float gHsl[3];
 in int gTextureId[3];
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/priority_render.cl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/priority_render.cl
index 98cf779d2..f64781ec6 100644
--- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/priority_render.cl
+++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/priority_render.cl
@@ -89,9 +89,9 @@ int count_prio_offset(__local struct shared_data *shared, int priority) {
   return total;
 }
 
-void get_face(__local struct shared_data *shared, __constant struct uniform *uni, __global const int4 *vb, __global const int4 *tempvb, uint localId,
-              struct modelinfo minfo, float cameraYaw, float cameraPitch,
-              /* out */ int *prio, int *dis, int4 *o1, int4 *o2, int4 *o3) {
+void get_face(__local struct shared_data *shared, __global const struct vert *vb, __global const struct vert *tempvb, uint localId, struct modelinfo minfo,
+              float cameraYaw, float cameraPitch,
+              /* out */ int *prio, int *dis, struct vert *o1, struct vert *o2, struct vert *o3) {
   int size = minfo.size;
   int offset = minfo.offset;
   int flags = minfo.flags;
@@ -103,9 +103,9 @@ void get_face(__local struct shared_data *shared, __constant struct uniform *uni
     ssboOffset = 0;
   }
 
-  int4 thisA;
-  int4 thisB;
-  int4 thisC;
+  struct vert thisA;
+  struct vert thisB;
+  struct vert thisC;
 
   // Grab triangle vertices from the correct buffer
   if (flags < 0) {
@@ -122,34 +122,37 @@ void get_face(__local struct shared_data *shared, __constant struct uniform *uni
     int orientation = flags & 0x7ff;
 
     // rotate for model orientation
-    int4 thisrvA = rotate_vertex(uni, thisA, orientation);
-    int4 thisrvB = rotate_vertex(uni, thisB, orientation);
-    int4 thisrvC = rotate_vertex(uni, thisC, orientation);
+    float4 thisrvA = rotate_vertex((float4)(thisA.x, thisA.y, thisA.z, 0), orientation);
+    float4 thisrvB = rotate_vertex((float4)(thisB.x, thisB.y, thisB.z, 0), orientation);
+    float4 thisrvC = rotate_vertex((float4)(thisC.x, thisC.y, thisC.z, 0), orientation);
 
     // calculate distance to face
-    int thisPriority = (thisA.w >> 16) & 0xff;  // all vertices on the face have the same priority
+    int thisPriority = (thisA.ahsl >> 16) & 0xff;  // all vertices on the face have the same priority
     int thisDistance = face_distance(thisrvA, thisrvB, thisrvC, cameraYaw, cameraPitch);
 
-    *o1 = thisrvA;
-    *o2 = thisrvB;
-    *o3 = thisrvC;
+    *o1 = (struct vert){thisrvA.x, thisrvA.y, thisrvA.z, thisA.ahsl};
+    *o2 = (struct vert){thisrvB.x, thisrvB.y, thisrvB.z, thisB.ahsl};
+    *o3 = (struct vert){thisrvC.x, thisrvC.y, thisrvC.z, thisC.ahsl};
 
     *prio = thisPriority;
     *dis = thisDistance;
   } else {
-    *o1 = (int4)(0, 0, 0, 0);
-    *o2 = (int4)(0, 0, 0, 0);
-    *o3 = (int4)(0, 0, 0, 0);
+    *o1 = (struct vert){0, 0, 0, 0};
+    *o2 = (struct vert){0, 0, 0, 0};
+    *o3 = (struct vert){0, 0, 0, 0};
     *prio = 0;
     *dis = 0;
   }
 }
 
-void add_face_prio_distance(__local struct shared_data *shared, __constant struct uniform *uni, uint localId, struct modelinfo minfo, int4 thisrvA,
-                            int4 thisrvB, int4 thisrvC, int thisPriority, int thisDistance, int4 pos) {
+void add_face_prio_distance(__local struct shared_data *shared, __constant struct uniform *uni, uint localId, struct modelinfo minfo, struct vert thisrvA,
+                            struct vert thisrvB, struct vert thisrvC, int thisPriority, int thisDistance, int4 pos) {
   if (localId < minfo.size) {
     // if the face is not culled, it is calculated into priority distance averages
-    if (face_visible(uni, thisrvA, thisrvB, thisrvC, pos)) {
+    float3 posA = (float3)(thisrvA.x, thisrvA.y, thisrvA.z);
+    float3 posB = (float3)(thisrvB.x, thisrvB.y, thisrvB.z);
+    float3 posC = (float3)(thisrvC.x, thisrvC.y, thisrvC.z);
+    if (face_visible(uni, posA, posB, posC, pos)) {
       atomic_add(&shared->totalNum[thisPriority], 1);
       atomic_add(&shared->totalDistance[thisPriority], thisDistance);
 
@@ -214,21 +217,6 @@ int tile_height(read_only image3d_t tileHeightImage, int z, int x, int y) {
   return read_imagei(tileHeightImage, tileHeightSampler, coord).x << 3;
 }
 
-int4 hillskew_vertex(read_only image3d_t tileHeightImage, int4 v, int hillskew, int y, int plane) {
-  if (hillskew == 1) {
-    int px = v.x & 127;
-    int pz = v.z & 127;
-    int sx = v.x >> 7;
-    int sz = v.z >> 7;
-    int h1 = px * tile_height(tileHeightImage, plane, sx + 1, sz) + (128 - px) * tile_height(tileHeightImage, plane, sx, sz) >> 7;
-    int h2 = px * tile_height(tileHeightImage, plane, sx + 1, sz + 1) + (128 - px) * tile_height(tileHeightImage, plane, sx, sz + 1) >> 7;
-    int h3 = pz * h2 + (128 - pz) * h1 >> 7;
-    return (int4)(v.x, v.y + h3 - y, v.z, v.w);
-  } else {
-    return v;
-  }
-}
-
 float4 hillskew_vertexf(read_only image3d_t tileHeightImage, float4 v, int hillskew, int y, int plane) {
   if (hillskew == 1) {
     float fx = v.x / 128;
@@ -246,8 +234,8 @@ float4 hillskew_vertexf(read_only image3d_t tileHeightImage, float4 v, int hills
 }
 
 void sort_and_insert(__local struct shared_data *shared, __constant struct uniform *uni, __global const float4 *texb, __global const float4 *temptexb,
-                     __global int4 *vout, __global float4 *uvout, uint localId, struct modelinfo minfo, int thisPriority, int thisDistance, int4 thisrvA,
-                     int4 thisrvB, int4 thisrvC, read_only image3d_t tileHeightImage) {
+                     __global struct vert *vout, __global float4 *uvout, uint localId, struct modelinfo minfo, int thisPriority, int thisDistance,
+                     struct vert thisrvA, struct vert thisrvB, struct vert thisrvC, read_only image3d_t tileHeightImage) {
   int size = minfo.size;
 
   if (localId < size) {
@@ -271,22 +259,22 @@ void sort_and_insert(__local struct shared_data *shared, __constant struct unifo
     }
 
     // position into scene
-    int4 pos = (int4)(minfo.x, minfo.y, minfo.z, 0);
-    thisrvA += pos;
-    thisrvB += pos;
-    thisrvC += pos;
+    float4 pos = (float4)(minfo.x, minfo.y, minfo.z, 0);
+    float4 vertA = (float4)(thisrvA.x, thisrvA.y, thisrvA.z, 0) + pos;
+    float4 vertB = (float4)(thisrvB.x, thisrvB.y, thisrvB.z, 0) + pos;
+    float4 vertC = (float4)(thisrvC.x, thisrvC.y, thisrvC.z, 0) + pos;
 
     // apply hillskew
     int plane = (flags >> 24) & 3;
     int hillskew = (flags >> 26) & 1;
-    thisrvA = hillskew_vertex(tileHeightImage, thisrvA, hillskew, minfo.y, plane);
-    thisrvB = hillskew_vertex(tileHeightImage, thisrvB, hillskew, minfo.y, plane);
-    thisrvC = hillskew_vertex(tileHeightImage, thisrvC, hillskew, minfo.y, plane);
+    vertA = hillskew_vertexf(tileHeightImage, vertA, hillskew, minfo.y, plane);
+    vertB = hillskew_vertexf(tileHeightImage, vertB, hillskew, minfo.y, plane);
+    vertC = hillskew_vertexf(tileHeightImage, vertC, hillskew, minfo.y, plane);
 
     // write to out buffer
-    vout[outOffset + myOffset * 3] = thisrvA;
-    vout[outOffset + myOffset * 3 + 1] = thisrvB;
-    vout[outOffset + myOffset * 3 + 2] = thisrvC;
+    vout[outOffset + myOffset * 3] = (struct vert){vertA.x, vertA.y, vertA.z, thisrvA.ahsl};
+    vout[outOffset + myOffset * 3 + 1] = (struct vert){vertB.x, vertB.y, vertB.z, thisrvB.ahsl};
+    vout[outOffset + myOffset * 3 + 2] = (struct vert){vertC.x, vertC.y, vertC.z, thisrvC.ahsl};
 
     if (toffset < 0) {
       uvout[outOffset + myOffset * 3] = (float4)(0, 0, 0, 0);
@@ -311,13 +299,13 @@ void sort_and_insert(__local struct shared_data *shared, __constant struct unifo
       texB = texB.yzwx;
       texC = texC.yzwx;
       // rotate
-      texA = rotatef_vertex(texA, orientation);
-      texB = rotatef_vertex(texB, orientation);
-      texC = rotatef_vertex(texC, orientation);
+      texA = rotate_vertex(texA, orientation);
+      texB = rotate_vertex(texB, orientation);
+      texC = rotate_vertex(texC, orientation);
       // position
-      texA += convert_float4(pos);
-      texB += convert_float4(pos);
-      texC += convert_float4(pos);
+      texA += pos;
+      texB += pos;
+      texC += pos;
       // hillskew
       texA = hillskew_vertexf(tileHeightImage, texA, hillskew, minfo.y, plane);
       texB = hillskew_vertexf(tileHeightImage, texB, hillskew, minfo.y, plane);
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/priority_render.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/priority_render.glsl
index 429b19b32..c674746e3 100644
--- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/priority_render.glsl
+++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/priority_render.glsl
@@ -91,7 +91,7 @@ int count_prio_offset(int priority) {
   return total;
 }
 
-void get_face(uint localId, modelinfo minfo, float cameraYaw, float cameraPitch, out int prio, out int dis, out ivec4 o1, out ivec4 o2, out ivec4 o3) {
+void get_face(uint localId, modelinfo minfo, float cameraYaw, float cameraPitch, out int prio, out int dis, out vert o1, out vert o2, out vert o3) {
   int size = minfo.size;
   int offset = minfo.offset;
   int flags = minfo.flags;
@@ -103,9 +103,9 @@ void get_face(uint localId, modelinfo minfo, float cameraYaw, float cameraPitch,
     ssboOffset = 0;
   }
 
-  ivec4 thisA;
-  ivec4 thisB;
-  ivec4 thisC;
+  vert thisA;
+  vert thisB;
+  vert thisC;
 
   // Grab triangle vertices from the correct buffer
   if (flags < 0) {
@@ -122,33 +122,44 @@ void get_face(uint localId, modelinfo minfo, float cameraYaw, float cameraPitch,
     int orientation = flags & 0x7ff;
 
     // rotate for model orientation
-    ivec4 thisrvA = rotate(thisA, orientation);
-    ivec4 thisrvB = rotate(thisB, orientation);
-    ivec4 thisrvC = rotate(thisC, orientation);
+    vec4 thisrvA = rotate_vertex(vec4(thisA.pos, 0), orientation);
+    vec4 thisrvB = rotate_vertex(vec4(thisB.pos, 0), orientation);
+    vec4 thisrvC = rotate_vertex(vec4(thisC.pos, 0), orientation);
 
     // calculate distance to face
-    int thisPriority = (thisA.w >> 16) & 0xff;  // all vertices on the face have the same priority
+    int thisPriority = (thisA.ahsl >> 16) & 0xff;  // all vertices on the face have the same priority
     int thisDistance = face_distance(thisrvA, thisrvB, thisrvC, cameraYaw, cameraPitch);
 
-    o1 = thisrvA;
-    o2 = thisrvB;
-    o3 = thisrvC;
+    o1.pos = thisrvA.xyz;
+    o1.ahsl = thisA.ahsl;
+
+    o2.pos = thisrvB.xyz;
+    o2.ahsl = thisB.ahsl;
+
+    o3.pos = thisrvC.xyz;
+    o3.ahsl = thisC.ahsl;
 
     prio = thisPriority;
     dis = thisDistance;
   } else {
-    o1 = ivec4(0);
-    o2 = ivec4(0);
-    o3 = ivec4(0);
+    o1.pos = vec3(0);
+    o1.ahsl = 0;
+
+    o2.pos = vec3(0);
+    o2.ahsl = 0;
+
+    o3.pos = vec3(0);
+    o3.ahsl = 0;
+
     prio = 0;
     dis = 0;
   }
 }
 
-void add_face_prio_distance(uint localId, modelinfo minfo, ivec4 thisrvA, ivec4 thisrvB, ivec4 thisrvC, int thisPriority, int thisDistance, ivec4 pos) {
+void add_face_prio_distance(uint localId, modelinfo minfo, vert thisrvA, vert thisrvB, vert thisrvC, int thisPriority, int thisDistance, ivec4 pos) {
   if (localId < minfo.size) {
     // if the face is not culled, it is calculated into priority distance averages
-    if (face_visible(thisrvA, thisrvB, thisrvC, pos)) {
+    if (face_visible(thisrvA.pos, thisrvB.pos, thisrvC.pos, pos)) {
       atomicAdd(totalNum[thisPriority], 1);
       atomicAdd(totalDistance[thisPriority], thisDistance);
 
@@ -226,22 +237,7 @@ vec4 hillskew_vertexf(vec4 v, int hillskew, int y, int plane) {
   }
 }
 
-ivec4 hillskew_vertex(ivec4 v, int hillskew, int y, int plane) {
-  if (hillskew == 1) {
-    int px = v.x & 127;
-    int pz = v.z & 127;
-    int sx = v.x >> 7;
-    int sz = v.z >> 7;
-    int h1 = px * tile_height(plane, sx + 1, sz) + (128 - px) * tile_height(plane, sx, sz) >> 7;
-    int h2 = px * tile_height(plane, sx + 1, sz + 1) + (128 - px) * tile_height(plane, sx, sz + 1) >> 7;
-    int h3 = pz * h2 + (128 - pz) * h1 >> 7;
-    return ivec4(v.x, v.y + h3 - y, v.z, v.w);
-  } else {
-    return v;
-  }
-}
-
-void sort_and_insert(uint localId, modelinfo minfo, int thisPriority, int thisDistance, ivec4 thisrvA, ivec4 thisrvB, ivec4 thisrvC) {
+void sort_and_insert(uint localId, modelinfo minfo, int thisPriority, int thisDistance, vert thisrvA, vert thisrvB, vert thisrvC) {
   int size = minfo.size;
 
   if (localId < size) {
@@ -265,22 +261,22 @@ void sort_and_insert(uint localId, modelinfo minfo, int thisPriority, int thisDi
     }
 
     // position into scene
-    ivec4 pos = ivec4(minfo.x, minfo.y, minfo.z, 0);
-    thisrvA += pos;
-    thisrvB += pos;
-    thisrvC += pos;
+    vec4 pos = vec4(minfo.x, minfo.y, minfo.z, 0);
+    vec4 vertA = vec4(thisrvA.pos, 0) + pos;
+    vec4 vertB = vec4(thisrvB.pos, 0) + pos;
+    vec4 vertC = vec4(thisrvC.pos, 0) + pos;
 
     // apply hillskew
     int plane = (flags >> 24) & 3;
     int hillskew = (flags >> 26) & 1;
-    thisrvA = hillskew_vertex(thisrvA, hillskew, minfo.y, plane);
-    thisrvB = hillskew_vertex(thisrvB, hillskew, minfo.y, plane);
-    thisrvC = hillskew_vertex(thisrvC, hillskew, minfo.y, plane);
+    vertA = hillskew_vertexf(vertA, hillskew, minfo.y, plane);
+    vertB = hillskew_vertexf(vertB, hillskew, minfo.y, plane);
+    vertC = hillskew_vertexf(vertC, hillskew, minfo.y, plane);
 
     // write to out buffer
-    vout[outOffset + myOffset * 3] = thisrvA;
-    vout[outOffset + myOffset * 3 + 1] = thisrvB;
-    vout[outOffset + myOffset * 3 + 2] = thisrvC;
+    vout[outOffset + myOffset * 3] = vert(vertA.xyz, thisrvA.ahsl);
+    vout[outOffset + myOffset * 3 + 1] = vert(vertB.xyz, thisrvB.ahsl);
+    vout[outOffset + myOffset * 3 + 2] = vert(vertC.xyz, thisrvC.ahsl);
 
     if (toffset < 0) {
       uvout[outOffset + myOffset * 3] = vec4(0);
@@ -305,9 +301,9 @@ void sort_and_insert(uint localId, modelinfo minfo, int thisPriority, int thisDi
       texB = texB.yzwx;
       texC = texC.yzwx;
       // rotate
-      texA = rotatef(texA, orientation);
-      texB = rotatef(texB, orientation);
-      texC = rotatef(texC, orientation);
+      texA = rotate_vertex(texA, orientation);
+      texB = rotate_vertex(texB, orientation);
+      texC = rotate_vertex(texC, orientation);
       // position
       texA += pos;
       texB += pos;
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/vert.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/vert.glsl
index 816bd52ee..7c37d22d7 100644
--- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/vert.glsl
+++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/vert.glsl
@@ -25,15 +25,16 @@
 
 #version 330
 
-#define TILE_SIZE 128
+#define TILE_SIZE 128.f
 
 #define FOG_SCENE_EDGE_MIN ((-expandedMapLoadingChunks * 8 + 1) * TILE_SIZE)
 #define FOG_SCENE_EDGE_MAX ((104 + expandedMapLoadingChunks * 8 - 1) * TILE_SIZE)
 #define FOG_CORNER_ROUNDING 1.5
 #define FOG_CORNER_ROUNDING_SQUARED (FOG_CORNER_ROUNDING * FOG_CORNER_ROUNDING)
 
-layout(location = 0) in ivec4 VertexPosition;
-layout(location = 1) in vec4 uv;
+layout(location = 0) in vec3 vert;
+layout(location = 1) in int ahsl;
+layout(location = 2) in vec4 uv;
 
 layout(std140) uniform uniforms {
   float cameraYaw;
@@ -44,7 +45,6 @@ layout(std140) uniform uniforms {
   float cameraX;
   float cameraY;
   float cameraZ;
-  ivec2 sinCosTable[2048];
 };
 
 uniform float brightness;
@@ -53,7 +53,7 @@ uniform int fogDepth;
 uniform int drawDistance;
 uniform int expandedMapLoadingChunks;
 
-out ivec3 gVertex;
+out vec3 gVertex;
 out vec4 gColor;
 out float gHsl;
 out int gTextureId;
@@ -67,14 +67,12 @@ float fogFactorLinear(const float dist, const float start, const float end) {
 }
 
 void main() {
-  ivec3 vertex = VertexPosition.xyz;
-  int ahsl = VertexPosition.w;
   int hsl = ahsl & 0xffff;
   float a = float(ahsl >> 24 & 0xff) / 255.f;
 
   vec3 rgb = hslToRgb(hsl);
 
-  gVertex = vertex;
+  gVertex = vert;
   gColor = vec4(rgb, 1.f - a);
   gHsl = float(hsl);
 
@@ -83,14 +81,14 @@ void main() {
 
   // the client draws one less tile to the north and east than it does to the south
   // and west, so subtract a tiles width from the north and east edges.
-  int fogWest = max(FOG_SCENE_EDGE_MIN, int(cameraX) - drawDistance);
-  int fogEast = min(FOG_SCENE_EDGE_MAX, int(cameraX) + drawDistance - TILE_SIZE);
-  int fogSouth = max(FOG_SCENE_EDGE_MIN, int(cameraZ) - drawDistance);
-  int fogNorth = min(FOG_SCENE_EDGE_MAX, int(cameraZ) + drawDistance - TILE_SIZE);
+  float fogWest = max(FOG_SCENE_EDGE_MIN, cameraX - drawDistance);
+  float fogEast = min(FOG_SCENE_EDGE_MAX, cameraX + drawDistance - TILE_SIZE);
+  float fogSouth = max(FOG_SCENE_EDGE_MIN, cameraZ - drawDistance);
+  float fogNorth = min(FOG_SCENE_EDGE_MAX, cameraZ + drawDistance - TILE_SIZE);
 
   // Calculate distance from the scene edge
-  int xDist = min(vertex.x - fogWest, fogEast - vertex.x);
-  int zDist = min(vertex.z - fogSouth, fogNorth - vertex.z);
+  float xDist = min(vert.x - fogWest, fogEast - vert.x);
+  float zDist = min(vert.z - fogSouth, fogNorth - vert.z);
   float nearestEdgeDistance = min(xDist, zDist);
   float secondNearestEdgeDistance = max(xDist, zDist);
   float fogDistance =
diff --git a/runelite-client/src/main/scripts/ChatBuilder.hash b/runelite-client/src/main/scripts/ChatBuilder.hash
index 9b63917e4..3c0446f53 100644
--- a/runelite-client/src/main/scripts/ChatBuilder.hash
+++ b/runelite-client/src/main/scripts/ChatBuilder.hash
@@ -1,1 +1,1 @@
-4A78CE396EF7493A1FE42C4A29A706F3BA62EE65F99AB118765DD93504ABC682
\ No newline at end of file
+99EBEC97A1D42C6CE5D82295910B6E2C61629AA1D437582D25CA20F3231AEB75
\ No newline at end of file
diff --git a/runelite-client/src/main/scripts/ChatBuilder.rs2asm b/runelite-client/src/main/scripts/ChatBuilder.rs2asm
index 4e458adf9..345b071a1 100644
--- a/runelite-client/src/main/scripts/ChatBuilder.rs2asm
+++ b/runelite-client/src/main/scripts/ChatBuilder.rs2asm
@@ -415,12 +415,12 @@ LABEL393:
    iload                  10
    iconst                 -1
    if_icmpne              LABEL397
-   jump                   LABEL1946
+   jump                   LABEL1997
 LABEL397:
    iload                  9
    iconst                 -1
    if_icmpne              LABEL401
-   jump                   LABEL1946
+   jump                   LABEL1997
 LABEL401:
    iload                  10
    chat_gethistoryex_byuid
@@ -439,7 +439,7 @@ LABEL401:
    invoke                 193
    iconst                 1
    if_icmpeq              CHAT_FILTER
-   jump                   LABEL1942
+   jump                   LABEL1993
 CHAT_FILTER:
    sload                  20                ; Load the message
    iconst                 1                 ; Gets changed to 0 if message is blocked
@@ -452,7 +452,7 @@ CHAT_FILTER:
    iconst                 1                 ; 2nd half of conditional
    sstore                 20                ; Override the message with our filtered message
    if_icmpeq              LABEL419          ; Check if we are building this message
-   jump                   LABEL1942          ; continue to next message, skipping this
+   jump                   LABEL1993          ; continue to next message, skipping this
 LABEL419:
    iload                  11
    sload                  18
@@ -468,7 +468,7 @@ LABEL419:
    invoke                 90
    iconst                 1
    if_icmpeq              LABEL434
-   jump                   LABEL1942
+   jump                   LABEL1993
 LABEL434:
    iconst                 0 ; splitpmbox
    iload                  10 ; message uid
@@ -1671,15 +1671,16 @@ LABEL1509:
       106: LABEL1514
       41: LABEL1514
       44: LABEL1514
-      101: LABEL1631
-      103: LABEL1683
-      104: LABEL1683
-      110: LABEL1683
-      14: LABEL1726
-      109: LABEL1787
-      111: LABEL1830
-      112: LABEL1873
-   jump                   LABEL1916
+      0: LABEL1631
+      101: LABEL1682
+      103: LABEL1734
+      104: LABEL1734
+      110: LABEL1734
+      14: LABEL1777
+      109: LABEL1838
+      111: LABEL1881
+      112: LABEL1924
+   jump                   LABEL1967
 LABEL1514:
    sconst                 "<col=ffffff>"
    sload                  18
@@ -1815,8 +1816,64 @@ LABEL1626:
    iload                  9
    if_setop              
 LABEL1630:
-   jump                   LABEL1928
+   jump                   LABEL1979
 LABEL1631:
+   sload                  18
+   string_length         
+   iconst                 0
+   if_icmpgt              LABEL1636
+   jump                   LABEL1669
+LABEL1636:
+   sload                  0
+   sload                  18
+   removetags            
+   compare               
+   iconst                 0
+   if_icmpne              LABEL1643
+   jump                   LABEL1669
+LABEL1643:
+   iconst                 8
+   sconst                 "Report"
+   iload                  9
+   if_setop              
+   sconst                 "<col=ffffff>"
+   sload                  18
+   sconst                 "</col>"
+   join_string            3
+   iload                  9
+   if_setopbase          
+   iconst                 86
+   iconst                 -2147483644
+   sconst                 "event_opbase"
+   iload                  11
+   sconst                 "isi"
+   iload                  9
+   if_setonop            
+   iconst                 -1
+   sconst                 ""
+   iload                  9
+   if_setonmouseover     
+   iconst                 -1
+   sconst                 ""
+   iload                  9
+   if_setonmouseleave    
+   jump                   LABEL1681
+LABEL1669:
+   iconst                 -1
+   sconst                 ""
+   iload                  9
+   if_setonop            
+   iconst                 -1
+   sconst                 ""
+   iload                  9
+   if_setonmouseover     
+   iconst                 -1
+   sconst                 ""
+   iload                  9
+   if_setonmouseleave    
+LABEL1681:
+   jump                   LABEL1979
+LABEL1682:
    sconst                 "<col=ffffff>"
    sload                  18
    sconst                 "</col>"
@@ -1840,31 +1897,31 @@ LABEL1631:
    if_setonmouseleave    
    invoke                 5548
    iconst                 1
-   if_icmpeq              LABEL1656
-   jump                   LABEL1661
-LABEL1656:
+   if_icmpeq              LABEL1707
+   jump                   LABEL1712
+LABEL1707:
    iconst                 1
    sconst                 "Accept invitation"
    iload                  9
    if_setop              
-   jump                   LABEL1665
-LABEL1661:
+   jump                   LABEL1716
+LABEL1712:
    iconst                 1
    sconst                 "Accept trade"
    iload                  9
    if_setop              
-LABEL1665:
+LABEL1716:
    iload                  15
    iconst                 1
-   if_icmpeq              LABEL1669
-   jump                   LABEL1674
-LABEL1669:
+   if_icmpeq              LABEL1720
+   jump                   LABEL1725
+LABEL1720:
    iconst                 6
    sconst                 "Message"
    iload                  9
    if_setop              
-   jump                   LABEL1682
-LABEL1674:
+   jump                   LABEL1733
+LABEL1725:
    iconst                 6
    sconst                 "Add friend"
    iload                  9
@@ -1873,9 +1930,9 @@ LABEL1674:
    sconst                 "Add ignore"
    iload                  9
    if_setop              
-LABEL1682:
-   jump                   LABEL1928
-LABEL1683:
+LABEL1733:
+   jump                   LABEL1979
+LABEL1734:
    sconst                 "<col=ffffff>"
    sload                  18
    sconst                 "</col>"
@@ -1903,15 +1960,15 @@ LABEL1683:
    if_setop              
    iload                  15
    iconst                 1
-   if_icmpeq              LABEL1712
-   jump                   LABEL1717
-LABEL1712:
+   if_icmpeq              LABEL1763
+   jump                   LABEL1768
+LABEL1763:
    iconst                 6
    sconst                 "Message"
    iload                  9
    if_setop              
-   jump                   LABEL1725
-LABEL1717:
+   jump                   LABEL1776
+LABEL1768:
    iconst                 6
    sconst                 "Add friend"
    iload                  9
@@ -1920,20 +1977,20 @@ LABEL1717:
    sconst                 "Add ignore"
    iload                  9
    if_setop              
-LABEL1725:
-   jump                   LABEL1928
-LABEL1726:
+LABEL1776:
+   jump                   LABEL1979
+LABEL1777:
    sload                  21
    string_length         
    iconst                 0
-   if_icmpgt              LABEL1731
-   jump                   LABEL1760
-LABEL1731:
+   if_icmpgt              LABEL1782
+   jump                   LABEL1811
+LABEL1782:
    iload                  13
    iconst                 -1
-   if_icmpne              LABEL1735
-   jump                   LABEL1760
-LABEL1735:
+   if_icmpne              LABEL1786
+   jump                   LABEL1811
+LABEL1786:
    iconst                 6
    sconst                 "Open"
    iload                  9
@@ -1958,8 +2015,8 @@ LABEL1735:
    sconst                 "Iii"
    iload                  9
    if_setonmouseleave    
-   jump                   LABEL1768
-LABEL1760:
+   jump                   LABEL1819
+LABEL1811:
    iconst                 -1
    sconst                 ""
    iload                  9
@@ -1968,7 +2025,7 @@ LABEL1760:
    sconst                 ""
    iload                  9
    if_setonmouseleave    
-LABEL1768:
+LABEL1819:
    iconst                 9
    sconst                 "Clear history"
    iload                  9
@@ -1987,8 +2044,8 @@ LABEL1768:
    sconst                 "iisi"
    iload                  9
    if_setonop            
-   jump                   LABEL1928
-LABEL1787:
+   jump                   LABEL1979
+LABEL1838:
    sconst                 "<col=0xffffff>"
    sload                  18
    sconst                 "</col>"
@@ -2016,15 +2073,15 @@ LABEL1787:
    if_setonmouseleave    
    iload                  15
    iconst                 1
-   if_icmpeq              LABEL1816
-   jump                   LABEL1821
-LABEL1816:
+   if_icmpeq              LABEL1867
+   jump                   LABEL1872
+LABEL1867:
    iconst                 6
    sconst                 "Message"
    iload                  9
    if_setop              
-   jump                   LABEL1829
-LABEL1821:
+   jump                   LABEL1880
+LABEL1872:
    iconst                 6
    sconst                 "Add friend"
    iload                  9
@@ -2033,9 +2090,9 @@ LABEL1821:
    sconst                 "Add ignore"
    iload                  9
    if_setop              
-LABEL1829:
-   jump                   LABEL1928
-LABEL1830:
+LABEL1880:
+   jump                   LABEL1979
+LABEL1881:
    sconst                 "<col=0xffffff>"
    sload                  18
    sconst                 "</col>"
@@ -2063,15 +2120,15 @@ LABEL1830:
    if_setonmouseleave    
    iload                  15
    iconst                 1
-   if_icmpeq              LABEL1859
-   jump                   LABEL1864
-LABEL1859:
+   if_icmpeq              LABEL1910
+   jump                   LABEL1915
+LABEL1910:
    iconst                 6
    sconst                 "Message"
    iload                  9
    if_setop              
-   jump                   LABEL1872
-LABEL1864:
+   jump                   LABEL1923
+LABEL1915:
    iconst                 6
    sconst                 "Add friend"
    iload                  9
@@ -2080,9 +2137,9 @@ LABEL1864:
    sconst                 "Add ignore"
    iload                  9
    if_setop              
-LABEL1872:
-   jump                   LABEL1928
-LABEL1873:
+LABEL1923:
+   jump                   LABEL1979
+LABEL1924:
    sconst                 "<col=0xffffff>"
    sload                  18
    sconst                 "</col>"
@@ -2110,15 +2167,15 @@ LABEL1873:
    if_setonmouseleave    
    iload                  15
    iconst                 1
-   if_icmpeq              LABEL1902
-   jump                   LABEL1907
-LABEL1902:
+   if_icmpeq              LABEL1953
+   jump                   LABEL1958
+LABEL1953:
    iconst                 6
    sconst                 "Message"
    iload                  9
    if_setop              
-   jump                   LABEL1915
-LABEL1907:
+   jump                   LABEL1966
+LABEL1958:
    iconst                 6
    sconst                 "Add friend"
    iload                  9
@@ -2127,9 +2184,9 @@ LABEL1907:
    sconst                 "Add ignore"
    iload                  9
    if_setop              
-LABEL1915:
-   jump                   LABEL1928
-LABEL1916:
+LABEL1966:
+   jump                   LABEL1979
+LABEL1967:
    iconst                 -1
    sconst                 ""
    iload                  9
@@ -2142,7 +2199,7 @@ LABEL1916:
    sconst                 ""
    iload                  9
    if_setonmouseleave    
-LABEL1928:
+LABEL1979:
    iload                  6
    iload                  7
    sub                   
@@ -2157,20 +2214,20 @@ LABEL1928:
    iload                  8
    enum                  
    istore                 9
-LABEL1942:
+LABEL1993:
    iload                  10
    chat_getprevuid       
    istore                 10
    jump                   LABEL393
-LABEL1946:
+LABEL1997:
    iload                  8
    istore                 22
-LABEL1948:
+LABEL1999:
    iload                  9
    iconst                 -1
-   if_icmpne              LABEL1952
-   jump                   LABEL2035
-LABEL1952:
+   if_icmpne              LABEL2003
+   jump                   LABEL2086
+LABEL2003:
    iload                  9
    if_clearops           
    iconst                 -1
@@ -2197,14 +2254,14 @@ LABEL1952:
    multiply              
    cc_find               
    iconst                 1
-   if_icmpeq              LABEL1980
-   jump                   LABEL1984
-LABEL1980:
+   if_icmpeq              LABEL2031
+   jump                   LABEL2035
+LABEL2031:
    sconst                 ""
    cc_settext            
    iconst                 1
    cc_sethide            
-LABEL1984:
+LABEL2035:
    iconst                 10616888
    iload                  8
    iconst                 4
@@ -2213,14 +2270,14 @@ LABEL1984:
    add                   
    cc_find               
    iconst                 1
-   if_icmpeq              LABEL1994
-   jump                   LABEL1998
-LABEL1994:
+   if_icmpeq              LABEL2045
+   jump                   LABEL2049
+LABEL2045:
    sconst                 ""
    cc_settext            
    iconst                 1
    cc_sethide            
-LABEL1998:
+LABEL2049:
    iconst                 10616888
    iload                  8
    iconst                 4
@@ -2229,14 +2286,14 @@ LABEL1998:
    add                   
    cc_find               
    iconst                 1
-   if_icmpeq              LABEL2008
-   jump                   LABEL2012
-LABEL2008:
+   if_icmpeq              LABEL2059
+   jump                   LABEL2063
+LABEL2059:
    sconst                 ""
    cc_settext            
    iconst                 1
    cc_sethide            
-LABEL2012:
+LABEL2063:
    iconst                 10616888
    iload                  8
    iconst                 4
@@ -2245,12 +2302,12 @@ LABEL2012:
    add                   
    cc_find               
    iconst                 1
-   if_icmpeq              LABEL2022
-   jump                   LABEL2024
-LABEL2022:
+   if_icmpeq              LABEL2073
+   jump                   LABEL2075
+LABEL2073:
    iconst                 1
    cc_sethide            
-LABEL2024:
+LABEL2075:
    iload                  8
    iconst                 1
    add                   
@@ -2261,8 +2318,8 @@ LABEL2024:
    iload                  8
    enum                  
    istore                 9
-   jump                   LABEL1948
-LABEL2035:
+   jump                   LABEL1999
+LABEL2086:
    iload                  6
    iconst                 2
    sub                   
@@ -2276,20 +2333,20 @@ LABEL2035:
    istore                 23
    iload                  6
    iload                  23
-   if_icmpgt              LABEL2050
-   jump                   LABEL2052
-LABEL2050:
+   if_icmpgt              LABEL2101
+   jump                   LABEL2103
+LABEL2101:
    iload                  6
    istore                 23
-LABEL2052:
+LABEL2103:
    iload                  22
    istore                 8
-LABEL2054:
+LABEL2105:
    iload                  8
    iconst                 0
-   if_icmpgt              LABEL2058
-   jump                   LABEL2141
-LABEL2058:
+   if_icmpgt              LABEL2109
+   jump                   LABEL2192
+LABEL2109:
    iload                  8
    iconst                 1
    sub                   
@@ -2320,15 +2377,15 @@ LABEL2058:
    multiply              
    cc_find               
    iconst                 1
-   if_icmpeq              LABEL2090
-   jump                   LABEL2095
-LABEL2090:
+   if_icmpeq              LABEL2141
+   jump                   LABEL2146
+LABEL2141:
    cc_getx               
    iload                  6
    iconst                 0
    iconst                 0
    cc_setposition        
-LABEL2095:
+LABEL2146:
    iconst                 10616888
    iload                  8
    iconst                 4
@@ -2337,15 +2394,15 @@ LABEL2095:
    add                   
    cc_find               
    iconst                 1
-   if_icmpeq              LABEL2105
-   jump                   LABEL2110
-LABEL2105:
+   if_icmpeq              LABEL2156
+   jump                   LABEL2161
+LABEL2156:
    cc_getx               
    iload                  6
    iconst                 0
    iconst                 0
    cc_setposition        
-LABEL2110:
+LABEL2161:
    iconst                 10616888
    iload                  8
    iconst                 4
@@ -2354,15 +2411,15 @@ LABEL2110:
    add                   
    cc_find               
    iconst                 1
-   if_icmpeq              LABEL2120
-   jump                   LABEL2125
-LABEL2120:
+   if_icmpeq              LABEL2171
+   jump                   LABEL2176
+LABEL2171:
    cc_getx               
    iload                  6
    iconst                 0
    iconst                 0
    cc_setposition        
-LABEL2125:
+LABEL2176:
    iconst                 10616888
    iload                  8
    iconst                 4
@@ -2371,17 +2428,17 @@ LABEL2125:
    add                   
    cc_find               
    iconst                 1
-   if_icmpeq              LABEL2135
-   jump                   LABEL2140
-LABEL2135:
+   if_icmpeq              LABEL2186
+   jump                   LABEL2191
+LABEL2186:
    cc_getx               
    iload                  6
    iconst                 0
    iconst                 0
    cc_setposition        
-LABEL2140:
-   jump                   LABEL2054
-LABEL2141:
+LABEL2191:
+   jump                   LABEL2105
+LABEL2192:
    iconst                 0
    iload                  23
    iconst                 10616888
diff --git a/runelite-client/src/main/scripts/MagicSpellbookRedraw.rs2asm b/runelite-client/src/main/scripts/MagicSpellbookRedraw.rs2asm
index 2d46babee..61e9635ad 100644
--- a/runelite-client/src/main/scripts/MagicSpellbookRedraw.rs2asm
+++ b/runelite-client/src/main/scripts/MagicSpellbookRedraw.rs2asm
@@ -382,7 +382,7 @@ LABEL343:
    iconst                 0
    istore                 25
    iload                  22
-   iconst                 2
+   iconst                 1 ; run our filter+sort even when there is only one vanilla spell
    if_icmpge              LABEL349
    jump                   LABEL357
 LABEL349:
diff --git a/runelite-jshell/pom.xml b/runelite-jshell/pom.xml
index 58bf6ddbd..704852cdb 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.28-SNAPSHOT</version>
+        <version>1.10.29-SNAPSHOT</version>
     </parent>
 
     <artifactId>jshell</artifactId>
diff --git a/runelite-maven-plugin/pom.xml b/runelite-maven-plugin/pom.xml
index 5b8c10eab..2fb805abb 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.28-SNAPSHOT</version>
+		<version>1.10.29-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>runelite-maven-plugin</artifactId>


Test patch:
diff --git a/runelite-client/src/test/java/net/runelite/client/config/KeybindTest.java b/runelite-client/src/test/java/net/runelite/client/config/KeybindTest.java
new file mode 100644
index 000000000..14c1a34f2
--- /dev/null
+++ b/runelite-client/src/test/java/net/runelite/client/config/KeybindTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.config;
+
+import static java.awt.event.InputEvent.CTRL_DOWN_MASK;
+import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
+import java.awt.event.KeyEvent;
+import static java.awt.event.KeyEvent.KEY_PRESSED;
+import static java.awt.event.KeyEvent.KEY_RELEASED;
+import static java.awt.event.KeyEvent.VK_CONTROL;
+import static java.awt.event.KeyEvent.VK_SHIFT;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class KeybindTest
+{
+	/**
+	 * Regression test for a bug where a modifier key keybind will not match key release if other modifiers are held down.
+	 */
+	@Test
+	public void testModifierOnlyKeybindReleaseWithOtherModifierKeyHeldDown()
+	{
+		Keybind keybind = new Keybind(VK_CONTROL, CTRL_DOWN_MASK);
+
+		// when a modifier key is pressed, the keyevent will have the modifier in the modifiers field and in the keyCode field.
+		// when it is released, it will only be in the keyCode field.
+		Assert.assertTrue(keybind.matches(createKeyEvent(KEY_PRESSED, CTRL_DOWN_MASK, VK_CONTROL)));
+		Assert.assertFalse(keybind.matches(createKeyEvent(KEY_PRESSED, SHIFT_DOWN_MASK | CTRL_DOWN_MASK, VK_SHIFT)));
+		Assert.assertTrue(keybind.matches(createKeyEvent(KEY_RELEASED, SHIFT_DOWN_MASK, VK_CONTROL)));
+		Assert.assertFalse(keybind.matches(createKeyEvent(KEY_RELEASED, 0, VK_SHIFT)));
+	}
+
+	private KeyEvent createKeyEvent(int id, int modifiersEx, int extendedKeyCode)
+	{
+		KeyEvent mock = Mockito.mock(KeyEvent.class);
+		Mockito.when(mock.getExtendedKeyCode()).thenReturn(extendedKeyCode);
+		Mockito.when(mock.getModifiersEx()).thenReturn(modifiersEx);
+		Mockito.when(mock.getID()).thenReturn(id);
+		return mock;
+	}
+}
diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/cannon/CannonPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/cannon/CannonPluginTest.java
index a13e29e04..47efe5541 100644
--- a/runelite-client/src/test/java/net/runelite/client/plugins/cannon/CannonPluginTest.java
+++ b/runelite-client/src/test/java/net/runelite/client/plugins/cannon/CannonPluginTest.java
@@ -104,11 +104,11 @@ public class CannonPluginTest
 	@Test
 	public void testAmmoCountOnPlace()
 	{
-		ChatMessage chatMessage = new ChatMessage();
-		chatMessage.setType(ChatMessageType.SPAM);
-		chatMessage.setMessage("You add the furnace.");
+		VarbitChanged varbitChanged = new VarbitChanged();
+		varbitChanged.setVarpId(VarPlayer.CANNON_STATE);
+		varbitChanged.setValue(4);
 
-		plugin.onChatMessage(chatMessage);
+		plugin.onVarbitChanged(varbitChanged);
 		assertTrue(plugin.isCannonPlaced());
 
 		plugin.onVarbitChanged(cannonAmmoChanged);
@@ -126,11 +126,11 @@ public class CannonPluginTest
 	{
 		when(config.showInfobox()).thenReturn(true);
 
-		ChatMessage chatMessage = new ChatMessage();
-		chatMessage.setType(ChatMessageType.SPAM);
-		chatMessage.setMessage("You add the furnace.");
+		VarbitChanged varbitChanged = new VarbitChanged();
+		varbitChanged.setVarpId(VarPlayer.CANNON_STATE);
+		varbitChanged.setValue(4);
 
-		plugin.onChatMessage(chatMessage);
+		plugin.onVarbitChanged(varbitChanged);
 		assertTrue(plugin.isCannonPlaced());
 
 		assertEquals(0, plugin.getCballsLeft());
diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/GroundItemsPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/GroundItemsPluginTest.java
index 6ed28b867..1c3d2e238 100644
--- a/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/GroundItemsPluginTest.java
+++ b/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/GroundItemsPluginTest.java
@@ -33,7 +33,6 @@ import net.runelite.api.Client;
 import net.runelite.api.ItemComposition;
 import net.runelite.api.ItemID;
 import net.runelite.api.ItemLayer;
-import net.runelite.api.Player;
 import net.runelite.api.Tile;
 import net.runelite.api.TileItem;
 import net.runelite.api.coords.WorldPoint;
@@ -114,7 +113,6 @@ public class GroundItemsPluginTest
 			return null;
 		}).when(executor).execute(any(Runnable.class));
 
-		when(client.getLocalPlayer()).thenReturn(mock(Player.class));
 		when(config.getHiddenItems()).thenReturn("");
 		when(config.showLootbeamForHighlighted()).thenReturn(false);
 		when(config.showLootbeamTier()).thenReturn(HighlightTier.OFF);