Repository: runelite/runelite
Pull request #17266 (2023-12-09 00:45:53)

fix ground items inferno despawn timer
Made change to GroundItemsOverlay.java to amend wrong despawn timer in the Inferno as described in issue #17256 


Patch:
diff --git a/runelite-api/pom.xml b/runelite-api/pom.xml
index 78e7a9956..5df254dd7 100644
--- a/runelite-api/pom.xml
+++ b/runelite-api/pom.xml
@@ -97,7 +97,6 @@
 								<exclude>net/runelite/api/widgets/ComponentID.class</exclude>
 								<exclude>net/runelite/api/widgets/InterfaceID.class</exclude>
 								<exclude>net/runelite/api/widgets/ItemQuantityMode.class</exclude>
-								<exclude>net/runelite/api/widgets/WidgetConfig.class</exclude>
 								<exclude>net/runelite/api/widgets/WidgetID*.class</exclude>
 								<exclude>net/runelite/api/widgets/WidgetModalMode.class</exclude>
 								<exclude>net/runelite/api/widgets/WidgetModelType.class</exclude>
diff --git a/runelite-api/src/main/java/net/runelite/api/EnumID.java b/runelite-api/src/main/java/net/runelite/api/EnumID.java
index 70bf124c9..111f694ec 100644
--- a/runelite-api/src/main/java/net/runelite/api/EnumID.java
+++ b/runelite-api/src/main/java/net/runelite/api/EnumID.java
@@ -51,12 +51,14 @@ public final class EnumID
 	 */
 	public static final int PETS = 2158;
 	public static final int POH_COSTUME_MEMBERS = 3077;
-	public static final int POH_COSTUMES_CLUE_BEGINNER = 3293;
-	public static final int POH_COSTUMES_CLUE_EASY = 3294;
-	public static final int POH_COSTUMES_CLUE_MEDIUM = 3295;
-	public static final int POH_COSTUMES_CLUE_HARD = 3296;
-	public static final int POH_COSTUMES_CLUE_ELITE = 3297;
-	public static final int POH_COSTUMES_CLUE_MASTER = 3298;
+	public static final int POH_COSTUME_CLUE_BEGINNER = 3293;
+	public static final int POH_COSTUME_CLUE_EASY = 3294;
+	public static final int POH_COSTUME_CLUE_MEDIUM = 3295;
+	public static final int POH_COSTUME_CLUE_HARD = 3296;
+	public static final int POH_COSTUME_CLUE_ELITE = 3297;
+	public static final int POH_COSTUME_CLUE_MASTER = 3298;
+	public static final int POH_COSTUME_ALTERNATE = 3303;
+	public static final int POH_COSTUME_ALTERNATES = 3304;
 	public static final int CLAN_RANK_NAME = 3797;
 	public static final int CLAN_RANK_GRAPHIC = 3798;
 
diff --git a/runelite-api/src/main/java/net/runelite/api/SceneTileModel.java b/runelite-api/src/main/java/net/runelite/api/SceneTileModel.java
index b7abcab08..f258355fb 100644
--- a/runelite-api/src/main/java/net/runelite/api/SceneTileModel.java
+++ b/runelite-api/src/main/java/net/runelite/api/SceneTileModel.java
@@ -77,6 +77,8 @@ public interface SceneTileModel
 
 	int[] getTriangleTextureId();
 
+	boolean isFlat();
+
 	int getBufferOffset();
 	void setBufferOffset(int bufferOffset);
 
diff --git a/runelite-api/src/main/java/net/runelite/api/SceneTilePaint.java b/runelite-api/src/main/java/net/runelite/api/SceneTilePaint.java
index 0247c17e8..59fd9b264 100644
--- a/runelite-api/src/main/java/net/runelite/api/SceneTilePaint.java
+++ b/runelite-api/src/main/java/net/runelite/api/SceneTilePaint.java
@@ -46,6 +46,8 @@ public interface SceneTilePaint
 
 	int getTexture();
 
+	boolean isFlat();
+
 	int getBufferOffset();
 	void setBufferOffset(int bufferOffset);
 
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java
index e347b6e65..9aa2aa5e5 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java
@@ -47,7 +47,6 @@ import net.runelite.api.MenuEntry;
 import net.runelite.api.VarClientStr;
 import net.runelite.api.events.GrandExchangeSearched;
 import net.runelite.api.events.MenuEntryAdded;
-import net.runelite.api.events.MenuOptionClicked;
 import net.runelite.api.events.ScriptCallbackEvent;
 import net.runelite.api.widgets.ComponentID;
 import net.runelite.api.widgets.Widget;
@@ -414,12 +413,6 @@ public class BankTagsPlugin extends Plugin
 			.build();
 	}
 
-	@Subscribe
-	public void onMenuOptionClicked(MenuOptionClicked event)
-	{
-		tabInterface.handleClick(event);
-	}
-
 	@Subscribe
 	public void onConfigChanged(ConfigChanged configChanged)
 	{
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java
index 9c6de6539..5778c4830 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java
@@ -78,6 +78,7 @@ import net.runelite.api.widgets.Widget;
 import net.runelite.api.widgets.WidgetConfig;
 import net.runelite.api.widgets.WidgetSizeMode;
 import net.runelite.api.widgets.WidgetType;
+import net.runelite.api.widgets.WidgetUtil;
 import net.runelite.client.callback.ClientThread;
 import net.runelite.client.chat.ChatMessageManager;
 import net.runelite.client.chat.QueuedMessage;
@@ -681,14 +682,21 @@ public class TabInterface
 		}
 	}
 
-	public void handleClick(MenuOptionClicked event)
+	@Subscribe
+	public void onMenuOptionClicked(MenuOptionClicked event)
 	{
+		// Close the chatbox input when clicking on things in the bank, to mimic how actions like withdrawing
+		// items or changing tabs close the withdraw-x input or the bank search input.
 		if (chatboxPanelManager.getCurrentInput() != null
-			&& event.getMenuAction() != MenuAction.CANCEL
+			&& event.getWidget() != null
 			&& !event.getMenuOption().equals(SCROLL_UP)
 			&& !event.getMenuOption().equals(SCROLL_DOWN))
 		{
-			chatboxPanelManager.close();
+			int interfaceId = WidgetUtil.componentToInterface(event.getWidget().getId());
+			if (interfaceId == InterfaceID.BANK || interfaceId == InterfaceID.BANK_INVENTORY)
+			{
+				chatboxPanelManager.close();
+			}
 		}
 
 		if (event.getMenuOption().startsWith("View tab") || event.getMenuOption().equals("View all items"))
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterPlugin.java
index 626964562..c93c0dd24 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterPlugin.java
@@ -32,6 +32,7 @@ import com.google.common.collect.ImmutableSet;
 import com.google.inject.Provides;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -104,7 +105,7 @@ public class ChatFilterPlugin extends Plugin
 		int count;
 	}
 
-	private final LinkedHashMap<String, Duplicate> duplicateChatCache = new LinkedHashMap<String, Duplicate>()
+	private final LinkedHashMap<String, Duplicate> duplicateChatCache = new LinkedHashMap<>()
 	{
 		private static final int MAX_ENTRIES = 100;
 
@@ -115,6 +116,19 @@ public class ChatFilterPlugin extends Plugin
 		}
 	};
 
+	private static class FilterCacheMap extends LinkedHashMap<Integer, String>
+	{
+		private static final int MAX_ENTRIES = 100;
+
+		@Override
+		protected boolean removeEldestEntry(Map.Entry<Integer, String> eldest)
+		{
+			return size() > MAX_ENTRIES;
+		}
+	}
+
+	private final Map<ChatMessageType, FilterCacheMap> filterCache = new HashMap<>();
+
 	@Inject
 	private Client client;
 
@@ -139,6 +153,8 @@ public class ChatFilterPlugin extends Plugin
 	{
 		filteredPatterns = Collections.emptyList();
 		filteredNamePatterns = Collections.emptyList();
+		duplicateChatCache.clear();
+		filterCache.clear();
 		client.refreshChat();
 	}
 
@@ -149,9 +165,11 @@ public class ChatFilterPlugin extends Plugin
 		{
 			// Login drops references to all messages and also resets the global message id counter.
 			// Invalidate the message id so it doesn't collide later when rebuilding the chatfilter.
+			case CONNECTION_LOST:
 			case HOPPING:
 			case LOGGING_IN:
 				duplicateChatCache.values().forEach(d -> d.messageId = -1);
+				filterCache.clear();
 		}
 	}
 
@@ -190,9 +208,9 @@ public class ChatFilterPlugin extends Plugin
 			case CLAN_CHAT:
 			case CLAN_GUEST_CHAT:
 			case CLAN_GIM_CHAT:
-				if (shouldFilterPlayerMessage(Text.removeTags(name)))
+				if (canFilterPlayer(Text.removeTags(name)))
 				{
-					message = censorMessage(name, message);
+					message = censorMessage(messageNode, name, message);
 					blockMessage = message == null;
 				}
 				break;
@@ -207,7 +225,7 @@ public class ChatFilterPlugin extends Plugin
 			case CLAN_GIM_MESSAGE:
 				if (config.filterGameChat())
 				{
-					message = censorMessage(null, message);
+					message = censorMessage(messageNode, null, message);
 					blockMessage = message == null;
 				}
 				break;
@@ -251,7 +269,7 @@ public class ChatFilterPlugin extends Plugin
 	@Subscribe
 	public void onOverheadTextChanged(OverheadTextChanged event)
 	{
-		if (!(event.getActor() instanceof Player) || event.getActor().getName() == null || !shouldFilterPlayerMessage(event.getActor().getName())) // NOPMD: SimplifyConditional
+		if (!(event.getActor() instanceof Player) || event.getActor().getName() == null || !canFilterPlayer(event.getActor().getName())) // NOPMD: SimplifyConditional
 		{
 			return;
 		}
@@ -286,7 +304,7 @@ public class ChatFilterPlugin extends Plugin
 		}
 	}
 
-	boolean shouldFilterPlayerMessage(String playerName)
+	boolean canFilterPlayer(String playerName)
 	{
 		boolean isMessageFromSelf = playerName.equals(client.getLocalPlayer().getName());
 		return !isMessageFromSelf &&
@@ -327,7 +345,7 @@ public class ChatFilterPlugin extends Plugin
 		String strippedAccents = stripAccents(strippedMessage);
 		assert strippedMessage.length() == strippedAccents.length();
 
-		if (username != null && shouldFilterByName(username))
+		if (username != null && isNameFiltered(username))
 		{
 			switch (config.filterType())
 			{
@@ -374,6 +392,25 @@ public class ChatFilterPlugin extends Plugin
 		return filtered ? strippedMessage : message;
 	}
 
+	private String censorMessage(MessageNode messageNode, String username, String message)
+	{
+		FilterCacheMap map = this.filterCache.get(messageNode.getType());
+		if (map == null)
+		{
+			map = new FilterCacheMap();
+			this.filterCache.put(messageNode.getType(), map);
+		}
+
+		if (map.containsKey(messageNode.getId()))
+		{
+			return map.get(messageNode.getId());
+		}
+
+		String censoredMessage = censorMessage(username, message);
+		map.put(messageNode.getId(), censoredMessage);
+		return censoredMessage;
+	}
+
 	void updateFilteredPatterns()
 	{
 		List<Pattern> patterns = new ArrayList<>();
@@ -398,6 +435,8 @@ public class ChatFilterPlugin extends Plugin
 
 		filteredPatterns = patterns;
 		filteredNamePatterns = namePatterns;
+
+		filterCache.clear();
 	}
 
 	private String stripAccents(String input)
@@ -432,7 +471,7 @@ public class ChatFilterPlugin extends Plugin
 	}
 
 	@VisibleForTesting
-	boolean shouldFilterByName(final String playerName)
+	boolean isNameFiltered(final String playerName)
 	{
 		String sanitizedName = Text.standardize(playerName);
 		for (Pattern pattern : filteredNamePatterns)
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java
index 63b18f2b4..08b361d1c 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java
@@ -1207,17 +1207,20 @@ public class ClueScrollPlugin extends Plugin
 		return false;
 	}
 
+	// from [proc,poh_costumes_countmembers] and [proc,poh_costumes_countalternates]
 	private boolean testTreasureChestTag(int itemId)
 	{
 		EnumComposition members = client.getEnum(EnumID.POH_COSTUME_MEMBERS);
 		EnumComposition[] enums = {
-			client.getEnum(EnumID.POH_COSTUMES_CLUE_BEGINNER),
-			client.getEnum(EnumID.POH_COSTUMES_CLUE_EASY),
-			client.getEnum(EnumID.POH_COSTUMES_CLUE_MEDIUM),
-			client.getEnum(EnumID.POH_COSTUMES_CLUE_HARD),
-			client.getEnum(EnumID.POH_COSTUMES_CLUE_ELITE),
-			client.getEnum(EnumID.POH_COSTUMES_CLUE_MASTER)
+			client.getEnum(EnumID.POH_COSTUME_CLUE_BEGINNER),
+			client.getEnum(EnumID.POH_COSTUME_CLUE_EASY),
+			client.getEnum(EnumID.POH_COSTUME_CLUE_MEDIUM),
+			client.getEnum(EnumID.POH_COSTUME_CLUE_HARD),
+			client.getEnum(EnumID.POH_COSTUME_CLUE_ELITE),
+			client.getEnum(EnumID.POH_COSTUME_CLUE_MASTER)
 		};
+		EnumComposition alt = client.getEnum(EnumID.POH_COSTUME_ALTERNATE);
+		EnumComposition alts = client.getEnum(EnumID.POH_COSTUME_ALTERNATES);
 		for (var tierEnum : enums)
 		{
 			for (int baseItem : tierEnum.getIntVals())
@@ -1228,16 +1231,27 @@ public class ClueScrollPlugin extends Plugin
 				}
 
 				int membersEnumId = members.getIntValue(baseItem);
-				if (membersEnumId == -1)
+				if (membersEnumId != -1)
 				{
-					continue;
-				}
+					// check members in the group
+					var memberEnum = client.getEnum(membersEnumId);
+					for (int memberItem : memberEnum.getIntVals())
+					{
+						if (memberItem == itemId)
+						{
+							return true;
+						}
 
-				// check members in the group
-				var memberEnum = client.getEnum(membersEnumId);
-				for (int memberItem : memberEnum.getIntVals())
+						if (checkAlternates(alt, alts, itemId, memberItem))
+						{
+							return true;
+						}
+					}
+				}
+				else
 				{
-					if (memberItem == itemId)
+					// single member group
+					if (checkAlternates(alt, alts, itemId, baseItem))
 					{
 						return true;
 					}
@@ -1247,6 +1261,29 @@ public class ClueScrollPlugin extends Plugin
 		return false;
 	}
 
+	private boolean checkAlternates(EnumComposition alt, EnumComposition alts, int targetItemId, int checkItemId)
+	{
+		if (alt.getIntValue(checkItemId) == targetItemId)
+		{
+			return true;
+		}
+
+		int altsEnumId = alts.getIntValue(checkItemId);
+		if (altsEnumId != -1)
+		{
+			var e = client.getEnum(altsEnumId);
+			for (int item : e.getIntVals())
+			{
+				if (item == targetItemId)
+				{
+					return true;
+				}
+			}
+		}
+
+		return false;
+	}
+
 	private void updateOverlayMenuEntries()
 	{
 		clueScrollOverlay.removeMenuEntry(RUNELITE_OVERLAY, "Set note", "Clue Scroll overlay");
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java
index 54476e48d..91e852cf6 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java
@@ -219,7 +219,7 @@ public class CrypticClue extends ClueScroll implements NpcClueScroll, ObjectClue
 		new CrypticClue("This crate holds a better reward than a broken arrow.", CRATE_356, new WorldPoint(2671, 3437, 0), "Inside the Ranging Guild. Search the crate behind the northern most building."),
 		new CrypticClue("Search the drawers in the house next to the Port Sarim mage shop.", DRAWERS, new WorldPoint(3024, 3259, 0), "House east of Betty's Mage shop in Port Sarim. Contains a cooking sink."),
 		new CrypticClue("With a name like that, you'd expect a little more than just a few scimitars.", "Daga", new WorldPoint(2759, 2775, 0), "Speak to Daga on Ape Atoll."),
-		new CrypticClue("Strength potions with red spiders' eggs? He is quite a herbalist.", "Apothecary", new WorldPoint(3194, 3403, 0), "Talk to Apothecary in the South-western Varrock. (the) apothecary is just north-west of the Varrock Swordshop."),
+		new CrypticClue("Strength potions with red spiders' eggs? He is quite a herbalist.", "Apothecary", new WorldPoint(3194, 3403, 0), "Talk to the Apothecary in south-west Varrock, just north-west of the Varrock Swordshop."),
 		new CrypticClue("Robin wishes to see your finest ranged equipment.", "Robin", new WorldPoint(3673, 3492, 0), "Robin at the inn in Port Phasmatys. Speak to him with +181 in ranged attack bonus. Bonus granted by the toxic blowpipe is ignored."),
 		new CrypticClue("You will need to under-cook to solve this one.", CRATE_357, new WorldPoint(3219, 9617, 0), "Search the crate in the Lumbridge basement."),
 		new CrypticClue("Search through some drawers found in Taverley's houses.", DRAWERS_350, new WorldPoint(2894, 3418, 0), "The south-eastern most house in Taverley, south of Jatix's Herblore Shop."),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java
index 6c2b46df8..b4c9bbb64 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java
@@ -117,7 +117,7 @@ public class EmoteClue extends ClueScroll implements LocationClueScroll
 		new EmoteClue("Headbang in the Fight Arena pub. Equip a pirate bandana, a dragonstone necklace and and a magic longbow.", "Fight Arena pub", OUTSIDE_THE_BAR_BY_THE_FIGHT_ARENA, new WorldPoint(2568, 3149, 0), HEADBANG, any("Any pirate bandana", item(PIRATE_BANDANA), item(PIRATE_BANDANA_7124), item(PIRATE_BANDANA_7130), item(PIRATE_BANDANA_7136)), item(DRAGON_NECKLACE), item(MAGIC_LONGBOW)),
 		new EmoteClue("Do a jig at the barrows chest. Beware of double agents! Equip any full barrows set.", "Barrows chest", BARROWS_CHEST, new WorldPoint(3551, 9694, 0), DOUBLE_AGENT_141, JIG, any("Any full barrows set", all(any("Ahrim's hood", item(AHRIMS_HOOD), range(AHRIMS_HOOD_100, AHRIMS_HOOD_0)), any("Ahrim's staff", item(AHRIMS_STAFF), range(AHRIMS_STAFF_100, AHRIMS_STAFF_0)), any("Ahrim's robetop", item(AHRIMS_ROBETOP), range(AHRIMS_ROBETOP_100, AHRIMS_ROBETOP_0)), any("Ahrim's robeskirt", item(AHRIMS_ROBESKIRT), range(AHRIMS_ROBESKIRT_100, AHRIMS_ROBESKIRT_0))), all(any("Dharok's helm", item(DHAROKS_HELM), range(DHAROKS_HELM_100, DHAROKS_HELM_0)), any("Dharok's greataxe", item(DHAROKS_GREATAXE), range(DHAROKS_GREATAXE_100, DHAROKS_GREATAXE_0)), any("Dharok's platebody", item(DHAROKS_PLATEBODY), range(DHAROKS_PLATEBODY_100, DHAROKS_PLATEBODY_0)), any("Dharok's platelegs", item(DHAROKS_PLATELEGS), range(DHAROKS_PLATELEGS_100, DHAROKS_PLATELEGS_0))), all(any("Guthan's helm", item(GUTHANS_HELM), range(GUTHANS_HELM_100, GUTHANS_HELM_0)), any("Guthan's warspear", item(GUTHANS_WARSPEAR), range(GUTHANS_WARSPEAR_100, GUTHANS_WARSPEAR_0)), any("Guthan's platebody", item(GUTHANS_PLATEBODY), range(GUTHANS_PLATEBODY_100, GUTHANS_PLATEBODY_0)), any("Guthan's chainskirt", item(GUTHANS_CHAINSKIRT), range(GUTHANS_CHAINSKIRT_100, GUTHANS_CHAINSKIRT_0))), all(any("Karil's coif", item(KARILS_COIF), range(KARILS_COIF_100, KARILS_COIF_0)), any("Karil's crossbow", item(KARILS_CROSSBOW), range(KARILS_CROSSBOW_100, KARILS_CROSSBOW_0)), any("Karil's leathertop", item(KARILS_LEATHERTOP), range(KARILS_LEATHERTOP_100, KARILS_LEATHERTOP_0)), any("Karil's leatherskirt", item(KARILS_LEATHERSKIRT), range(KARILS_LEATHERSKIRT_100, KARILS_LEATHERSKIRT_0))), all(any("Torag's helm", item(TORAGS_HELM), range(TORAGS_HELM_100, TORAGS_HELM_0)), any("Torag's hammers", item(TORAGS_HAMMERS), range(TORAGS_HAMMERS_100, TORAGS_HAMMERS_0)), any("Torag's platebody", item(TORAGS_PLATEBODY), range(TORAGS_PLATEBODY_100, TORAGS_PLATEBODY_0)), any("Torag's platelegs", item(TORAGS_PLATELEGS), range(TORAGS_PLATELEGS_100, TORAGS_PLATELEGS_0))), all(any("Verac's helm", item(VERACS_HELM), range(VERACS_HELM_100, VERACS_HELM_0)), any("Verac's flail", item(VERACS_FLAIL), range(VERACS_FLAIL_100, VERACS_FLAIL_0)), any("Verac's brassard", item(VERACS_BRASSARD), range(VERACS_BRASSARD_100, VERACS_BRASSARD_0)), any("Verac's plateskirt", item(VERACS_PLATESKIRT), range(VERACS_PLATESKIRT_100, VERACS_PLATESKIRT_0))))),
 		new EmoteClue("Jig at Jiggig. Beware of double agents! Equip a Rune spear, rune platelegs and any rune heraldic helm.", "Jiggig", IN_THE_MIDDLE_OF_JIGGIG, new WorldPoint(2477, 3047, 0), DOUBLE_AGENT_108, JIG, range("Any rune heraldic helm", RUNE_HELM_H1, RUNE_HELM_H5), item(RUNE_SPEAR), item(RUNE_PLATELEGS)),
-		new EmoteClue("Cheer at the games room. Have nothing equipped at all when you do.", "Games room", null, new WorldPoint(2207, 4952, 0), CHEER, emptySlot("Nothing at all", HEAD, CAPE, AMULET, WEAPON, BODY, SHIELD, LEGS, GLOVES, BOOTS, RING, AMMO)),
+		new EmoteClue("Cheer at the games room. Have nothing equipped at all when you do.", "Burthorpe Games Room", null, new WorldPoint(2207, 4952, 0), CHEER, emptySlot("Nothing at all", HEAD, CAPE, AMULET, WEAPON, BODY, SHIELD, LEGS, GLOVES, BOOTS, RING, AMMO)),
 		new EmoteClue("Panic on the pier where you catch the Fishing trawler. Have nothing equipped at all when you do.", "Fishing trawler", null, new WorldPoint(2676, 3169, 0), PANIC, emptySlot("Nothing at all", HEAD, CAPE, AMULET, WEAPON, BODY, SHIELD, LEGS, GLOVES, BOOTS, RING, AMMO)),
 		new EmoteClue("Panic in the heart of the Haunted Woods. Beware of double agents! Have no items equipped when you do.", "Haunted Woods (ALQ)", null, new WorldPoint(3611, 3492, 0), DOUBLE_AGENT_108, PANIC, emptySlot("Nothing at all", HEAD, CAPE, AMULET, WEAPON, BODY, SHIELD, LEGS, GLOVES, BOOTS, RING, AMMO)),
 		new EmoteClue("Show your anger towards the Statue of Saradomin in Ellamaria's garden. Beware of double agents! Equip a zamorak godsword.", "Varrock Castle", BY_THE_BEAR_CAGE_IN_VARROCK_PALACE_GARDENS, new WorldPoint(3230, 3478, 0), DOUBLE_AGENT_141, ANGRY, any("Zamorak godsword", item(ZAMORAK_GODSWORD), item(ZAMORAK_GODSWORD_OR))),
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 4619305dd..ef4de1efe 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
@@ -201,7 +201,11 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 
 	private final GLBuffer uniformBuffer = new GLBuffer("uniform buffer");
 
-	private GpuIntBuffer vertexBuffer;
+	private final IntBuffer uniformBuf = ByteBuffer.allocateDirect(32 * Integer.BYTES)
+		.order(ByteOrder.nativeOrder())
+		.asIntBuffer();
+
+	private GpuFloatBuffer vertexBuffer;
 	private GpuFloatBuffer uvBuffer;
 
 	private GpuIntBuffer modelBufferUnordered;
@@ -276,7 +280,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 
 	private int sceneId;
 	private int nextSceneId;
-	private GpuIntBuffer nextSceneVertexBuffer;
+	private GpuFloatBuffer nextSceneVertexBuffer;
 	private GpuFloatBuffer nextSceneTexBuffer;
 
 	@Override
@@ -364,7 +368,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 					}
 				}
 
-				vertexBuffer = new GpuIntBuffer();
+				vertexBuffer = new GpuFloatBuffer();
 				uvBuffer = new GpuFloatBuffer();
 
 				modelBufferUnordered = new GpuIntBuffer();
@@ -663,11 +667,14 @@ 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.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 +682,14 @@ 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.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();
@@ -797,20 +807,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 	private void initUniformBuffer()
 	{
 		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);
-		GL43C.glBindBuffer(GL43C.GL_UNIFORM_BUFFER, 0);
+		updateBuffer(uniformBuffer, GL43C.GL_UNIFORM_BUFFER, 32, GL43C.GL_DYNAMIC_DRAW, CL12.CL_MEM_READ_ONLY);
 	}
 
 	private void initAAFbo(int width, int height, int aaSamples)
@@ -880,11 +877,8 @@ 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.
-		// We can reuse the vertex buffer since it isn't used yet.
-		vertexBuffer.clear();
-		vertexBuffer.ensureCapacity(32);
-		IntBuffer uniformBuf = vertexBuffer.getBuffer();
+		// UBO.
+		uniformBuf.clear();
 		uniformBuf
 			.put(Float.floatToIntBits((float) cameraYaw))
 			.put(Float.floatToIntBits((float) cameraPitch))
@@ -915,7 +909,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 			vertexBuffer.flip();
 			uvBuffer.flip();
 
-			IntBuffer vertexBuffer = this.vertexBuffer.getBuffer();
+			FloatBuffer vertexBuffer = this.vertexBuffer.getBuffer();
 			FloatBuffer uvBuffer = this.uvBuffer.getBuffer();
 
 			updateBuffer(tmpVertexBuffer, GL43C.GL_ARRAY_BUFFER, vertexBuffer, GL43C.GL_DYNAMIC_DRAW, 0L);
@@ -932,7 +926,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 		modelBufferSmall.flip();
 		modelBufferUnordered.flip();
 
-		IntBuffer vertexBuffer = this.vertexBuffer.getBuffer();
+		FloatBuffer vertexBuffer = this.vertexBuffer.getBuffer();
 		FloatBuffer uvBuffer = this.uvBuffer.getBuffer();
 		IntBuffer modelBuffer = this.modelBuffer.getBuffer();
 		IntBuffer modelBufferSmall = this.modelBufferSmall.getBuffer();
@@ -950,7 +944,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 		// Output buffers
 		updateBuffer(tmpOutBuffer,
 			GL43C.GL_ARRAY_BUFFER,
-			targetBufferOffset * 16, // each element is an ivec4, which is 16 bytes
+			targetBufferOffset * 16, // each element is a vec4, which is 16 bytes
 			GL43C.GL_STREAM_DRAW,
 			CL12.CL_MEM_WRITE_ONLY);
 		updateBuffer(tmpOutUvBuffer,
@@ -1074,8 +1068,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 		if (computeMode == ComputeMode.NONE)
 		{
 			targetBufferOffset += sceneUploader.upload(model,
-				tileX, tileY,
-				tileX << Perspective.LOCAL_COORD_BITS, tileY << Perspective.LOCAL_COORD_BITS,
+				0, 0,
 				vertexBuffer, uvBuffer,
 				true);
 		}
@@ -1280,7 +1273,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 			// Calculate projection matrix
 			float[] projectionMatrix = Mat4.scale(client.getScale(), client.getScale(), 1);
 			Mat4.mul(projectionMatrix, Mat4.projection(viewportWidth, viewportHeight, 50));
-			Mat4.mul(projectionMatrix, Mat4.rotateX((float) -(Math.PI - cameraPitch)));
+			Mat4.mul(projectionMatrix, Mat4.rotateX((float) cameraPitch));
 			Mat4.mul(projectionMatrix, Mat4.rotateY((float) cameraYaw));
 			Mat4.mul(projectionMatrix, Mat4.translate((float) -cameraX, (float) -cameraY, (float) -cameraZ));
 			GL43C.glUniformMatrix4fv(uniProjectionMatrix, false, projectionMatrix);
@@ -1516,7 +1509,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 			return;
 		}
 
-		GpuIntBuffer vertexBuffer = new GpuIntBuffer();
+		GpuFloatBuffer vertexBuffer = new GpuFloatBuffer();
 		GpuFloatBuffer uvBuffer = new GpuFloatBuffer();
 
 		sceneUploader.upload(scene, vertexBuffer, uvBuffer);
@@ -1779,7 +1772,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 			buffer.put(uvOffset);
 			buffer.put(tc);
 			buffer.put(targetBufferOffset);
-			buffer.put(FLAG_SCENE_BUFFER | (hillskew ? (1 << 26) : 0) | (plane << 24) | (model.getRadius() << 12) | orientation);
+			buffer.put(FLAG_SCENE_BUFFER | (hillskew ? (1 << 26) : 0) | (plane << 24) | orientation);
 			buffer.put(x + client.getCameraX2()).put(y + client.getCameraY2()).put(z + client.getCameraZ2());
 
 			targetBufferOffset += tc * 3;
@@ -1813,7 +1806,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 			buffer.put(hasUv ? tempUvOffset : -1);
 			buffer.put(len / 3);
 			buffer.put(targetBufferOffset);
-			buffer.put((model.getRadius() << 12) | orientation);
+			buffer.put(orientation);
 			buffer.put(x + client.getCameraX2()).put(y + client.getCameraY2()).put(z + client.getCameraZ2());
 
 			tempOffset += len;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/Mat4.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/Mat4.java
index 041107664..5b608aa77 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/Mat4.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/Mat4.java
@@ -97,9 +97,9 @@ public class Mat4
 		return new float[]
 			{
 				2 / w, 0, 0, 0,
-				0, 2 / h, 0, 0,
-				0, 0, -1, -1,
-				0, 0, -2 * n, 0
+				0, -2 / h, 0, 0,
+				0, 0, 0, 1,
+				0, 0, 2 * n, 0
 			};
 	}
 
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 f94573aec..99a1821f8 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
@@ -79,7 +79,7 @@ class SceneUploader
 		}
 	}
 
-	void upload(Scene scene, GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer)
+	void upload(Scene scene, GpuFloatBuffer vertexBuffer, GpuFloatBuffer uvBuffer)
 	{
 		++sceneId;
 		offset = 0;
@@ -113,7 +113,7 @@ class SceneUploader
 		log.debug("Scene upload time: {} unique models: {} length: {}KB", stopwatch, uniqueModels, (offset * 16) / 1024);
 	}
 
-	private void upload(Scene scene, Tile tile, GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer)
+	private void upload(Scene scene, Tile tile, GpuFloatBuffer vertexBuffer, GpuFloatBuffer uvBuffer)
 	{
 		Tile bridge = tile.getBridge();
 		if (bridge != null)
@@ -160,8 +160,7 @@ class SceneUploader
 			}
 			Point tilePoint = tile.getSceneLocation();
 			int len = upload(sceneTileModel,
-				tilePoint.getX(), tilePoint.getY(),
-				0, 0,
+				tilePoint.getX() << Perspective.LOCAL_COORD_BITS, tilePoint.getY() << Perspective.LOCAL_COORD_BITS,
 				vertexBuffer, uvBuffer, false);
 			sceneTileModel.setBufferLen(len);
 			offset += len;
@@ -229,25 +228,22 @@ class SceneUploader
 		}
 	}
 
-	int upload(Scene scene, SceneTilePaint tile, int tileZ, int tileX, int tileY, GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer,
-		int offsetX, int offsetY, boolean padUvs)
+	int upload(Scene scene, SceneTilePaint tile, int tileZ, int tileX, int tileY, GpuFloatBuffer vertexBuffer, GpuFloatBuffer uvBuffer,
+		int lx, int lz, boolean padUvs)
 	{
-		final int[][][] tileHeights = scene.getTileHeights();
-
-		final int localX = offsetX;
-		final int localY = offsetY;
-
 		tileX += GpuPlugin.SCENE_OFFSET;
 		tileY += GpuPlugin.SCENE_OFFSET;
-		int swHeight = tileHeights[tileZ][tileX][tileY];
-		int seHeight = tileHeights[tileZ][tileX + 1][tileY];
-		int neHeight = tileHeights[tileZ][tileX + 1][tileY + 1];
-		int nwHeight = tileHeights[tileZ][tileX][tileY + 1];
 
+		final int[][][] tileHeights = scene.getTileHeights();
+		final int swHeight = tileHeights[tileZ][tileX][tileY];
+		final int seHeight = tileHeights[tileZ][tileX + 1][tileY];
+		final int neHeight = tileHeights[tileZ][tileX + 1][tileY + 1];
+		final int nwHeight = tileHeights[tileZ][tileX][tileY + 1];
+
+		final int swColor = tile.getSwColor();
+		final int seColor = tile.getSeColor();
 		final int neColor = tile.getNeColor();
 		final int nwColor = tile.getNwColor();
-		final int seColor = tile.getSeColor();
-		final int swColor = tile.getSwColor();
 
 		if (neColor == 12345678)
 		{
@@ -258,54 +254,63 @@ class SceneUploader
 		uvBuffer.ensureCapacity(24);
 
 		// 0,0
-		int vertexDx = localX;
-		int vertexDy = localY;
-		int vertexDz = swHeight;
-		final int c1 = swColor;
+		final int lx0 = lx;
+		final int ly0 = swHeight;
+		final int lz0 = lz;
+		final int hsl0 = swColor;
 
 		// 1,0
-		int vertexCx = localX + Perspective.LOCAL_TILE_SIZE;
-		int vertexCy = localY;
-		int vertexCz = seHeight;
-		final int c2 = seColor;
+		final int lx1 = lx + Perspective.LOCAL_TILE_SIZE;
+		final int ly1 = seHeight;
+		final int lz1 = lz;
+		final int hsl1 = seColor;
 
 		// 1,1
-		int vertexAx = localX + Perspective.LOCAL_TILE_SIZE;
-		int vertexAy = localY + Perspective.LOCAL_TILE_SIZE;
-		int vertexAz = neHeight;
-		final int c3 = neColor;
+		final int lx2 = lx + Perspective.LOCAL_TILE_SIZE;
+		final int ly2 = neHeight;
+		final int lz2 = lz + Perspective.LOCAL_TILE_SIZE;
+		final int hsl2 = neColor;
 
 		// 0,1
-		int vertexBx = localX;
-		int vertexBy = localY + Perspective.LOCAL_TILE_SIZE;
-		int vertexBz = nwHeight;
-		final int c4 = nwColor;
+		final int lx3 = lx;
+		final int ly3 = nwHeight;
+		final int lz3 = lz + Perspective.LOCAL_TILE_SIZE;
+		final int hsl3 = nwColor;
 
-		vertexBuffer.put(vertexAx, vertexAz, vertexAy, c3);
-		vertexBuffer.put(vertexBx, vertexBz, vertexBy, c4);
-		vertexBuffer.put(vertexCx, vertexCz, vertexCy, c2);
+		vertexBuffer.put(lx2, ly2, lz2, Float.intBitsToFloat(0x00800000 | hsl2));
+		vertexBuffer.put(lx3, ly3, lz3, Float.intBitsToFloat(0x00800000 | hsl3));
+		vertexBuffer.put(lx1, ly1, lz1, Float.intBitsToFloat(0x00800000 | hsl1));
 
-		vertexBuffer.put(vertexDx, vertexDz, vertexDy, c1);
-		vertexBuffer.put(vertexCx, vertexCz, vertexCy, c2);
-		vertexBuffer.put(vertexBx, vertexBz, vertexBy, c4);
+		vertexBuffer.put(lx0, ly0, lz0, Float.intBitsToFloat(0x00800000 | hsl0));
+		vertexBuffer.put(lx1, ly1, lz1, Float.intBitsToFloat(0x00800000 | hsl1));
+		vertexBuffer.put(lx3, ly3, lz3, Float.intBitsToFloat(0x00800000 | hsl3));
 
 		if (padUvs || tile.getTexture() != -1)
 		{
 			int tex = tile.getTexture() + 1;
-			uvBuffer.put(tex, vertexDx, vertexDz, vertexDy);
-			uvBuffer.put(tex, vertexCx, vertexCz, vertexCy);
-			uvBuffer.put(tex, vertexBx, vertexBz, vertexBy);
+			if (tile.isFlat())
+			{
+				uvBuffer.put(tex, lx0, ly0, lz0);
+				uvBuffer.put(tex, lx1, ly1, lz1);
+				uvBuffer.put(tex, lx3, ly3, lz3);
+			}
+			else
+			{
+				uvBuffer.put(tex, lx2, ly2, lz2);
+				uvBuffer.put(tex, lx3, ly3, lz3);
+				uvBuffer.put(tex, lx1, ly1, lz1);
+			}
 
-			uvBuffer.put(tex, vertexDx, vertexDz, vertexDy);
-			uvBuffer.put(tex, vertexCx, vertexCz, vertexCy);
-			uvBuffer.put(tex, vertexBx, vertexBz, vertexBy);
+			uvBuffer.put(tex, lx0, ly0, lz0);
+			uvBuffer.put(tex, lx1, ly1, lz1);
+			uvBuffer.put(tex, lx3, ly3, lz3);
 		}
 
 		return 6;
 	}
 
-	int upload(SceneTileModel sceneTileModel, int tileX, int tileY, int offsetX, int offsetZ,
-			GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer, boolean padUvs)
+	int upload(SceneTileModel sceneTileModel, int lx, int lz,
+		GpuFloatBuffer vertexBuffer, GpuFloatBuffer uvBuffer, boolean padUvs)
 	{
 		final int[] faceX = sceneTileModel.getFaceX();
 		final int[] faceY = sceneTileModel.getFaceY();
@@ -326,21 +331,18 @@ class SceneUploader
 		vertexBuffer.ensureCapacity(faceCount * 12);
 		uvBuffer.ensureCapacity(faceCount * 12);
 
-		int baseX = tileX << Perspective.LOCAL_COORD_BITS;
-		int baseY = tileY << Perspective.LOCAL_COORD_BITS;
-
 		int cnt = 0;
 		for (int i = 0; i < faceCount; ++i)
 		{
-			final int triangleA = faceX[i];
-			final int triangleB = faceY[i];
-			final int triangleC = faceZ[i];
+			final int vertex0 = faceX[i];
+			final int vertex1 = faceY[i];
+			final int vertex2 = faceZ[i];
 
-			final int colorA = triangleColorA[i];
-			final int colorB = triangleColorB[i];
-			final int colorC = triangleColorC[i];
+			final int hsl0 = triangleColorA[i];
+			final int hsl1 = triangleColorB[i];
+			final int hsl2 = triangleColorC[i];
 
-			if (colorA == 12345678)
+			if (hsl0 == 12345678)
 			{
 				continue;
 			}
@@ -348,36 +350,45 @@ class SceneUploader
 			cnt += 3;
 
 			// vertexes are stored in scene local, convert to tile local
-			int vertexXA = vertexX[triangleA] - baseX;
-			int vertexYA = vertexY[triangleA];
-			int vertexZA = vertexZ[triangleA] - baseY;
+			int lx0 = vertexX[vertex0] - lx;
+			int ly0 = vertexY[vertex0];
+			int lz0 = vertexZ[vertex0] - lz;
 
-			int vertexXB = vertexX[triangleB] - baseX;
-			int vertexYB = vertexY[triangleB];
-			int vertexZB = vertexZ[triangleB] - baseY;
+			int lx1 = vertexX[vertex1] - lx;
+			int ly1 = vertexY[vertex1];
+			int lz1 = vertexZ[vertex1] - lz;
 
-			int vertexXC = vertexX[triangleC] - baseX;
-			int vertexYC = vertexY[triangleC];
-			int vertexZC = vertexZ[triangleC] - baseY;
+			int lx2 = vertexX[vertex2] - lx;
+			int ly2 = vertexY[vertex2];
+			int lz2 = vertexZ[vertex2] - lz;
 
-			vertexBuffer.put(vertexXA + offsetX, vertexYA, vertexZA + offsetZ, colorA);
-			vertexBuffer.put(vertexXB + offsetX, vertexYB, vertexZB + offsetZ, colorB);
-			vertexBuffer.put(vertexXC + offsetX, vertexYC, vertexZC + offsetZ, colorC);
+			vertexBuffer.put(lx0, ly0, lz0, Float.intBitsToFloat(0x00800000 | hsl0));
+			vertexBuffer.put(lx1, ly1, lz1, Float.intBitsToFloat(0x00800000 | hsl1));
+			vertexBuffer.put(lx2, ly2, lz2, Float.intBitsToFloat(0x00800000 | hsl2));
 
 			if (padUvs || triangleTextures != null)
 			{
 				if (triangleTextures != null && triangleTextures[i] != -1)
 				{
 					int tex = triangleTextures[i] + 1;
-					uvBuffer.put(tex, offsetX, vertexYA, offsetZ);
-					uvBuffer.put(tex, offsetX + 128, vertexYB, offsetZ);
-					uvBuffer.put(tex, offsetX, vertexYC, offsetZ + 128);
+					if (sceneTileModel.isFlat())
+					{
+						uvBuffer.put(tex, vertexX[0] - lx, vertexY[0], vertexZ[0] - lz);
+						uvBuffer.put(tex, vertexX[1] - lx, vertexY[1], vertexZ[1] - lz);
+						uvBuffer.put(tex, vertexX[3] - lx, vertexY[3], vertexZ[3] - lz);
+					}
+					else
+					{
+						uvBuffer.put(tex, vertexX[vertex0] - lx, vertexY[vertex0], vertexZ[vertex0] - lz);
+						uvBuffer.put(tex, vertexX[vertex1] - lx, vertexY[vertex1], vertexZ[vertex1] - lz);
+						uvBuffer.put(tex, vertexX[vertex2] - lx, vertexY[vertex2], vertexZ[vertex2] - lz);
+					}
 				}
 				else
 				{
-					uvBuffer.put(0, 0, 0, 0f);
-					uvBuffer.put(0, 0, 0, 0f);
-					uvBuffer.put(0, 0, 0, 0f);
+					uvBuffer.put(0, 0, 0, 0);
+					uvBuffer.put(0, 0, 0, 0);
+					uvBuffer.put(0, 0, 0, 0);
 				}
 			}
 		}
@@ -385,7 +396,7 @@ class SceneUploader
 		return cnt;
 	}
 
-	private void uploadSceneModel(Model model, GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer)
+	private void uploadSceneModel(Model model, GpuFloatBuffer vertexBuffer, GpuFloatBuffer uvBuffer)
 	{
 		// deduplicate hillskewed models
 		if (model.getUnskewedModel() != null)
@@ -419,7 +430,7 @@ class SceneUploader
 		}
 	}
 
-	public int pushModel(Model model, GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer)
+	public int pushModel(Model model, GpuFloatBuffer vertexBuffer, GpuFloatBuffer uvBuffer)
 	{
 		final int triangleCount = Math.min(model.getFaceCount(), GpuPlugin.MAX_TRIANGLE);
 
@@ -497,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(vertexX[triangleA], vertexY[triangleA], vertexZ[triangleA], Float.intBitsToFloat(packAlphaPriority | color1));
+			vertexBuffer.put(vertexX[triangleB], vertexY[triangleB], vertexZ[triangleB], Float.intBitsToFloat(packAlphaPriority | color2));
+			vertexBuffer.put(vertexX[triangleC], vertexY[triangleC], vertexZ[triangleC], Float.intBitsToFloat(packAlphaPriority | color3));
 
 			if (faceTextures != null)
 			{
@@ -600,7 +611,7 @@ class SceneUploader
 		orderedFaces = null;
 	}
 
-	int pushSortedModel(Model model, int orientation, int pitchSin, int pitchCos, int yawSin, int yawCos, int x, int y, int z, GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer)
+	int pushSortedModel(Model model, int orientation, int pitchSin, int pitchCos, int yawSin, int yawCos, int x, int y, int z, GpuFloatBuffer vertexBuffer, GpuFloatBuffer uvBuffer)
 	{
 		final int vertexCount = model.getVerticesCount();
 		final int[] verticesX = model.getVerticesX();
@@ -921,7 +932,7 @@ class SceneUploader
 		return len;
 	}
 
-	private int pushFace(Model model, int face, GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer)
+	private int pushFace(Model model, int face, GpuFloatBuffer vertexBuffer, GpuFloatBuffer uvBuffer)
 	{
 		final int[] indices1 = model.getFaceIndices1();
 		final int[] indices2 = model.getFaceIndices2();
@@ -971,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(modelLocalX[triangleA], modelLocalY[triangleA], modelLocalZ[triangleA], Float.intBitsToFloat(packAlphaPriority | color1));
+		vertexBuffer.put(modelLocalX[triangleB], modelLocalY[triangleB], modelLocalZ[triangleB], Float.intBitsToFloat(packAlphaPriority | color2));
+		vertexBuffer.put(modelLocalX[triangleC], modelLocalY[triangleC], modelLocalZ[triangleC], Float.intBitsToFloat(packAlphaPriority | color3));
 
 		if (faceTextures != null && faceTextures[face] != -1)
 		{
@@ -1020,7 +1031,7 @@ class SceneUploader
 		{
 			priority = (facePriorities[face] & 0xff) << 16;
 		}
-		return alpha | priority;
+		return alpha | priority | 0x00800000;
 	}
 
 	private static int interpolateHSL(int hsl, byte hue2, byte sat2, byte lum2, byte lerp)
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 155a8a7bc..0c997d389 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
@@ -72,6 +72,7 @@ public class GroundItemsOverlay extends Overlay
 	private static final Color PUBLIC_TIMER_COLOR = Color.YELLOW;
 	private static final Color PRIVATE_TIMER_COLOR = Color.GREEN;
 	private static final int TIMER_OVERLAY_DIAMETER = 10;
+	private static final Duration DESPAWN_TIME_INFERNO = Duration.ofMinutes(180);
 	private static final Duration DESPAWN_TIME_INSTANCE = Duration.ofMinutes(30);
 	private static final Duration DESPAWN_TIME_LOOT = Duration.ofMinutes(2);
 	private static final Duration DESPAWN_TIME_DROP = Duration.ofMinutes(3);
@@ -87,6 +88,7 @@ public class GroundItemsOverlay extends Overlay
 	private static final int NIGHTMARE_REGION = 15515;
 	private static final int TEMPOROSS_REGION = 12078;
 	private static final int KALPHITE_QUEEN_REGION = 13972;
+	private static final int INFERNO_REGION = 9043;
 
 	private final Client client;
 	private final GroundItemsPlugin plugin;
@@ -446,6 +448,10 @@ public class GroundItemsOverlay extends Overlay
 					}
 				}
 			}
+			else if (playerRegionID == INFERNO_REGION)
+			{
+				despawnTime = spawnTime.plus(DESPAWN_TIME_INFERNO);
+			}
 			else if (playerRegionID == ZILYANA_REGION || playerRegionID == GRAARDOR_REGION ||
 				playerRegionID == KRIL_TSUTSAROTH_REGION || playerRegionID == KREEARRA_REGION ||
 				playerRegionID == NEX_REGION || playerRegionID == KALPHITE_QUEEN_REGION ||
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/music/MusicPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/music/MusicPlugin.java
index baf923292..3e64d2833 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/music/MusicPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/music/MusicPlugin.java
@@ -192,10 +192,19 @@ public class MusicPlugin extends Plugin
 			channels = new Channel[]{musicChannel, effectChannel, areaChannel};
 
 			addMusicButtons();
-			if (client.getGameState() == GameState.LOGGED_IN && musicConfig.granularSliders())
+			if (client.getGameState() == GameState.LOGGED_IN)
 			{
-				updateMusicOptions();
-				resetSettingsWindow();
+				if (musicConfig.granularSliders())
+				{
+					updateMusicOptions();
+					resetSettingsWindow();
+				}
+
+				if (musicConfig.muteAmbientSounds())
+				{
+					// Reload the scene to remove ambient sounds
+					client.setGameState(GameState.LOADING);
+				}
 			}
 		});
 	}
@@ -214,6 +223,15 @@ public class MusicPlugin extends Plugin
 		{
 			shuttingDown = true;
 			teardownMusicOptions();
+
+			if (musicConfig.muteAmbientSounds())
+			{
+				// Reload the scene to reapply ambient sounds
+				if (client.getGameState() == GameState.LOGGED_IN)
+				{
+					client.setGameState(GameState.LOADING);
+				}
+			}
 		});
 	}
 
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java
index 835de8eb4..e3644fe22 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java
@@ -248,15 +248,18 @@ public class RaidsPlugin extends Plugin
 		// if the player's party state has changed
 		if (event.getVarpId() == VarPlayer.IN_RAID_PARTY)
 		{
-			boolean inRaid = client.getVarbitValue(Varbits.IN_RAID) == 1;
+			boolean inRaid = inRaidChambers;
+			int prevRaidID = raidPartyID;
 			raidPartyID = event.getValue();
 
-			// Party dissolution
-			if (client.getGameState() == GameState.LOGGED_IN
-				&& !inRaid)
+			if (client.getGameState() == GameState.LOGGED_IN)
 			{
-				log.debug("Raid party has been dissolved");
-				reset();
+				// Party dissolution
+				if (!inRaid || (prevRaidID != -1 && raidPartyID != -1 && prevRaidID != raidPartyID))
+				{
+					log.debug("Raid party has been dissolved");
+					reset();
+				}
 			}
 		}
 
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 658ee06f4..1a8b8dd40 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 {
@@ -40,7 +39,7 @@ struct shared_data {
   int totalDistance[12];   // sum of distances to faces of a given priority
   int totalMappedNum[18];  // number of faces with a given adjusted priority
   int min10;               // minimum distance to a face of priority 10
-  uint renderPris[0];      // packed distance and face id
+  int renderPris[0];       // priority for face draw order
 };
 
 struct modelinfo {
@@ -48,7 +47,7 @@ struct modelinfo {
   int toffset;  // offset into texture buffer
   int size;     // length in faces
   int idx;      // write idx in target buffer
-  int flags;    // buffer, hillskew, plane, radius, orientation
+  int flags;    // buffer, hillskew, plane, orientation
   int x;        // scene position x
   int y;        // scene position y
   int z;        // scene position z
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 8efd963bf..9bbed7583 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,61 +49,45 @@ 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);
-}
-
-float3 rotatef_vertex(float3 vertex, int orientation) {
+float4 rotate_vertex(float4 vertex, int orientation) {
   float rad = orientation * UNIT;
   float s = sin(rad);
   float c = cos(rad);
   float x = vertex.z * s + vertex.x * c;
   float z = vertex.z * c - vertex.x * s;
-  return (float3)(x, vertex.y, z);
+  return (float4)(x, vertex.y, z, vertex.w);
 }
 
 /*
  * 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));
-
-  int pitchSin = (int)(65536.0f * sin(cameraPitch));
-  int pitchCos = (int)(65536.0f * cos(cameraPitch));
-
-  int j = vertex.z * yawCos - vertex.x * yawSin >> 16;
-  int l = vertex.y * pitchSin + j * pitchCos >> 16;
-
-  return l;
+float vertex_distance(float4 vertex, float cameraYaw, float cameraPitch) {
+  float rotxz = vertex.z * cos(cameraYaw) - vertex.x * sin(cameraYaw);
+  float rotyz = rotxz * cos(cameraPitch) + vertex.y * sin(cameraPitch);
+  return rotyz;
 }
 
 /*
  * 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;
+int 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, float4 vA, float4 vB, float4 vC, int4 position) {
   // Move model to scene location, and account for camera offset
   float4 cameraPos = (float4)(uni->cameraX, uni->cameraY, uni->cameraZ, 0);
 
-  float4 lA = convert_float4(vA + position) - cameraPos;
-  float4 lB = convert_float4(vB + position) - cameraPos;
-  float4 lC = convert_float4(vC + position) - cameraPos;
+  float4 lA = vA + convert_float4(position) - cameraPos;
+  float4 lB = vB + convert_float4(position) - cameraPos;
+  float4 lC = vC + convert_float4(position) - 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);
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 240a33b28..1ce860430 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,54 +28,38 @@
 /*
  * 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);
-}
-
-vec3 rotatef(vec3 vertex, int orientation) {
+vec4 rotate(vec4 vertex, int orientation) {
   float rad = orientation * UNIT;
   float s = sin(rad);
   float c = cos(rad);
-  mat3 m = mat3(c, 0, s, 0, 1, 0, -s, 0, c);
+  mat4 m = mat4(c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1);
   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));
-
-  int pitchSin = int(65536.0f * sin(cameraPitch));
-  int pitchCos = int(65536.0f * cos(cameraPitch));
-
-  int j = vertex.z * yawCos - vertex.x * yawSin >> 16;
-  int l = vertex.y * pitchSin + j * pitchCos >> 16;
-
-  return l;
+float distance(vec4 vertex, float cameraYaw, float cameraPitch) {
+  float rotxz = vertex.z * cos(cameraYaw) - vertex.x * sin(cameraYaw);
+  float rotyz = rotxz * cos(cameraPitch) + vertex.y * sin(cameraPitch);
+  return rotyz;
 }
 
 /*
  * 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(vec4 vA, vec4 vB, vec4 vC, ivec4 position) {
   // Move model to scene location, and account for camera offset
   vec4 cameraPos = vec4(cameraX, cameraY, cameraZ, 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..d057871b9 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,13 +30,13 @@
 #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 float4 *vb, __global const float4 *tempvb,
+                                                                            __global const float4 *texb, __global const float4 *temptexb, __global float4 *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];
-  int4 pos = (int4)(minfo.x, minfo.y, minfo.z, 0);
 
   if (localId == 0) {
     shared->min10 = 6000;
@@ -51,9 +51,9 @@ __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];
+  float4 v1[FACE_COUNT];
+  float4 v2[FACE_COUNT];
+  float4 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]);
@@ -62,7 +62,7 @@ __kernel __attribute__((work_group_size_hint(256, 1, 1))) void computeLarge(__lo
   barrier(CLK_LOCAL_MEM_FENCE);
 
   for (int i = 0; i < FACE_COUNT; i++) {
-    add_face_prio_distance(shared, uni, localId + i, minfo, v1[i], v2[i], v3[i], prio[i], dis[i], pos);
+    add_face_prio_distance(shared, uni, localId + i, minfo, v1[i], v2[i], v3[i], prio[i], dis[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 624d66211..cc43bb20a 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
@@ -32,8 +32,8 @@ shared int totalDistance[12];  // sum of distances to faces of a given priority
 
 shared int totalMappedNum[18];  // number of faces with a given adjusted priority
 
-shared int min10;                                         // minimum distance to a face of priority 10
-shared uint renderPris[THREAD_COUNT * FACES_PER_THREAD];  // packed distance and face id
+shared int min10;                                        // minimum distance to a face of priority 10
+shared int renderPris[THREAD_COUNT * FACES_PER_THREAD];  // priority for face draw order
 
 #include "comp_common.glsl"
 
@@ -46,7 +46,6 @@ void main() {
   uint groupId = gl_WorkGroupID.x;
   uint localId = gl_LocalInvocationID.x * FACES_PER_THREAD;
   modelinfo minfo = ol[groupId];
-  ivec4 pos = ivec4(minfo.x, minfo.y, minfo.z, 0);
 
   if (localId == 0) {
     min10 = 6000;
@@ -61,9 +60,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];
+  vec4 vA[FACES_PER_THREAD];
+  vec4 vB[FACES_PER_THREAD];
+  vec4 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]);
@@ -73,7 +72,7 @@ void main() {
   barrier();
 
   for (int i = 0; i < FACES_PER_THREAD; i++) {
-    add_face_prio_distance(localId + i, minfo, vA[i], vB[i], vC[i], prio[i], dis[i], pos);
+    add_face_prio_distance(localId + i, minfo, vA[i], vB[i], vC[i], prio[i], dis[i]);
   }
 
   memoryBarrierShared();
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 261377131..3f2fa31b2 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 {
@@ -43,7 +42,7 @@ struct modelinfo {
   int toffset;  // offset into texture buffer
   int size;     // length in faces
   int idx;      // write idx in target buffer
-  int flags;    // buffer, hillskew, plane, radius, orientation
+  int flags;    // buffer, hillskew, plane, orientation
   int x;        // scene position x
   int y;        // scene position y
   int z;        // scene position z
@@ -54,15 +53,15 @@ layout(std430, binding = 0) readonly buffer modelbuffer_in {
 };
 
 layout(std430, binding = 1) readonly buffer vertexbuffer_in {
-  ivec4 vb[];
+  vec4 vb[];
 };
 
 layout(std430, binding = 2) readonly buffer tempvertexbuffer_in {
-  ivec4 tempvb[];
+  vec4 tempvb[];
 };
 
 layout(std430, binding = 3) writeonly buffer vertex_out {
-  ivec4 vout[];
+  vec4 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..cc5edb8e6 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,9 @@
 
 #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 float4 *vb,
+                                                                              __global const float4 *tempvb, __global const float4 *texb,
+                                                                              __global const float4 *temptexb, __global float4 *vout, __global float4 *uvout) {
   size_t groupId = get_group_id(0);
   size_t localId = get_local_id(0);
   struct modelinfo minfo = ol[groupId];
@@ -44,27 +44,27 @@ __kernel __attribute__((reqd_work_group_size(6, 1, 1))) void computeUnordered(__
   }
 
   uint ssboOffset = localId;
-  int4 thisA, thisB, thisC;
+  float4 vertA, vertB, vertC;
 
   // Grab triangle vertices from the correct buffer
   if (flags < 0) {
-    thisA = vb[offset + ssboOffset * 3];
-    thisB = vb[offset + ssboOffset * 3 + 1];
-    thisC = vb[offset + ssboOffset * 3 + 2];
+    vertA = vb[offset + ssboOffset * 3];
+    vertB = vb[offset + ssboOffset * 3 + 1];
+    vertC = vb[offset + ssboOffset * 3 + 2];
   } else {
-    thisA = tempvb[offset + ssboOffset * 3];
-    thisB = tempvb[offset + ssboOffset * 3 + 1];
-    thisC = tempvb[offset + ssboOffset * 3 + 2];
+    vertA = tempvb[offset + ssboOffset * 3];
+    vertB = tempvb[offset + ssboOffset * 3 + 1];
+    vertC = tempvb[offset + ssboOffset * 3 + 2];
   }
 
   uint myOffset = localId;
-  int4 pos = (int4)(minfo.x, minfo.y, minfo.z, 0);
-  float4 texPos = convert_float4(pos.wxyz);
+  float4 pos = (float4)(minfo.x, minfo.y, minfo.z, 0);
+  float4 texPos = pos.wxyz;
 
   // 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] = pos + vertA;
+  vout[outOffset + myOffset * 3 + 1] = pos + vertB;
+  vout[outOffset + myOffset * 3 + 2] = pos + vertC;
 
   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..4c0e80cf7 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,27 +45,27 @@ void main() {
   }
 
   uint ssboOffset = localId;
-  ivec4 thisA, thisB, thisC;
+  vec4 vertA, vertB, vertC;
 
   // Grab triangle vertices from the correct buffer
   if (flags < 0) {
-    thisA = vb[offset + ssboOffset * 3];
-    thisB = vb[offset + ssboOffset * 3 + 1];
-    thisC = vb[offset + ssboOffset * 3 + 2];
+    vertA = vb[offset + ssboOffset * 3];
+    vertB = vb[offset + ssboOffset * 3 + 1];
+    vertC = vb[offset + ssboOffset * 3 + 2];
   } else {
-    thisA = tempvb[offset + ssboOffset * 3];
-    thisB = tempvb[offset + ssboOffset * 3 + 1];
-    thisC = tempvb[offset + ssboOffset * 3 + 2];
+    vertA = tempvb[offset + ssboOffset * 3];
+    vertB = tempvb[offset + ssboOffset * 3 + 1];
+    vertC = tempvb[offset + ssboOffset * 3 + 2];
   }
 
   uint myOffset = localId;
-  ivec4 pos = ivec4(minfo.x, minfo.y, minfo.z, 0);
-  ivec4 texPos = pos.wxyz;
+  vec4 pos = vec4(minfo.x, minfo.y, minfo.z, 0);
+  vec4 texPos = pos.wxyz;
 
   // 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] = pos + vertA;
+  vout[outOffset + myOffset * 3 + 1] = pos + vertB;
+  vout[outOffset + myOffset * 3 + 2] = pos + vertC;
 
   if (toffset < 0) {
     uvout[outOffset + myOffset * 3] = vec4(0);
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/frag.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/frag.glsl
index 8a2e71d37..91132038b 100644
--- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/frag.glsl
+++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/frag.glsl
@@ -25,6 +25,7 @@
 #version 330
 
 //#define FRAG_UVS
+//#define ZBUF
 
 uniform sampler2DArray textures;
 uniform float brightness;
@@ -38,12 +39,23 @@ noperspective centroid in float fHsl;
 flat in int fTextureId;
 in vec2 fUv;
 in float fFogAmount;
+#ifdef ZBUF
+in float fDepth;
+#endif
 
 out vec4 FragColor;
 
 #include "hsl_to_rgb.glsl"
 #include "colorblind.glsl"
 
+#ifdef ZBUF
+float linear_depth(float depth) {
+  // depth is computed as 100/z, solve for z
+  float z = 100 / depth;
+  return 1 - z / 10000;  // we don't have a far plane, but the client uses 10000
+}
+#endif
+
 void main() {
   vec4 c;
 
@@ -68,13 +80,24 @@ void main() {
   }
 
   vec3 mixedColor = mix(c.rgb, fogColor.rgb, fFogAmount);
+  FragColor = vec4(mixedColor, c.a);
+
 #ifdef FRAG_UVS
   if (fTextureId > 0) {
     FragColor = vec4(fUv.x, 0, fUv.y, 1);
-  } else {
+  }
 #endif
-    FragColor = vec4(mixedColor, c.a);
-#ifdef FRAG_UVS
+
+#ifdef ZBUF
+  float dc = linear_depth(fDepth);
+  if (dc > 1.0) {
+    FragColor = vec4(1, 0, 0, 1);
+  } else if (dc < -1.0) {
+    FragColor = vec4(0, 0, 1, 1);
+  } else if (dc < 0.0) {
+    FragColor = vec4(0, 1, 0, 1);
+  } else {
+    FragColor = vec4(dc, dc, dc, 1);
   }
 #endif
 }
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 8463e1880..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
@@ -25,6 +25,8 @@
 
 #version 330
 
+//#define ZBUF
+
 // smallest unit of the texture which can be moved per tick. textures are all
 // 128x128px - so this is equivalent to +1px
 #define TEXTURE_ANIM_UNIT (1.0f / 128.0f)
@@ -41,7 +43,6 @@ layout(std140) uniform uniforms {
   float cameraX;
   float cameraY;
   float cameraZ;
-  ivec2 sinCosTable[2048];
 };
 
 #include "uv.glsl"
@@ -50,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];
@@ -62,6 +63,9 @@ noperspective centroid out float fHsl;
 flat out int fTextureId;
 out vec2 fUv;
 out float fFogAmount;
+#ifdef ZBUF
+out float fDepth;
+#endif
 
 void main() {
   int textureId = gTextureId[0];
@@ -87,7 +91,13 @@ void main() {
     fTextureId = gTextureId[i];
     fUv = uv[i];
     fFogAmount = gFogAmount[i];
-    gl_Position = projectionMatrix * vec4(gVertex[i], 1);
+
+    vec4 pos = projectionMatrix * vec4(gVertex[i], 1);
+#ifdef ZBUF
+    fDepth = pos.z / pos.w;
+#endif
+    gl_Position = pos;
+
     EmitVertex();
   }
 
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 4329618d2..3d6dec0ee 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,
+void get_face(__local struct shared_data *shared, __constant struct uniform *uni, __global const float4 *vb, __global const float4 *tempvb, uint localId,
               struct modelinfo minfo, float cameraYaw, float cameraPitch,
-              /* out */ int *prio, int *dis, int4 *o1, int4 *o2, int4 *o3) {
+              /* out */ int *prio, int *dis, float4 *o1, float4 *o2, float4 *o3) {
   int size = minfo.size;
   int offset = minfo.offset;
   int flags = minfo.flags;
@@ -103,59 +103,53 @@ void get_face(__local struct shared_data *shared, __constant struct uniform *uni
     ssboOffset = 0;
   }
 
-  int4 thisA;
-  int4 thisB;
-  int4 thisC;
+  float4 vertA, vertB, vertC;
 
   // Grab triangle vertices from the correct buffer
   if (flags < 0) {
-    thisA = vb[offset + ssboOffset * 3];
-    thisB = vb[offset + ssboOffset * 3 + 1];
-    thisC = vb[offset + ssboOffset * 3 + 2];
+    vertA = vb[offset + ssboOffset * 3];
+    vertB = vb[offset + ssboOffset * 3 + 1];
+    vertC = vb[offset + ssboOffset * 3 + 2];
   } else {
-    thisA = tempvb[offset + ssboOffset * 3];
-    thisB = tempvb[offset + ssboOffset * 3 + 1];
-    thisC = tempvb[offset + ssboOffset * 3 + 2];
+    vertA = tempvb[offset + ssboOffset * 3];
+    vertB = tempvb[offset + ssboOffset * 3 + 1];
+    vertC = tempvb[offset + ssboOffset * 3 + 2];
   }
 
   if (localId < size) {
-    int radius = (flags >> 12) & 0xfff;
     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 rvertA = rotate_vertex(vertA, orientation);
+    float4 rvertB = rotate_vertex(vertB, orientation);
+    float4 rvertC = rotate_vertex(vertC, orientation);
 
     // calculate distance to face
-    int thisPriority = (thisA.w >> 16) & 0xff;  // all vertices on the face have the same priority
-    int thisDistance;
-    if (radius == 0) {
-      thisDistance = 0;
-    } else {
-      thisDistance = face_distance(thisrvA, thisrvB, thisrvC, cameraYaw, cameraPitch) + radius;
-    }
+    float w = vertA.w;
+    int thisPriority = ((*(int *)&w) >> 16) & 0xf;  // all vertices on the face have the same priority
+    int thisDistance = face_distance(rvertA, rvertB, rvertC, cameraYaw, cameraPitch);
 
-    *o1 = thisrvA;
-    *o2 = thisrvB;
-    *o3 = thisrvC;
+    *o1 = rvertA;
+    *o2 = rvertB;
+    *o3 = rvertC;
 
     *prio = thisPriority;
     *dis = thisDistance;
   } else {
-    *o1 = (int4)(0, 0, 0, 0);
-    *o2 = (int4)(0, 0, 0, 0);
-    *o3 = (int4)(0, 0, 0, 0);
+    *o1 = (float4)(0, 0, 0, 0);
+    *o2 = (float4)(0, 0, 0, 0);
+    *o3 = (float4)(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, float4 rvertA,
+                            float4 rvertB, float4 rvertC, int thisPriority, int thisDistance) {
   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)) {
+    int4 pos = (int4)(minfo.x, minfo.y, minfo.z, 0);
+    if (face_visible(uni, rvertA, rvertB, rvertC, pos)) {
       atomic_add(&shared->totalNum[thisPriority], 1);
       atomic_add(&shared->totalDistance[thisPriority], thisDistance);
 
@@ -206,38 +200,39 @@ void insert_face(__local struct shared_data *shared, uint localId, struct modeli
   if (localId < size) {
     // calculate base offset into renderPris based on number of faces with a lower priority
     int baseOff = count_prio_offset(shared, adjPrio);
-    // the furthest faces draw first, and have the highest value
+    // the furthest faces draw first, and have the highest priority.
     // if two faces have the same distance, the one with the
-    // lower id draws first
-    shared->renderPris[baseOff + prioIdx] = ((uint)(distance << 16)) | (~localId & 0xffffu);
+    // lower id draws first.
+    shared->renderPris[baseOff + prioIdx] = distance << 16 | (int)(~localId & 0xffffu);
   }
 }
 
 int tile_height(read_only image3d_t tileHeightImage, int z, int x, int y) {
-#define ESCENE_OFFSET 40 // (184-104)/2
+#define ESCENE_OFFSET 40  // (184-104)/2
   const sampler_t tileHeightSampler = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP_TO_EDGE;
   int4 coord = (int4)(x + ESCENE_OFFSET, y + ESCENE_OFFSET, z, 0);
   return read_imagei(tileHeightImage, tileHeightSampler, coord).x << 3;
 }
 
-int4 hillskew_vertex(read_only image3d_t tileHeightImage, int4 v, int hillskew, int y, int plane) {
+float4 hillskew_vertex(read_only image3d_t tileHeightImage, float4 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);
+    float fx = v.x / 128;
+    float fz = v.z / 128;
+    int sx = (int)(floor(fx));
+    int sz = (int)(floor(fz));
+    float it;
+    float h1 = mix(tile_height(tileHeightImage, plane, sx, sz), tile_height(tileHeightImage, plane, sx + 1, sz), fract(fx, &it));
+    float h2 = mix(tile_height(tileHeightImage, plane, sx, sz + 1), tile_height(tileHeightImage, plane, sx + 1, sz + 1), fract(fx, &it));
+    float h3 = mix(h1, h2, fract(fz, &it));
+    return (float4)(v.x, v.y + (int)(h3)-y, v.z, v.w);
   } else {
     return v;
   }
 }
 
 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 float4 *vout, __global float4 *uvout, uint localId, struct modelinfo minfo, int thisPriority, int thisDistance, float4 rvertA,
+                     float4 rvertB, float4 rvertC, read_only image3d_t tileHeightImage) {
   int size = minfo.size;
 
   if (localId < size) {
@@ -250,7 +245,7 @@ void sort_and_insert(__local struct shared_data *shared, __constant struct unifo
     const int numOfPriority = shared->totalMappedNum[thisPriority];
     const int start = priorityOffset;                // index of first face with this priority
     const int end = priorityOffset + numOfPriority;  // index of last face with this priority
-    const uint renderPriority = ((uint)(thisDistance << 16)) | (~localId & 0xffffu);
+    const int renderPriority = thisDistance << 16 | (int)(~localId & 0xffffu);
     int myOffset = priorityOffset;
 
     // calculate position this face will be in
@@ -261,22 +256,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);
+    rvertA += pos;
+    rvertB += pos;
+    rvertC += 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);
+    rvertA = hillskew_vertex(tileHeightImage, rvertA, hillskew, minfo.y, plane);
+    rvertB = hillskew_vertex(tileHeightImage, rvertB, hillskew, minfo.y, plane);
+    rvertC = hillskew_vertex(tileHeightImage, rvertC, 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] = rvertA;
+    vout[outOffset + myOffset * 3 + 1] = rvertB;
+    vout[outOffset + myOffset * 3 + 2] = rvertC;
 
     if (toffset < 0) {
       uvout[outOffset + myOffset * 3] = (float4)(0, 0, 0, 0);
@@ -296,9 +291,9 @@ void sort_and_insert(__local struct shared_data *shared, __constant struct unifo
       }
 
       int orientation = flags & 0x7ff;
-      uvout[outOffset + myOffset * 3] = (float4)(texA.x, rotatef_vertex(texA.yzw, orientation) + convert_float3(pos.xyz));
-      uvout[outOffset + myOffset * 3 + 1] = (float4)(texB.x, rotatef_vertex(texB.yzw, orientation) + convert_float3(pos.xyz));
-      uvout[outOffset + myOffset * 3 + 2] = (float4)(texC.x, rotatef_vertex(texC.yzw, orientation) + convert_float3(pos.xyz));
+      uvout[outOffset + myOffset * 3] = (float4)(texA.x, rotate_vertex((float4)(texA.yzw, 0), orientation).xyz + pos.xyz);
+      uvout[outOffset + myOffset * 3 + 1] = (float4)(texB.x, rotate_vertex((float4)(texB.yzw, 0), orientation).xyz + pos.xyz);
+      uvout[outOffset + myOffset * 3 + 2] = (float4)(texC.x, rotate_vertex((float4)(texC.yzw, 0), orientation).xyz + pos.xyz);
     }
   }
 }
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 d68a1ef9f..4ab8dc082 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 vec4 o1, out vec4 o2, out vec4 o3) {
   int size = minfo.size;
   int offset = minfo.offset;
   int flags = minfo.flags;
@@ -103,58 +103,51 @@ void get_face(uint localId, modelinfo minfo, float cameraYaw, float cameraPitch,
     ssboOffset = 0;
   }
 
-  ivec4 thisA;
-  ivec4 thisB;
-  ivec4 thisC;
+  vec4 vertA, vertB, vertC;
 
   // Grab triangle vertices from the correct buffer
   if (flags < 0) {
-    thisA = vb[offset + ssboOffset * 3];
-    thisB = vb[offset + ssboOffset * 3 + 1];
-    thisC = vb[offset + ssboOffset * 3 + 2];
+    vertA = vb[offset + ssboOffset * 3];
+    vertB = vb[offset + ssboOffset * 3 + 1];
+    vertC = vb[offset + ssboOffset * 3 + 2];
   } else {
-    thisA = tempvb[offset + ssboOffset * 3];
-    thisB = tempvb[offset + ssboOffset * 3 + 1];
-    thisC = tempvb[offset + ssboOffset * 3 + 2];
+    vertA = tempvb[offset + ssboOffset * 3];
+    vertB = tempvb[offset + ssboOffset * 3 + 1];
+    vertC = tempvb[offset + ssboOffset * 3 + 2];
   }
 
   if (localId < size) {
-    int radius = (flags >> 12) & 0xfff;
     int orientation = flags & 0x7ff;
 
     // rotate for model orientation
-    ivec4 thisrvA = rotate(thisA, orientation);
-    ivec4 thisrvB = rotate(thisB, orientation);
-    ivec4 thisrvC = rotate(thisC, orientation);
+    vec4 rvertA = rotate(vertA, orientation);
+    vec4 rvertB = rotate(vertB, orientation);
+    vec4 rvertC = rotate(vertC, orientation);
 
     // calculate distance to face
-    int thisPriority = (thisA.w >> 16) & 0xff;  // all vertices on the face have the same priority
-    int thisDistance;
-    if (radius == 0) {
-      thisDistance = 0;
-    } else {
-      thisDistance = face_distance(thisrvA, thisrvB, thisrvC, cameraYaw, cameraPitch) + radius;
-    }
+    int thisPriority = (floatBitsToInt(vertA.w) >> 16) & 0xf;  // all vertices on the face have the same priority
+    int thisDistance = face_distance(rvertA, rvertB, rvertC, cameraYaw, cameraPitch);
 
-    o1 = thisrvA;
-    o2 = thisrvB;
-    o3 = thisrvC;
+    o1 = rvertA;
+    o2 = rvertB;
+    o3 = rvertC;
 
     prio = thisPriority;
     dis = thisDistance;
   } else {
-    o1 = ivec4(0);
-    o2 = ivec4(0);
-    o3 = ivec4(0);
+    o1 = vec4(0);
+    o2 = vec4(0);
+    o3 = vec4(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, vec4 rvertA, vec4 rvertB, vec4 rvertC, int thisPriority, int thisDistance) {
   if (localId < minfo.size) {
     // if the face is not culled, it is calculated into priority distance averages
-    if (face_visible(thisrvA, thisrvB, thisrvC, pos)) {
+    ivec4 pos = ivec4(minfo.x, minfo.y, minfo.z, 0);
+    if (face_visible(rvertA, rvertB, rvertC, pos)) {
       atomicAdd(totalNum[thisPriority], 1);
       atomicAdd(totalDistance[thisPriority], thisDistance);
 
@@ -205,34 +198,34 @@ void insert_face(uint localId, modelinfo minfo, int adjPrio, int distance, int p
   if (localId < size) {
     // calculate base offset into renderPris based on number of faces with a lower priority
     int baseOff = count_prio_offset(adjPrio);
-    // the furthest faces draw first, and have the highest value
+    // the furthest faces draw first, and have the highest priority.
     // if two faces have the same distance, the one with the
-    // lower id draws first
-    renderPris[baseOff + prioIdx] = uint(distance << 16) | (~localId & 0xffffu);
+    // lower id draws first.
+    renderPris[baseOff + prioIdx] = distance << 16 | int(~localId & 0xffffu);
   }
 }
 
 int tile_height(int z, int x, int y) {
-#define ESCENE_OFFSET 40 // (184-104)/2
+#define ESCENE_OFFSET 40  // (184-104)/2
   return texelFetch(tileHeightSampler, ivec3(x + ESCENE_OFFSET, y + ESCENE_OFFSET, z), 0).r << 3;
 }
 
-ivec4 hillskew_vertex(ivec4 v, int hillskew, int y, int plane) {
+vec4 hillskew_vertex(vec4 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);
+    float fx = v.x / 128;
+    float fz = v.z / 128;
+    int sx = int(floor(fx));
+    int sz = int(floor(fz));
+    float h1 = mix(tile_height(plane, sx, sz), tile_height(plane, sx + 1, sz), fract(fx));
+    float h2 = mix(tile_height(plane, sx, sz + 1), tile_height(plane, sx + 1, sz + 1), fract(fx));
+    float h3 = mix(h1, h2, fract(fz));
+    return vec4(v.x, v.y + int(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, vec4 rvertA, vec4 rvertB, vec4 rvertC) {
   int size = minfo.size;
 
   if (localId < size) {
@@ -245,7 +238,7 @@ void sort_and_insert(uint localId, modelinfo minfo, int thisPriority, int thisDi
     const int numOfPriority = totalMappedNum[thisPriority];
     const int start = priorityOffset;                // index of first face with this priority
     const int end = priorityOffset + numOfPriority;  // index of last face with this priority
-    const uint renderPriority = uint(thisDistance << 16) | (~localId & 0xffffu);
+    const int renderPriority = thisDistance << 16 | int(~localId & 0xffffu);
     int myOffset = priorityOffset;
 
     // calculate position this face will be in
@@ -256,22 +249,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);
+    rvertA += pos;
+    rvertB += pos;
+    rvertC += 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);
+    rvertA = hillskew_vertex(rvertA, hillskew, minfo.y, plane);
+    rvertB = hillskew_vertex(rvertB, hillskew, minfo.y, plane);
+    rvertC = hillskew_vertex(rvertC, 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] = rvertA;
+    vout[outOffset + myOffset * 3 + 1] = rvertB;
+    vout[outOffset + myOffset * 3 + 2] = rvertC;
 
     if (toffset < 0) {
       uvout[outOffset + myOffset * 3] = vec4(0);
@@ -291,9 +284,9 @@ void sort_and_insert(uint localId, modelinfo minfo, int thisPriority, int thisDi
       }
 
       int orientation = flags & 0x7ff;
-      uvout[outOffset + myOffset * 3] = vec4(texA.x, rotatef(texA.yzw, orientation) + pos.xyz);
-      uvout[outOffset + myOffset * 3 + 1] = vec4(texB.x, rotatef(texB.yzw, orientation) + pos.xyz);
-      uvout[outOffset + myOffset * 3 + 2] = vec4(texC.x, rotatef(texC.yzw, orientation) + pos.xyz);
+      uvout[outOffset + myOffset * 3] = vec4(texA.x, rotate(vec4(texA.yzw, 0), orientation).xyz + pos.xyz);
+      uvout[outOffset + myOffset * 3 + 1] = vec4(texB.x, rotate(vec4(texB.yzw, 0), orientation).xyz + pos.xyz);
+      uvout[outOffset + myOffset * 3 + 2] = vec4(texC.x, rotate(vec4(texC.yzw, 0), orientation).xyz + pos.xyz);
     }
   }
 }
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..340947aec 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.0f
 
 #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 pos;
+layout(location = 1) in int ahsl;
+layout(location = 2) in vec4 tex;
 
 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,30 +67,28 @@ 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 = pos;
   gColor = vec4(rgb, 1.f - a);
   gHsl = float(hsl);
 
-  gTextureId = int(uv.x);  // the texture id + 1;
-  gTexPos = uv.yzw;
+  gTextureId = int(tex.x);  // the texture id + 1;
+  gTexPos = tex.yzw;
 
   // 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(pos.x - fogWest, fogEast - pos.x);
+  float zDist = min(pos.z - fogSouth, fogNorth - pos.z);
   float nearestEdgeDistance = min(xDist, zDist);
   float secondNearestEdgeDistance = max(xDist, zDist);
   float fogDistance =


Test patch:
diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/chatfilter/ChatFilterPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/chatfilter/ChatFilterPluginTest.java
index 318615fc4..c62e937d0 100644
--- a/runelite-client/src/test/java/net/runelite/client/plugins/chatfilter/ChatFilterPluginTest.java
+++ b/runelite-client/src/test/java/net/runelite/client/plugins/chatfilter/ChatFilterPluginTest.java
@@ -222,7 +222,7 @@ public class ChatFilterPluginTest
 	public void testMessageFromFriendIsFiltered()
 	{
 		when(chatFilterConfig.filterFriends()).thenReturn(true);
-		assertTrue(chatFilterPlugin.shouldFilterPlayerMessage("Iron Mammal"));
+		assertTrue(chatFilterPlugin.canFilterPlayer("Iron Mammal"));
 	}
 
 	@Test
@@ -230,7 +230,7 @@ public class ChatFilterPluginTest
 	{
 		when(client.isFriended("Iron Mammal", false)).thenReturn(true);
 		when(chatFilterConfig.filterFriends()).thenReturn(false);
-		assertFalse(chatFilterPlugin.shouldFilterPlayerMessage("Iron Mammal"));
+		assertFalse(chatFilterPlugin.canFilterPlayer("Iron Mammal"));
 	}
 
 	@Test
@@ -238,7 +238,7 @@ public class ChatFilterPluginTest
 	{
 		when(client.isFriended("B0aty", false)).thenReturn(false);
 		when(chatFilterConfig.filterFriendsChat()).thenReturn(true);
-		assertTrue(chatFilterPlugin.shouldFilterPlayerMessage("B0aty"));
+		assertTrue(chatFilterPlugin.canFilterPlayer("B0aty"));
 	}
 
 	@Test
@@ -246,21 +246,21 @@ public class ChatFilterPluginTest
 	{
 		when(friendsChatManager.findByName("B0aty")).thenReturn(mock(FriendsChatMember.class));
 		when(chatFilterConfig.filterFriendsChat()).thenReturn(false);
-		assertFalse(chatFilterPlugin.shouldFilterPlayerMessage("B0aty"));
+		assertFalse(chatFilterPlugin.canFilterPlayer("B0aty"));
 	}
 
 	@Test
 	public void testMessageFromSelfIsNotFiltered()
 	{
 		when(localPlayer.getName()).thenReturn("Swampletics");
-		assertFalse(chatFilterPlugin.shouldFilterPlayerMessage("Swampletics"));
+		assertFalse(chatFilterPlugin.canFilterPlayer("Swampletics"));
 	}
 
 	@Test
 	public void testMessageFromNonFriendNonFCIsFiltered()
 	{
 		when(client.isFriended("Woox", false)).thenReturn(false);
-		assertTrue(chatFilterPlugin.shouldFilterPlayerMessage("Woox"));
+		assertTrue(chatFilterPlugin.canFilterPlayer("Woox"));
 	}
 
 	@Test
@@ -269,8 +269,8 @@ public class ChatFilterPluginTest
 		when(chatFilterConfig.filteredNames()).thenReturn("Gamble [0-9]*");
 
 		chatFilterPlugin.updateFilteredPatterns();
-		assertTrue(chatFilterPlugin.shouldFilterByName("Gamble 1234"));
-		assertFalse(chatFilterPlugin.shouldFilterByName("Adam"));
+		assertTrue(chatFilterPlugin.isNameFiltered("Gamble 1234"));
+		assertFalse(chatFilterPlugin.isNameFiltered("Adam"));
 	}
 
 	@Test