diff --git a/cache/src/main/java/net/runelite/cache/definitions/NpcDefinition.java b/cache/src/main/java/net/runelite/cache/definitions/NpcDefinition.java index 69db696418d..94436f9823c 100644 --- a/cache/src/main/java/net/runelite/cache/definitions/NpcDefinition.java +++ b/cache/src/main/java/net/runelite/cache/definitions/NpcDefinition.java @@ -51,6 +51,7 @@ public class NpcDefinition public int crawlRotate180Animation = -1; public int crawlRotateLeftAnimation = -1; public int crawlRotateRightAnimation = -1; + public boolean idleAnimRestart; public short[] recolorToFind; public short[] recolorToReplace; public short[] retextureToFind; @@ -81,4 +82,5 @@ public class NpcDefinition public boolean canHideForOverlap; public int overlapTintHSL = 39188; public boolean unknown1 = false; + public boolean zbuf = true; } diff --git a/cache/src/main/java/net/runelite/cache/definitions/ObjectDefinition.java b/cache/src/main/java/net/runelite/cache/definitions/ObjectDefinition.java index 3a93d10baee..d229f09316c 100644 --- a/cache/src/main/java/net/runelite/cache/definitions/ObjectDefinition.java +++ b/cache/src/main/java/net/runelite/cache/definitions/ObjectDefinition.java @@ -68,6 +68,7 @@ public class ObjectDefinition private boolean obstructsGround = false; private int contouredGround = -1; private int supportsItems = -1; + private int raise; private int[] configChangeDest; private int category; private boolean isRotated = false; diff --git a/cache/src/main/java/net/runelite/cache/definitions/loaders/NpcLoader.java b/cache/src/main/java/net/runelite/cache/definitions/loaders/NpcLoader.java index 726148d1385..0c0aa1ed5fc 100644 --- a/cache/src/main/java/net/runelite/cache/definitions/loaders/NpcLoader.java +++ b/cache/src/main/java/net/runelite/cache/definitions/loaders/NpcLoader.java @@ -383,6 +383,10 @@ else if (opcode == 129) { def.unknown1 = true; } + else if (opcode == 130) + { + def.idleAnimRestart = true; + } else if (opcode == 145) { def.canHideForOverlap = true; @@ -391,6 +395,10 @@ else if (opcode == 146) { def.overlapTintHSL = stream.readUnsignedShort(); } + else if (opcode == 147) + { + def.zbuf = false; + } else if (opcode == 249) { length = stream.readUnsignedByte(); diff --git a/cache/src/main/java/net/runelite/cache/definitions/loaders/ObjectLoader.java b/cache/src/main/java/net/runelite/cache/definitions/loaders/ObjectLoader.java index f11aab4ac8b..d2abc4b3f41 100644 --- a/cache/src/main/java/net/runelite/cache/definitions/loaders/ObjectLoader.java +++ b/cache/src/main/java/net/runelite/cache/definitions/loaders/ObjectLoader.java @@ -396,6 +396,10 @@ else if (opcode == 95) { def.setSoundVisibility(is.readUnsignedByte()); } + else if (opcode == 96) + { + def.setRaise(is.readUnsignedByte()); + } else if (opcode == 249) { int length = is.readUnsignedByte(); diff --git a/gradle.properties b/gradle.properties index 84df6847473..813b1934873 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,10 +28,10 @@ org.gradle.parallel=true org.gradle.caching=false project.build.group=net.runelite -project.build.version=1.12.14 +project.build.version=1.12.15 glslang.path= -microbot.version=2.1.16 +microbot.version=2.1.17 microbot.commit.sha=nogit microbot.repo.url=http://138.201.81.246:8081/repository/microbot-snapshot/ microbot.repo.username= diff --git a/runelite-api/src/main/java/net/runelite/api/Client.java b/runelite-api/src/main/java/net/runelite/api/Client.java index 99430850dfe..2133c6fd70a 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -144,8 +144,7 @@ public interface Client extends OAuthApi, GameEngine void setGameState(GameState gameState); /** - * Causes the client to shutdown. It is faster than - * {@link java.applet.Applet#stop()} because it doesn't wait for 4000ms. + * Causes the client to shutdown. * This will call {@link System#exit} when it is done */ void stopNow(); diff --git a/runelite-api/src/main/java/net/runelite/api/ClientConfiguration.java b/runelite-api/src/main/java/net/runelite/api/ClientConfiguration.java new file mode 100644 index 00000000000..f7d0eb891e3 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/ClientConfiguration.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.api; + +import java.net.URL; + +public interface ClientConfiguration +{ + URL getCodeBase(); + String getParameter(String key); + void onError(String code); +} diff --git a/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java b/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java index 4b80d581f50..15081b5584f 100644 --- a/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java +++ b/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java @@ -55,6 +55,10 @@ public interface DecorativeObject extends TileObject */ int getYOffset(); + int getXOffset2(); + + int getYOffset2(); + /** * A bitfield containing various flags: *
{@code
diff --git a/runelite-api/src/main/java/net/runelite/api/GameEngine.java b/runelite-api/src/main/java/net/runelite/api/GameEngine.java
index 1de44385eaa..c37befbdfb7 100644
--- a/runelite-api/src/main/java/net/runelite/api/GameEngine.java
+++ b/runelite-api/src/main/java/net/runelite/api/GameEngine.java
@@ -31,6 +31,9 @@
  */
 public interface GameEngine
 {
+	void setConfiguration(ClientConfiguration configuration);
+	void initialize();
+
 	/**
 	 * Gets the canvas that contains everything.
 	 *
diff --git a/runelite-api/src/main/java/net/runelite/api/Perspective.java b/runelite-api/src/main/java/net/runelite/api/Perspective.java
index c8477c051b6..ca5d282e49b 100644
--- a/runelite-api/src/main/java/net/runelite/api/Perspective.java
+++ b/runelite-api/src/main/java/net/runelite/api/Perspective.java
@@ -132,8 +132,8 @@ public static Point localToCanvas(@Nonnull Client client, @Nonnull LocalPoint po
 			}
 
 			LocalPoint entityLocation = we.getLocalLocation();
-			int height = getTileHeight(we.getWorldView(), point.getX(), point.getY(), plane); // height in wv
-			height += getTileHeight(wv, entityLocation.getX(), entityLocation.getY(), wv.getPlane()); // height of we
+			int height = we.getWorldView().getTileHeight(point.getX(), point.getY(), plane); // height in wv
+			height += wv.getTileHeight(entityLocation.getX(), entityLocation.getY(), wv.getPlane()); // height of we
 			height -= heightOffset;
 
 			WorldView subWv = we.getWorldView();
@@ -671,30 +671,6 @@ public static int getFootprintTileHeight(@Nonnull Client client, @Nonnull LocalP
 		return h;
 	}
 
-	/**
-	 * Get the height of a location, in local coordinates. Interpolates the height from the adjacent tiles.
-	 * Does not account for bridges.
-	 * @return
-	 */
-	private static int getTileHeight(@Nonnull WorldView wv, int localX, int localY, int plane)
-	{
-		int offset = wv.isTopLevel() ? ESCENE_OFFSET : 0;
-		int sceneX = (localX >> LOCAL_COORD_BITS) + offset;
-		int sceneY = (localY >> LOCAL_COORD_BITS) + offset;
-		if (sceneX >= 0 && sceneY >= 0 && sceneX < wv.getSizeX() + offset && sceneY < wv.getSizeY() + offset)
-		{
-			int[][][] tileHeights = wv.getScene().getTileHeights();
-
-			int x = localX & (LOCAL_TILE_SIZE - 1);
-			int y = localY & (LOCAL_TILE_SIZE - 1);
-			int var8 = x * tileHeights[plane][sceneX + 1][sceneY] + (LOCAL_TILE_SIZE - x) * tileHeights[plane][sceneX][sceneY] >> LOCAL_COORD_BITS;
-			int var9 = tileHeights[plane][sceneX][sceneY + 1] * (LOCAL_TILE_SIZE - x) + x * tileHeights[plane][sceneX + 1][sceneY + 1] >> LOCAL_COORD_BITS;
-			return (LOCAL_TILE_SIZE - y) * var8 + y * var9 >> LOCAL_COORD_BITS;
-		}
-
-		return 0;
-	}
-
 	/**
 	 * Calculates a tile polygon from offset worldToScreen() points.
 	 *
@@ -761,10 +737,11 @@ public static Polygon getCanvasTileAreaPoly(
 		}
 
 		int offset = wv.isTopLevel() ? ESCENE_OFFSET : 0;
+		int escene = offset << 1;
 		final int msx = localLocation.getSceneX() + offset;
 		final int msy = localLocation.getSceneY() + offset;
 
-		if (msx < 0 || msy < 0 || msx >= wv.getSizeX() + offset || msy >= wv.getSizeY() + offset)
+		if (msx < 0 || msy < 0 || msx >= wv.getSizeX() + escene || msy >= wv.getSizeY() + escene)
 		{
 			// out of scene
 			return null;
@@ -778,10 +755,10 @@ public static Polygon getCanvasTileAreaPoly(
 		var scene = wv.getScene();
 		final byte[][][] tileSettings = scene.getExtendedTileSettings();
 
-		int tilePlane = level;
+		int mapLevel = level;
 		if (level < Constants.MAX_Z - 1 && (tileSettings[1][msx][msy] & TILE_FLAG_BRIDGE) == TILE_FLAG_BRIDGE)
 		{
-			tilePlane = level + 1;
+			mapLevel = level + 1;
 		}
 
 		final int swX = localLocation.getX() - (sizeX * LOCAL_TILE_SIZE / 2);
@@ -796,10 +773,10 @@ public static Polygon getCanvasTileAreaPoly(
 		final int nwX = neX;
 		final int nwY = swY;
 
-		final int swHeight = getTileHeight(wv, swX, swY, tilePlane) - heightOffset;
-		final int nwHeight = getTileHeight(wv, nwX, nwY, tilePlane) - heightOffset;
-		final int neHeight = getTileHeight(wv, neX, neY, tilePlane) - heightOffset;
-		final int seHeight = getTileHeight(wv, seX, seY, tilePlane) - heightOffset;
+		final int swHeight = wv.getTileHeight(swX, swY, mapLevel) - heightOffset;
+		final int nwHeight = wv.getTileHeight(nwX, nwY, mapLevel) - heightOffset;
+		final int neHeight = wv.getTileHeight(neX, neY, mapLevel) - heightOffset;
+		final int seHeight = wv.getTileHeight(seX, seY, mapLevel) - heightOffset;
 
 		Point p1 = localToCanvas(client, wv.getId(), swX, swY, swHeight);
 		Point p2 = localToCanvas(client, wv.getId(), nwX, nwY, nwHeight);
diff --git a/runelite-api/src/main/java/net/runelite/api/Renderable.java b/runelite-api/src/main/java/net/runelite/api/Renderable.java
index b47d52fcd28..b3162b3e91b 100644
--- a/runelite-api/src/main/java/net/runelite/api/Renderable.java
+++ b/runelite-api/src/main/java/net/runelite/api/Renderable.java
@@ -24,6 +24,8 @@
  */
 package net.runelite.api;
 
+import org.intellij.lang.annotations.MagicConstant;
+
 /**
  * Represents an object that can be rendered.
  */
@@ -42,4 +44,12 @@ public interface Renderable extends Node
 	void setModelHeight(int modelHeight);
 
 	int getAnimationHeightOffset();
+
+	@MagicConstant(intValues = {RENDERMODE_DEFAULT, RENDERMODE_SORTED, RENDERMODE_SORTED_NO_DEPTH, RENDERMODE_UNSORTED})
+	int getRenderMode();
+
+	int RENDERMODE_DEFAULT = 0;
+	int RENDERMODE_SORTED = 1;
+	int RENDERMODE_SORTED_NO_DEPTH = 2;
+	int RENDERMODE_UNSORTED = 3;
 }
diff --git a/runelite-api/src/main/java/net/runelite/api/WorldView.java b/runelite-api/src/main/java/net/runelite/api/WorldView.java
index 01bf39cb8fe..34c1636d9f7 100644
--- a/runelite-api/src/main/java/net/runelite/api/WorldView.java
+++ b/runelite-api/src/main/java/net/runelite/api/WorldView.java
@@ -264,4 +264,10 @@ Projectile createProjectile(int id, int plane, int startX, int startY, int start
 	 */
 	@MagicConstant(intValues = {Constants.CLICK_ACTION_NONE, Constants.CLICK_ACTION_WALK, Constants.CLICK_ACTION_SET_HEADING})
 	int getYellowClickAction();
+
+	/**
+	 * Gets the tile height at the given coordinates, interpolating the height from adjacent tiles.
+	 * @return
+	 */
+	int getTileHeight(int x, int y, int maplevel);
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java
index 188281d9eca..83fee7358ad 100644
--- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java
+++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java
@@ -32,11 +32,49 @@
 import com.google.inject.Guice;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
-import joptsimple.*;
+import java.io.File;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.lang.management.RuntimeMXBean;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+import javax.annotation.Nullable;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+import javax.management.ObjectName;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+import javax.swing.SwingUtilities;
+import joptsimple.ArgumentAcceptingOptionSpec;
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+import joptsimple.OptionSpec;
+import joptsimple.ValueConversionException;
+import joptsimple.ValueConverter;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
 import net.runelite.api.Client;
-import net.runelite.api.Constants;
 import net.runelite.client.account.SessionManager;
 import net.runelite.client.config.ConfigManager;
 import net.runelite.client.discord.DiscordService;
@@ -152,7 +190,6 @@ public class RuneLite
 	private Gson gson;
 
 	@Inject
-	@Nullable
 	private Client client;
 
 	@Inject
@@ -435,15 +472,10 @@ public void start() throws Exception
 		// Start the applet
 		copyJagexCache();
 
-		// Client size must be set prior to init
-		var applet = (Applet) client;
-		applet.setSize(Constants.GAME_FIXED_SIZE);
-
 		System.setProperty("jagex.disableBouncyCastle", "true");
 		System.setProperty("jagex.userhome", RUNELITE_DIR.getAbsolutePath());
 
-		applet.init();
-		applet.start();
+		client.initialize();
 
 		SplashScreen.stage(.57, null, "Loading configuration");
 
diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java b/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java
index a120f34b255..27cbaec3d84 100644
--- a/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java
+++ b/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java
@@ -32,7 +32,6 @@
 import com.google.inject.binder.ConstantBindingBuilder;
 import com.google.inject.name.Names;
 import com.google.inject.util.Providers;
-import java.applet.Applet;
 import java.io.File;
 import java.util.HashMap;
 import java.util.Map;
@@ -152,13 +151,6 @@ else if (entry.getValue() instanceof Boolean)
 		requestStaticInjection(Microbot.class);
 	}
 
-	@Provides
-	@Singleton
-	Applet provideApplet(Client client)
-	{
-		return (Applet) client;
-	}
-
 	@Provides
 	@Singleton
 	Client provideClient()
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 0cc7546ff96..b88520f1770 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
@@ -611,7 +611,7 @@ private void opTagTab(ScriptEvent event)
 			{
 				if (client.getVarbitValue(VarbitID.BANK_CURRENTTAB) == PotionStorage.BANKTAB_POTIONSTORE)
 				{
-					// Opening a tag tab with the potion store open would leave the store open in the bankground,
+					// Opening a tag tab with the potion store open would leave the store open in the background,
 					// making deposits not work. Force close the potion store.
 					log.debug("Closing potion store");
 					client.menuAction(-1, InterfaceID.Bankmain.POTIONSTORE_BUTTON, MenuAction.CC_OP, 1, -1, "Potion store", "");
@@ -873,7 +873,9 @@ public void onMenuOptionClicked(MenuOptionClicked event)
 			}
 		}
 
-		if (event.getMenuOption().startsWith("View tab") || event.getMenuOption().equals("View all items") || event.getMenuOption().equals("Potion store"))
+		MenuEntry menuEntry = event.getMenuEntry();
+		if (event.getMenuOption().startsWith("View tab") || event.getMenuOption().equals("View all items")
+			|| (menuEntry.getType() == MenuAction.CC_OP && menuEntry.getParam1() == InterfaceID.Bankmain.POTIONSTORE_BUTTON))
 		{
 			closeTag(false);
 		}
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 4744cb2f184..e1eb875364a 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
@@ -28,7 +28,21 @@
 import com.google.common.primitives.Ints;
 import com.google.inject.Provides;
 import lombok.extern.slf4j.Slf4j;
-import net.runelite.api.*;
+import net.runelite.api.BufferProvider;
+import net.runelite.api.Client;
+import net.runelite.api.Constants;
+import net.runelite.api.FloatProjection;
+import net.runelite.api.GameObject;
+import net.runelite.api.GameState;
+import net.runelite.api.Model;
+import net.runelite.api.Perspective;
+import net.runelite.api.Projection;
+import net.runelite.api.Renderable;
+import net.runelite.api.Scene;
+import net.runelite.api.TextureProvider;
+import net.runelite.api.TileObject;
+import net.runelite.api.WorldEntity;
+import net.runelite.api.WorldView;
 import net.runelite.api.events.GameStateChanged;
 import net.runelite.api.events.PostClientTick;
 import net.runelite.api.hooks.DrawCallbacks;
@@ -1214,12 +1228,13 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj
 
 		Renderable renderable = gameObject.getRenderable();
 		int size = m.getFaceCount() * 3 * VAO.VERT_SIZE;
-		if (renderable instanceof Player || m.getFaceTransparencies() != null)
+		int renderMode = renderable.getRenderMode();
+		if (renderMode == Renderable.RENDERMODE_SORTED_NO_DEPTH || m.getFaceTransparencies() != null)
 		{
 			// opaque player faces have their own vao and are drawn in a separate pass from normal opaque faces
 			// because they are not depth tested. transparent player faces don't need their own vao because normal
 			// transparent faces are already not depth tested
-			VAO o = renderable instanceof Player ? vaoPO.get(size) : vaoO.get(size);
+			VAO o = renderMode == Renderable.RENDERMODE_SORTED_NO_DEPTH ? vaoPO.get(size) : vaoO.get(size);
 			VAO a = vaoA.get(size);
 
 			int start = a.vbo.vb.position();
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 65c90c91eda..1989414d130 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
@@ -322,7 +322,7 @@ private int uploadZoneTile(Scene scene, Zone zone, Tile t, GpuIntBuffer vertexBu
 			uploadZoneRenderable(renderable, zone, 0, decorativeObject.getX() + decorativeObject.getXOffset(), decorativeObject.getZ(), decorativeObject.getY() + decorativeObject.getYOffset(), -1, -1, -1, -1, decorativeObject.getId(), vertexBuffer, ab);
 
 			Renderable renderable2 = decorativeObject.getRenderable2();
-			uploadZoneRenderable(renderable2, zone, 0, decorativeObject.getX(), decorativeObject.getZ(), decorativeObject.getY(), -1, -1, -1, -1, decorativeObject.getId(), vertexBuffer, ab);
+			uploadZoneRenderable(renderable2, zone, 0, decorativeObject.getX() + decorativeObject.getXOffset2(), decorativeObject.getZ(), decorativeObject.getY() + decorativeObject.getYOffset2(), -1, -1, -1, -1, decorativeObject.getId(), vertexBuffer, ab);
 		}
 
 		GroundObject groundObject = t.getGroundObject();
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/Zone.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/Zone.java
index b3e77b14b48..5bc9bed8e19 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/Zone.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/Zone.java
@@ -313,7 +313,6 @@ static class AlphaModel
 		// only set for static geometry as they require sorting
 		int radius;
 		int[] packedFaces;
-		byte[] renderPriorities;
 
 		static final int SKIP = 1; // temporary model is in a closer zone
 		static final int TEMP = 2; // temporary model added to a closer zone
@@ -422,7 +421,6 @@ void addAlphaModel(int vao, Model model, int startpos, int endpos, int x, int y,
 
 		assert radius >= 0;
 
-		m.renderPriorities = model.getFaceRenderPriorities();
 		m.radius = 2 + (int) Math.sqrt(radius);
 
 		assert packedFaces.length > 0;
@@ -462,7 +460,6 @@ void removeTemp()
 			{
 				alphaModels.remove(i);
 				m.packedFaces = null;
-				m.renderPriorities = null;
 				modelCache.add(m);
 			}
 			m.flags &= ~AlphaModel.SKIP;
@@ -643,54 +640,16 @@ void renderAlpha(int zx, int zz, int cyaw, int cpitch, int minLevel, int current
 				flush();
 			}
 
-			byte[] faceRenderPriorities = m.renderPriorities;
 			final int start = m.startpos / (VERT_SIZE >> 2); // ints to verts
-			if (faceRenderPriorities == null)
+			for (int i = diameter - 1; i >= 0; --i)
 			{
-				for (int i = diameter - 1; i >= 0; --i)
+				for (char face = zsortHead[i]; face != (char) -1; face = zsortNext[face])
 				{
-					for (char face = zsortHead[i]; face != (char) -1; face = zsortNext[face])
-					{
-						int faceIdx = face * 3;
-						faceIdx += start;
-						alphaElements.put(faceIdx++);
-						alphaElements.put(faceIdx++);
-						alphaElements.put(faceIdx++);
-					}
-				}
-			}
-			else
-			{
-				// Vanilla uses priority draw order for alpha faces and not depth draw order
-				// And since we don't have the full model here, only the alpha faces, we can't compute the
-				// 10/11 insertion points either. Just ignore those since I think they are mostly for players,
-				// which are rendered differently anyway.
-				Arrays.fill(numOfPriority, 0);
-
-				for (int i = diameter - 1; i >= 0; --i)
-				{
-					for (char face = zsortHead[i]; face != (char) -1; face = zsortNext[face])
-					{
-						final byte pri = faceRenderPriorities[face];
-						final int distIdx = numOfPriority[pri]++;
-
-						orderedFaces[pri][distIdx] = face;
-					}
-				}
-
-				for (int pri = 0; pri < 12; ++pri)
-				{
-					final int priNum = numOfPriority[pri];
-					final int[] priFaces = orderedFaces[pri];
-
-					for (int faceIdx = 0; faceIdx < priNum; ++faceIdx)
-					{
-						final int face = priFaces[faceIdx];
-						int idx = face * 3 + start;
-						alphaElements.put(idx++);
-						alphaElements.put(idx++);
-						alphaElements.put(idx++);
-					}
+					int faceIdx = face * 3;
+					faceIdx += start;
+					alphaElements.put(faceIdx++);
+					alphaElements.put(faceIdx++);
+					alphaElements.put(faceIdx++);
 				}
 			}
 		}
@@ -806,7 +765,6 @@ void multizoneLocs(Scene scene, int zx, int zz, int cx, int cz, Zone[][] zones)
 				m2.zofz = (byte) (closestZoneZ - zz);
 
 				m2.packedFaces = m.packedFaces;
-				m2.renderPriorities = m.renderPriorities;
 				m2.radius = m.radius;
 
 				m2.flags = AlphaModel.TEMP;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChanges.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChanges.java
index fe8708a9782..9acd04a10f4 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChanges.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChanges.java
@@ -280,7 +280,7 @@ private void init()
 		// Regular overload (NMZ)
 		add(combo(SUPER_ATTACK_POT, SUPER_STRENGTH_POT, SUPER_DEFENCE_POT, superRangingPot, superMagicPot, heal(HITPOINTS, -50)), ItemID.NZONE1DOSEOVERLOADPOTION, ItemID.NZONE2DOSEOVERLOADPOTION, ItemID.NZONE3DOSEOVERLOADPOTION, ItemID.NZONE4DOSEOVERLOADPOTION);
 		// Blighted overload (DMM)
-		add(combo(boost(ATTACK, perc(.15, 8)), boost(STRENGTH, perc(.15, 8)), new BoostedStatBoost(DEFENCE, false, perc(.1, -1)), boost(RANGED, perc(.1, 7)), boost(MAGIC, perc(.1, 1)), heal(HITPOINTS, -25)), ItemID.DEADMAN1DOSEOVERLOAD, ItemID.DEADMAN2DOSEOVERLOAD, ItemID.DEADMAN3DOSEOVERLOAD, ItemID.DEADMAN4DOSEOVERLOAD);
+		add(combo(boost(ATTACK, perc(.15, 8)), boost(STRENGTH, perc(.15, 8)), new BoostedStatBoost(DEFENCE, false, perc(.1, -1)), boost(RANGED, perc(.1, 7)), boost(MAGIC, perc(.1, 1)), heal(HITPOINTS, -10)), ItemID.DEADMAN1DOSEOVERLOAD, ItemID.DEADMAN2DOSEOVERLOAD, ItemID.DEADMAN3DOSEOVERLOAD, ItemID.DEADMAN4DOSEOVERLOAD);
 
 		// Bandages (Castle Wars)
 		add(new CastleWarsBandage(), ItemID.CASTLEWARS_BANDAGES);
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java
index 603d5fb3044..bd0120d8d88 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java
@@ -1604,7 +1604,7 @@ else if (parent != null && menuEntry.getOption().hashCode() == wornItemSwapConfi
 			{
 				final int componentId = w.getId(); // on dynamic components, this is the parent layer id
 				final int itemId = w.getIndex() == -1 ? -1 : ItemVariationMapping.map(w.getItemId());
-				final Integer op = getUiSwapConfig(shiftModifier(), componentId, itemId);
+				final Integer op = getMigratedUiSwapConfig(shiftModifier(), componentId, itemId);
 				if (op != null && op == menuEntry.getIdentifier())
 				{
 					swap(menu, menuEntries, index, menuEntries.length - 1);
@@ -1985,6 +1985,30 @@ private int defaultOp(ItemComposition itemComposition, boolean shift)
 		return -1; // use
 	}
 
+	private Integer getMigratedUiSwapConfig(boolean shift, int componentId, int itemId)
+	{
+		Integer swap = getUiSwapConfig(shift, componentId, itemId);
+		if (componentId == InterfaceID.Bankmain.ITEMS)
+		{
+			// remap 12.13 -> 12.12 for 1/28/2026 game update
+			if (swap == null)
+			{
+				swap = getUiSwapConfig(shift, InterfaceID.Bankmain.SCROLLBAR, itemId);
+				if (swap != null)
+				{
+					unsetUiSwapConfig(shift, InterfaceID.Bankmain.SCROLLBAR, itemId);
+					setUiSwapConfig(shift, InterfaceID.Bankmain.ITEMS, itemId, swap);
+					log.debug("Migrated swap {} for {} from scrollbar to items", swap, itemId);
+				}
+			}
+			else
+			{
+				unsetUiSwapConfig(shift, InterfaceID.Bankmain.SCROLLBAR, itemId);
+			}
+		}
+		return swap;
+	}
+
 	private Integer getUiSwapConfig(boolean shift, int componentId, int itemId)
 	{
 		String config = configManager.getConfiguration(MenuEntrySwapperConfig.GROUP,
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/api/actor/Rs2ActorModel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/api/actor/Rs2ActorModel.java
index 8a04bbabc1f..1767e21ee5c 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/api/actor/Rs2ActorModel.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/api/actor/Rs2ActorModel.java
@@ -402,6 +402,11 @@ public int getAnimationHeightOffset()
         return actor.getAnimationHeightOffset();
     }
 
+    @Override
+    public int getRenderMode() {
+        return actor.getRenderMode();
+    }
+
     @Override
     public Model getModel()
     {
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/api/tileitem/models/Rs2TileItemModel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/api/tileitem/models/Rs2TileItemModel.java
index c691c6a222e..d04f1735697 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/api/tileitem/models/Rs2TileItemModel.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/api/tileitem/models/Rs2TileItemModel.java
@@ -77,6 +77,11 @@ public int getAnimationHeightOffset() {
         return tileItem.getAnimationHeightOffset();
     }
 
+    @Override
+    public int getRenderMode() {
+        return tileItem.getRenderMode();
+    }
+
     @Override
     public Node getNext() {
         return tileItem.getNext();
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/MagicMushtree.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/MagicMushtree.java
new file mode 100644
index 00000000000..12d5eaa87dd
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/MagicMushtree.java
@@ -0,0 +1,99 @@
+package net.runelite.client.plugins.microbot.shortestpath;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import net.runelite.api.TileObject;
+import net.runelite.api.coords.WorldPoint;
+import net.runelite.client.plugins.microbot.util.player.Rs2Player;
+import net.runelite.client.plugins.microbot.util.widget.Rs2Widget;
+
+import static net.runelite.client.plugins.microbot.util.Global.sleepUntil;
+
+/**
+ * Handles the Magic Mushtree (Mycelium Transportation System) on Fossil Island.
+ * The mushtree network connects four locations:
+ * - House on the Hill
+ * - Verdant Valley
+ * - Sticky Swamp
+ * - Mushroom Meadow
+ */
+@Getter
+@RequiredArgsConstructor
+public enum MagicMushtree {
+    HOUSE_ON_THE_HILL("House on the Hill", new WorldPoint(3764, 3879, 1)),
+    VERDANT_VALLEY("Verdant Valley", new WorldPoint(3760, 3758, 0)),
+    STICKY_SWAMP("Sticky Swamp", new WorldPoint(3676, 3755, 0)),
+    MUSHROOM_MEADOW("Mushroom Meadow", new WorldPoint(3676, 3871, 0));
+
+    private final String destinationName;
+    private final WorldPoint destination;
+
+    // Object IDs for the Magic Mushtrees
+    public static final int MUSHTREE_HOUSE_ON_HILL = 30920;
+    public static final int MUSHTREE_OTHER = 30924;
+
+    private static final int OFFSET = 10;
+
+    /**
+     * Checks if the given object ID is a Magic Mushtree.
+     */
+    public static boolean isMagicMushtree(int objectId) {
+        return objectId == MUSHTREE_HOUSE_ON_HILL || objectId == MUSHTREE_OTHER;
+    }
+
+    /**
+     * Checks if the given TileObject is a Magic Mushtree.
+     */
+    public static boolean isMagicMushtree(TileObject tileObject) {
+        return tileObject != null && isMagicMushtree(tileObject.getId());
+    }
+
+    /**
+     * Handles the Magic Mushtree transport after the initial "Use" interaction.
+     * Waits for the menu to appear, then clicks the appropriate destination.
+     *
+     * @param transport The transport containing the destination
+     * @return true if the transport was handled successfully
+     */
+    public static boolean handleTransport(Transport transport) {
+        WorldPoint dest = transport.getDestination();
+        MagicMushtree destination = getByDestination(dest);
+
+        if (destination == null) {
+            return false;
+        }
+
+        // Wait for the mushtree menu widget to appear
+        if (!sleepUntil(() -> Rs2Widget.hasWidget("Mycelium"), 5000)) {
+            return false;
+        }
+
+        // Click the destination option
+        if (!Rs2Widget.clickWidget(destination.getDestinationName())) {
+            return false;
+        }
+
+        // Wait until we arrive at destination
+        sleepUntil(() -> Rs2Player.getWorldLocation().distanceTo(dest) < OFFSET, 10000);
+        return true;
+    }
+
+    /**
+     * Gets the MagicMushtree enum by destination WorldPoint.
+     */
+    public static MagicMushtree getByDestination(WorldPoint destination) {
+        if (destination == null) return null;
+
+        for (MagicMushtree mushtree : values()) {
+            WorldPoint dest = mushtree.getDestination();
+            if (dest.equals(destination)) {
+                return mushtree;
+            }
+            // Also match by X and Y only (ignore plane differences in destination matching)
+            if (dest.getX() == destination.getX() && dest.getY() == destination.getY()) {
+                return mushtree;
+            }
+        }
+        return null;
+    }
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/ActorModel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/ActorModel.java
index 7f538df1147..0e46d2fec71 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/ActorModel.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/ActorModel.java
@@ -397,6 +397,11 @@ public int getAnimationHeightOffset()
 		return actor.getAnimationHeightOffset();
 	}
 
+	@Override
+	public int getRenderMode() {
+		return actor.getRenderMode();
+	}
+
 	@Override
 	public Model getModel()
 	{
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/bank/enums/BankLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/bank/enums/BankLocation.java
index f76956c04a8..af2d3fb40cd 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/bank/enums/BankLocation.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/bank/enums/BankLocation.java
@@ -11,6 +11,7 @@
 import net.runelite.api.gameval.VarbitID;
 import net.runelite.client.plugins.microbot.Microbot;
 import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment;
+import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory;
 import net.runelite.client.plugins.microbot.util.player.Rs2Player;
 
 @Getter
@@ -38,6 +39,9 @@ public enum BankLocation {
     CRAFTING_GUILD(new WorldPoint(2936, 3281, 0), true),
 	DARKFROST(new WorldPoint(1527, 3292, 0), true),
     DARKMEYER(new WorldPoint(3604, 3366, 0), true),
+    DEEPFIN_POINT(new WorldPoint(1935, 2755,0), true),
+    DEEPFIN_MINE_MID(new WorldPoint(2011, 9186,0), true),
+    DEEPFIN_MINE_EAST(new WorldPoint(2094, 9197,0), true),
     DORGESH_KAAN_BANK(new WorldPoint(2702, 5350, 0), true),
     DRAYNOR_VILLAGE(new WorldPoint(3093, 3245, 0), false),
     DUEL_ARENA(new WorldPoint(3381, 3268, 0), true),
@@ -98,7 +102,8 @@ public enum BankLocation {
     SHILO_VILLAGE(new WorldPoint(2852, 2954, 0), true),
     SOPHANEM(new WorldPoint(2799, 5169, 0), true),
     SULPHUR_MINE(new WorldPoint(1453, 3858, 0), true),
-	TAL_TEKLAN(new WorldPoint(1243, 3121, 0), true),
+	SUNBLEAK_ISLAND(new WorldPoint(2195, 2314, 0), true),
+    TAL_TEKLAN(new WorldPoint(1243, 3121, 0), true),
     TREE_GNOME_STRONGHOLD_NIEVE(new WorldPoint(2445, 3424, 1), true),
     TZHAAR(new WorldPoint(2446, 5178, 0), true),
     VARLAMORE_EAST(new WorldPoint(1780, 3094, 0), true),
@@ -128,7 +133,9 @@ public boolean hasRequirements() {
                 boolean isWearingCraftingGuild = (Rs2Equipment.isWearing("brown apron") || Rs2Equipment.isWearing("golden apron")) ||
                         (Rs2Equipment.isWearing("max cape") || Rs2Equipment.isWearing("max hood")) ||
                         (Rs2Equipment.isWearing("crafting cape") || Rs2Equipment.isWearing("crafting hood"));
-                return isWearingCraftingGuild && (hasMaxedCrafting || hasFaladorHardDiary);
+                // Also check if crafting cape is in inventory (can equip it to teleport and enter)
+                boolean hasCraftingCapeInInventory = Rs2Inventory.contains("crafting cape") || Rs2Inventory.contains("crafting cape(t)");
+                return (isWearingCraftingGuild || hasCraftingCapeInInventory) && (hasMaxedCrafting || hasFaladorHardDiary);
             case LUMBRIDGE_BASEMENT:
                 return Rs2Player.getQuestState(Quest.RECIPE_FOR_DISASTER__ANOTHER_COOKS_QUEST) == QuestState.FINISHED;
             case COOKS_GUILD:
@@ -259,6 +266,18 @@ public boolean hasRequirements() {
             case PRIFDDINAS_SOUTH:
                 // Requires Song of the elves to be completed
                 return Rs2Player.getQuestState(Quest.SONG_OF_THE_ELVES) == QuestState.FINISHED;
+            case SUNBLEAK_ISLAND:
+                // Requires sailing level 72
+                return Rs2Player.getSkillRequirement(Skill.SAILING, 72, false);
+            case DEEPFIN_POINT:
+                // Requires sailing level 67
+                return Rs2Player.getSkillRequirement(Skill.SAILING, 67, false);
+            case DEEPFIN_MINE_MID:
+                // Requires sailing level 67 + Bank to be made
+                return Rs2Player.getSkillRequirement(Skill.SAILING, 67, false);
+            case DEEPFIN_MINE_EAST:
+                // Requires sailing level 67 + Bank to be made
+                return Rs2Player.getSkillRequirement(Skill.SAILING, 67, false);
             default:
                 return true;
         }
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/poh/data/MountedDigsite.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/poh/data/MountedDigsite.java
index 1ece55792b4..7e63bc90f40 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/poh/data/MountedDigsite.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/poh/data/MountedDigsite.java
@@ -35,7 +35,7 @@ public boolean execute() {
             return false;
         }
         if (pendant.getId() == objectId) {
-            //The correct id for the object means it has the right left click option, so we can just use that.
+            // The correct id for the object means it has the right left click option, so we can just use that.
             return Rs2GameObject.interact(pendant, destinationName);
         }
         Widget widget = getWidget();
@@ -49,6 +49,10 @@ public boolean execute() {
         return Rs2Widget.clickWidget(destinationName);
     }
 
+    private static Widget getWidget() {
+        return Rs2Widget.getWidget(InterfaceID.MENU, 3);
+    }
+
     public static final Integer[] IDS = {ObjectID.POH_AMULET_DIGSITE, ObjectID.POH_AMULET_DIG_LITHKREN, ObjectID.POH_AMULET_DIG_FOSSIL, ObjectID.POH_AMULET_DIG_DIGSITE};
 
     public static DecorativeObject getObject() {
@@ -63,10 +67,6 @@ public static boolean isMountedDigsite(DecorativeObject go) {
         return false;
     }
 
-    private static Widget getWidget() {
-        return Rs2Widget.getWidget(InterfaceID.MENU, 3);
-    }
-
     @Override
     public String displayInfo() {
         return "MountedDigsite -> " + destinationName;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java
index 7a0b6944c48..5e97769a763 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java
@@ -1605,9 +1605,8 @@ private static boolean handleTransports(List path, int indexOfStartP
                     }
 
                     if (object != null) {
-                        System.out.println("Object Type: " + Rs2GameObject.getObjectType(object));
-
-                        if (!(object instanceof GroundObject)) {
+                        // Skip reachability check for GroundObjects and Magic Mushtrees
+                        if (!(object instanceof GroundObject) && !MagicMushtree.isMagicMushtree(transport.getObjectId())) {
                             if (!Rs2Tile.isTileReachable(transport.getOrigin())) {
                                 break;
                             }
@@ -1811,6 +1810,11 @@ private static boolean handleObjectExceptions(Transport transport, TileObject ti
             sleepUntil(() -> Rs2Player.getWorldLocation().distanceTo2D(transport.getDestination()) < OFFSET, 10000);
             return true;
         }
+
+        // Handle Magic Mushtree (Fossil Island Mycelium Transportation System)
+        if (MagicMushtree.isMagicMushtree(tileObject)) {
+            return MagicMushtree.handleTransport(transport);
+        }
         return false;
     }
 
@@ -1925,6 +1929,18 @@ private static boolean handleInventoryTeleports(Transport transport, int itemId)
 
             if (itemAction.equalsIgnoreCase("open") && itemId == ItemID.BOOKOFSCROLLS_CHARGED) {
                 return handleMasterScrollBook(destination);
+            } else if (isDialogueBasedTeleportItem(transport.getDisplayInfo())) {
+                // Multi-destination teleport items: wait for destination selection dialogue
+                Rs2Dialogue.sleepUntilSelectAnOption();
+                Rs2Dialogue.clickOption(destination);
+                log.info("Traveling to {} - ({})", transport.getDisplayInfo(), transport.getDestination());
+                return true;
+            } else if (transport.getDisplayInfo().toLowerCase().contains("burning amulet")) {
+                // Burning amulet in inventory: confirm wilderness teleport
+                Rs2Dialogue.sleepUntilInDialogue();
+                Rs2Dialogue.clickOption("Okay, teleport to level");
+                log.info("Traveling to {} - ({})", transport.getDisplayInfo(), transport.getDestination());
+                return true;
             } else if (wildernessTransport) {
                 Rs2Dialogue.sleepUntilInDialogue();
                 return Rs2Dialogue.clickOption("Yes", "Okay");
@@ -1960,6 +1976,28 @@ private static boolean handleWearableTeleports(Transport transport, int itemId)
         return false;
     }
 
+    /**
+     * Checks if the teleport item requires dialogue-based destination selection.
+     * These are items that, when rubbed/activated, show a dialogue menu to choose destination.
+     *
+     * @param displayInfo the displayInfo from the transport
+     * @return true if the item requires dialogue handling
+     */
+    private static boolean isDialogueBasedTeleportItem(String displayInfo) {
+        if (displayInfo == null) return false;
+        String lowerDisplayInfo = displayInfo.toLowerCase();
+        return lowerDisplayInfo.contains("slayer ring")
+                || lowerDisplayInfo.contains("games necklace")
+                || lowerDisplayInfo.contains("skills necklace")
+                || lowerDisplayInfo.contains("ring of dueling")
+                || lowerDisplayInfo.contains("ring of wealth")
+                || lowerDisplayInfo.contains("amulet of glory")
+                || lowerDisplayInfo.contains("combat bracelet")
+                || lowerDisplayInfo.contains("digsite pendant")
+                || lowerDisplayInfo.contains("necklace of passage")
+                || lowerDisplayInfo.contains("giantsoul amulet");
+    }
+
     /**
      * Checks if the player's current location is within the specified area defined by the given world points.
      *
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapPlugin.java
index b41c8c893e2..dcf08f32660 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/minimap/MinimapPlugin.java
@@ -26,6 +26,7 @@
 
 import com.google.inject.Provides;
 import java.awt.Color;
+import java.time.temporal.ChronoUnit;
 import java.util.Arrays;
 import javax.inject.Inject;
 import net.runelite.api.Client;
@@ -43,6 +44,7 @@
 import net.runelite.client.events.ConfigChanged;
 import net.runelite.client.plugins.Plugin;
 import net.runelite.client.plugins.PluginDescriptor;
+import net.runelite.client.task.Schedule;
 
 @PluginDescriptor(
 	name = "Minimap",
@@ -68,6 +70,9 @@ public class MinimapPlugin extends Plugin
 	@Inject
 	private ClientThread clientThread;
 
+	@Inject
+	private ConfigManager configManager;
+
 	private SpritePixels[] originalDotSprites;
 
 	@Provides
@@ -83,6 +88,11 @@ protected void startUp()
 		storeOriginalDots();
 		replaceMapDots();
 		client.setMinimapZoom(config.zoom());
+		Double zoomLevel = configManager.getConfiguration(MinimapConfig.GROUP, "zoomLevel", double.class);
+		if (zoomLevel != null && zoomLevel > 0d)
+		{
+			client.setMinimapZoom(zoomLevel);
+		}
 	}
 
 	@Override
@@ -131,6 +141,13 @@ else if (event.getKey().equals("zoom"))
 		replaceMapDots();
 	}
 
+	@Schedule(period = 11, unit = ChronoUnit.SECONDS, asynchronous = true)
+	public void saveZoom()
+	{
+		double zoom = client.getMinimapZoom();
+		configManager.setConfiguration(MinimapConfig.GROUP, "zoomLevel", zoom);
+	}
+
 	@Subscribe
 	public void onScriptPostFired(ScriptPostFired scriptPostFired)
 	{
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java
index a63ec469d3a..7805e0cc9be 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java
@@ -83,6 +83,7 @@
 import net.runelite.client.plugins.PluginDescriptor;
 import net.runelite.client.plugins.worldhopper.ping.Ping;
 import net.runelite.client.plugins.worldhopper.ping.RetransmitCalculator;
+import net.runelite.client.plugins.worldhopper.ping.TCP_INFO_v0;
 import net.runelite.client.ui.ClientToolbar;
 import net.runelite.client.ui.NavigationButton;
 import net.runelite.client.ui.overlay.OverlayManager;
@@ -449,13 +450,17 @@ else if (BEFORE_OPTIONS.contains(option))
 	@Subscribe
 	public void onGameStateChanged(GameStateChanged gameStateChanged)
 	{
-		// If the player has disabled the side bar plugin panel, do not update the UI
-		if (config.showSidebar() && gameStateChanged.getGameState() == GameState.LOGGED_IN)
+		if (gameStateChanged.getGameState() == GameState.LOGGED_IN)
 		{
 			if (lastWorld != client.getWorld())
 			{
 				int newWorld = client.getWorld();
-				panel.switchCurrentHighlight(newWorld, lastWorld);
+				// If the player has disabled the side bar plugin panel, do not update the UI
+				if (config.showSidebar())
+				{
+					panel.switchCurrentHighlight(newWorld, lastWorld);
+				}
+				currentPing = -1;
 				lastWorld = newWorld;
 			}
 		}
@@ -831,7 +836,7 @@ private void pingInitialWorlds()
 
 		for (World world : worldResult.getWorlds())
 		{
-			int ping = ping(world);
+			int ping = ping(world, false);
 			SwingUtilities.invokeLater(() -> panel.updatePing(world.getId(), ping));
 		}
 
@@ -872,7 +877,7 @@ private void pingNextWorld()
 			return;
 		}
 
-		int ping = ping(world);
+		int ping = ping(world, false);
 		log.trace("Ping for world {} is: {}", world.getId(), ping);
 
 		if (panel.isActive())
@@ -900,8 +905,26 @@ private void pingCurrentWorld()
 			return;
 		}
 
-		int ping = ping(currentWorld);
-		log.trace("Ping for current world is: {}", currentPing);
+		int ping = ping(currentWorld, true);
+		log.trace("Ping for current world is: {}", ping);
+
+		FileDescriptor fd = client.getSocketFD();
+		int rtt = -1;
+		if (fd != null)
+		{
+			TCP_INFO_v0 tcpInfo = Ping.getTcpInfo(fd);
+			if (tcpInfo != null)
+			{
+				rtt = (int) (tcpInfo.RttUs.longValue() / 1000L);
+				retransmitCalculator.record(tcpInfo);
+			}
+		}
+
+		if (ping < 0)
+		{
+			ping = rtt; // use rtt for ping if icmp is blocked
+			storedPings.put(currentWorld.getId(), rtt);
+		}
 
 		if (ping < 0)
 		{
@@ -914,12 +937,6 @@ private void pingCurrentWorld()
 		{
 			SwingUtilities.invokeLater(() -> panel.updatePing(currentWorld.getId(), currentPing));
 		}
-
-		FileDescriptor fd = client.getSocketFD();
-		if (fd != null)
-		{
-			retransmitCalculator.record(fd);
-		}
 	}
 
 	Integer getStoredPing(World world)
@@ -932,9 +949,9 @@ Integer getStoredPing(World world)
 		return storedPings.get(world.getId());
 	}
 
-	private int ping(World world)
+	private int ping(World world, boolean isCurrentWorld)
 	{
-		int ping = Ping.ping(world);
+		int ping = Ping.ping(world, !isCurrentWorld);
 		storedPings.put(world.getId(), ping);
 		return ping;
 	}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/Ping.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/Ping.java
index cd542567d72..8dccc36488e 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/Ping.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/Ping.java
@@ -29,7 +29,11 @@
 import com.sun.jna.Memory;
 import com.sun.jna.Native;
 import com.sun.jna.Pointer;
+import com.sun.jna.platform.win32.WinNT;
+import com.sun.jna.ptr.IntByReference;
+import java.io.FileDescriptor;
 import java.io.IOException;
+import java.lang.reflect.Field;
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
@@ -49,7 +53,13 @@ public class Ping
 
 	private static short seq;
 
+	@Deprecated
 	public static int ping(World world)
+	{
+		return ping(world, false);
+	}
+
+	public static int ping(World world, boolean useTcpPing)
 	{
 		InetAddress inetAddress;
 		try
@@ -73,20 +83,29 @@ public static int ping(World world)
 			switch (OSType.getOSType())
 			{
 				case Windows:
-					return windowsPing(inetAddress);
+					int p = windowsPing(inetAddress);
+					if (p == -1 && useTcpPing)
+					{
+						p = tcpPing(inetAddress);
+					}
+					return p;
 				case MacOS:
 				case Linux:
 					try
 					{
 						return icmpPing(inetAddress, OSType.getOSType() == OSType.MacOS);
 					}
-					catch (Exception ex)
+					catch (IOException ex)
 					{
 						log.debug("error during icmp ping", ex);
-						return tcpPing(inetAddress);
 					}
+					// FALLTHROUGH
 				default:
-					return tcpPing(inetAddress);
+					if (useTcpPing)
+					{
+						return tcpPing(inetAddress);
+					}
+					return -1;
 			}
 		}
 		catch (IOException ex)
@@ -269,4 +288,58 @@ private static int tcpPing(InetAddress inetAddress) throws IOException
 			return (int) ((end - start) / 1000000L);
 		}
 	}
+
+	public static TCP_INFO_v0 getTcpInfo(FileDescriptor fd)
+	{
+		if (OSType.getOSType() != OSType.Windows)
+		{
+			return null;
+		}
+
+		int handle;
+		try
+		{
+			Field f = FileDescriptor.class.getDeclaredField("fd");
+			f.setAccessible(true);
+			handle = f.getInt(fd);
+		}
+		catch (NoSuchFieldException | IllegalAccessException ex)
+		{
+			log.debug(null, ex);
+			return null;
+		}
+
+		IntByReference tcpInfoVersion = new IntByReference(0); // Version 0 of TCP_INFO
+		TCP_INFO_v0 info = new TCP_INFO_v0();
+		IntByReference bytesReturned = new IntByReference();
+
+		Ws2_32 winsock = Ws2_32.INSTANCE;
+		int rc;
+		try
+		{
+			rc = winsock.WSAIoctl(
+				new WinNT.HANDLE(Pointer.createConstant(handle)),
+				Ws2_32.SIO_TCP_INFO,
+				tcpInfoVersion.getPointer(), Integer.BYTES,
+				info.getPointer(), info.size(),
+				bytesReturned,
+				Pointer.NULL,
+				Pointer.NULL
+			);
+		}
+		catch (UnsatisfiedLinkError ex)
+		{
+			// probably Windows 7
+			log.debug("WSAIoctl()", ex);
+			return null;
+		}
+		if (rc != 0)
+		{
+			log.debug("WSAIoctl(SIO_TCP_INFO) error"); // WSAGetLastError() seems to always be 0?
+			return null;
+		}
+
+		info.read();
+		return info;
+	}
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/RetransmitCalculator.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/RetransmitCalculator.java
index 68e29189729..3ced1859c3e 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/RetransmitCalculator.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/ping/RetransmitCalculator.java
@@ -24,14 +24,8 @@
  */
 package net.runelite.client.plugins.worldhopper.ping;
 
-import com.sun.jna.Pointer;
-import com.sun.jna.platform.win32.WinNT;
-import com.sun.jna.ptr.IntByReference;
-import java.io.FileDescriptor;
-import java.lang.reflect.Field;
 import java.util.Arrays;
 import lombok.extern.slf4j.Slf4j;
-import net.runelite.client.util.OSType;
 
 @Slf4j
 public class RetransmitCalculator
@@ -44,58 +38,8 @@ public class RetransmitCalculator
 	private final long[] bytesRetrans = new long[SAMPLES];
 	private int loss;
 
-	public void record(FileDescriptor fd)
+	public void record(TCP_INFO_v0 info)
 	{
-		if (OSType.getOSType() != OSType.Windows)
-		{
-			return;
-		}
-
-		int handle;
-		try
-		{
-			Field f = FileDescriptor.class.getDeclaredField("fd");
-			f.setAccessible(true);
-			handle = f.getInt(fd);
-		}
-		catch (NoSuchFieldException | IllegalAccessException ex)
-		{
-			log.debug(null, ex);
-			return;
-		}
-
-		IntByReference tcpInfoVersion = new IntByReference(0); // Version 0 of TCP_INFO
-		TCP_INFO_v0 info = new TCP_INFO_v0();
-		IntByReference bytesReturned = new IntByReference();
-
-		Ws2_32 winsock = Ws2_32.INSTANCE;
-		int rc;
-		try
-		{
-			rc = winsock.WSAIoctl(
-				new WinNT.HANDLE(Pointer.createConstant(handle)),
-				Ws2_32.SIO_TCP_INFO,
-				tcpInfoVersion.getPointer(), Integer.BYTES,
-				info.getPointer(), info.size(),
-				bytesReturned,
-				Pointer.NULL,
-				Pointer.NULL
-			);
-		}
-		catch (UnsatisfiedLinkError ex)
-		{
-			// probably Windows 7
-			log.debug("WSAIoctl()", ex);
-			return;
-		}
-		if (rc != 0)
-		{
-			log.debug("WSAIoctl(SIO_TCP_INFO) error"); // WSAGetLastError() seems to always be 0?
-			return;
-		}
-
-		info.read();
-
 		int nextIndex = index++ & (SAMPLES - 1);
 
 		long connectionTime = info.ConnectionTimeMs.longValue();
diff --git a/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java b/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java
index e7604b2b252..5bd4e772b5d 100644
--- a/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java
+++ b/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java
@@ -27,7 +27,6 @@
 package net.runelite.client.rs;
 
 import com.google.common.base.Strings;
-import java.applet.Applet;
 import java.io.IOException;
 import java.util.Map;
 import java.util.function.Supplier;
@@ -193,7 +192,7 @@ private Client loadClient(RSConfig config) throws ClassNotFoundException, Illega
 			.loadClass(initialClass);
 
 		Client rs = (Client) clientClass.newInstance();
-		((Applet) rs).setStub(new RSAppletStub(config, runtimeConfigLoader));
+		rs.setConfiguration(new RSAppletStub(config, runtimeConfigLoader));
 
 		log.info("injected-client {}", rs.getBuildID());
 
diff --git a/runelite-client/src/main/java/net/runelite/client/rs/RSAppletStub.java b/runelite-client/src/main/java/net/runelite/client/rs/RSAppletStub.java
index 327526ba4fd..54adf8c5818 100644
--- a/runelite-client/src/main/java/net/runelite/client/rs/RSAppletStub.java
+++ b/runelite-client/src/main/java/net/runelite/client/rs/RSAppletStub.java
@@ -25,42 +25,22 @@
  */
 package net.runelite.client.rs;
 
-import java.applet.Applet;
-import java.applet.AppletContext;
-import java.applet.AppletStub;
-import java.applet.AudioClip;
-import java.awt.Image;
-import java.io.IOException;
-import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.util.Enumeration;
-import java.util.Iterator;
 import javax.swing.SwingUtilities;
 import lombok.RequiredArgsConstructor;
+import net.runelite.api.ClientConfiguration;
 import net.runelite.client.RuntimeConfig;
 import net.runelite.client.RuntimeConfigLoader;
 import net.runelite.client.ui.FatalErrorDialog;
 import net.runelite.client.util.LinkBrowser;
 
 @RequiredArgsConstructor
-class RSAppletStub implements AppletStub
+class RSAppletStub implements ClientConfiguration
 {
 	private final RSConfig config;
 	private final RuntimeConfigLoader runtimeConfigLoader;
 
-	@Override
-	public boolean isActive()
-	{
-		return true;
-	}
-
-	@Override
-	public URL getDocumentBase()
-	{
-		return getCodeBase();
-	}
-
 	@Override
 	public URL getCodeBase()
 	{
@@ -81,129 +61,58 @@ public String getParameter(String name)
 	}
 
 	@Override
-	public AppletContext getAppletContext()
+	public void onError(String code)
 	{
-		return new AppletContext()
+		try
 		{
-			@Override
-			public AudioClip getAudioClip(URL url)
-			{
-				return null;
-			}
-
-			@Override
-			public Image getImage(URL url)
-			{
-				return null;
-			}
-
-			@Override
-			public Applet getApplet(String name)
-			{
-				return null;
-			}
-
-			@Override
-			public Enumeration getApplets()
-			{
-				return null;
-			}
-
-			@Override
-			public void showDocument(URL url)
-			{
-				if (url.getPath().startsWith("/error_game_"))
-				{
-					try
-					{
-						RuntimeConfig rtc = runtimeConfigLoader.get();
-						if (rtc.showOutageMessage())
-						{
-							return;
-						}
-					}
-					catch (Exception e)
-					{
-					}
-
-					String code = url.getPath()
-						.replace("/", "")
-						.replace(".ws", "");
-
-					if (code.equals("error_game_js5connect"))
-					{
-						SwingUtilities.invokeLater(() ->
-							new FatalErrorDialog("RuneLite is unable to connect to the RuneScape update server. " +
-								"RuneScape might be offline for an update, check the game status page. If the game " +
-								"is online, then either a firewall is blocking RuneLite, or you don't have internet access.")
-								.setTitle("RuneLite", "Unable to connect to update server")
-								.addButton("Game Status", () -> LinkBrowser.browse("https://secure.runescape.com/m=news/game-status-information-centre?oldschool=1"))
-								.open());
-					}
-					else if (code.equals("error_game_js5io"))
-					{
-						SwingUtilities.invokeLater(() ->
-							new FatalErrorDialog("OldSchool RuneScape is unable to retrieve updates from its update server. " +
-								"This is likely due to a firewall blocking the RuneScape server. Try disabling your firewall, or use " +
-								"a different network.")
-								.setTitle("RuneLite", "Unable to connect to update server")
-								.addHelpButtons()
-								.open());
-					}
-					else if (code.equals("error_game_crash"))
-					{
-						SwingUtilities.invokeLater(() ->
-							new FatalErrorDialog("OldSchool RuneScape has crashed. Crashes are most commonly caused by plugin hub plugins, " +
-								"but can also be caused by RuneLite or Jagex client bugs. If you receive this message commonly, try playing in " +
-								"safe mode to eliminate the potential of plugins causing the crash. The client log file will contain additional " +
-								"information about the crash.")
-								.setTitle("RuneLite", "OldSchool RuneScape has crashed")
-								.addHelpButtons()
-								.open());
-					}
-					else
-					{
-						SwingUtilities.invokeLater(() ->
-							new FatalErrorDialog("OldSchool RuneScape has crashed with the message: " + code)
-								.setTitle("RuneLite", "OldSchool RuneScape has crashed")
-								.addHelpButtons()
-								.open());
-					}
-				}
-			}
-
-			@Override
-			public void showDocument(URL url, String target)
+			RuntimeConfig rtc = runtimeConfigLoader.get();
+			if (rtc.showOutageMessage())
 			{
-				showDocument(url);
+				return;
 			}
+		}
+		catch (Exception e)
+		{
+		}
 
-			@Override
-			public void showStatus(String status)
-			{
-			}
-
-			@Override
-			public void setStream(String key, InputStream stream) throws IOException
-			{
-			}
-
-			@Override
-			public InputStream getStream(String key)
-			{
-				return null;
-			}
-
-			@Override
-			public Iterator getStreamKeys()
-			{
-				return null;
-			}
-		};
-	}
-
-	@Override
-	public void appletResize(int width, int height)
-	{
+		if (code.equals("error_game_js5connect"))
+		{
+			SwingUtilities.invokeLater(() ->
+				new FatalErrorDialog("RuneLite is unable to connect to the RuneScape update server. " +
+					"RuneScape might be offline for an update, check the game status page. If the game " +
+					"is online, then either a firewall is blocking RuneLite, or you don't have internet access.")
+					.setTitle("RuneLite", "Unable to connect to update server")
+					.addButton("Game Status", () -> LinkBrowser.browse("https://secure.runescape.com/m=news/game-status-information-centre?oldschool=1"))
+					.open());
+		}
+		else if (code.equals("error_game_js5io"))
+		{
+			SwingUtilities.invokeLater(() ->
+				new FatalErrorDialog("OldSchool RuneScape is unable to retrieve updates from its update server. " +
+					"This is likely due to a firewall blocking the RuneScape server. Try disabling your firewall, or use " +
+					"a different network.")
+					.setTitle("RuneLite", "Unable to connect to update server")
+					.addHelpButtons()
+					.open());
+		}
+		else if (code.equals("error_game_crash"))
+		{
+			SwingUtilities.invokeLater(() ->
+				new FatalErrorDialog("OldSchool RuneScape has crashed. Crashes are most commonly caused by plugin hub plugins, " +
+					"but can also be caused by RuneLite or Jagex client bugs. If you receive this message commonly, try playing in " +
+					"safe mode to eliminate the potential of plugins causing the crash. The client log file will contain additional " +
+					"information about the crash.")
+					.setTitle("RuneLite", "OldSchool RuneScape has crashed")
+					.addHelpButtons()
+					.open());
+		}
+		else
+		{
+			SwingUtilities.invokeLater(() ->
+				new FatalErrorDialog("OldSchool RuneScape has crashed with the message: " + code)
+					.setTitle("RuneLite", "OldSchool RuneScape has crashed")
+					.addHelpButtons()
+					.open());
+		}
 	}
 }
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/outline/ModelOutlineRenderer.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/outline/ModelOutlineRenderer.java
index 1b9e90ba78e..f7d0a6a4b6f 100644
--- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/outline/ModelOutlineRenderer.java
+++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/outline/ModelOutlineRenderer.java
@@ -1092,7 +1092,10 @@ private void drawOutline(DecorativeObject decorativeObject, int outlineWidth, Co
 			if (model != null)
 			{
 				// Offset is not used for the second model
-				drawModelOutline(decorativeObject.getWorldView(), model, decorativeObject.getX(), decorativeObject.getY(), decorativeObject.getZ() - renderable2.getAnimationHeightOffset(),
+				drawModelOutline(decorativeObject.getWorldView(), model,
+					decorativeObject.getX() + decorativeObject.getXOffset2(),
+					decorativeObject.getY() + decorativeObject.getYOffset2(),
+					decorativeObject.getZ() - renderable2.getAnimationHeightOffset(),
 					0, outlineWidth, color, feather);
 			}
 		}
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/fairy_rings.tsv b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/fairy_rings.tsv
index b8983f746ba..f7bd32fb4d3 100644
--- a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/fairy_rings.tsv
+++ b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/fairy_rings.tsv
@@ -54,6 +54,7 @@
 1651 3010 0					9650=1	5	
 1359 2941 0					9650=1	5	
 1429 3324 0					9650=1	5	
+3178 2447 0				Troubled Tortugans		5	
 	2996 3114 0					5	AIQ
 	2700 3247 0					5	AIR
 	2328 4426 0					5	AIR DLR DJQ AJS
@@ -104,6 +105,7 @@
 	3423 3016 0					5	DLQ
 	2213 3099 0					5	DLR
 	3447 9824 0			In Search of the Myreque		5	DLS
+	3178 2447 0			Troubled Tortugans		5	CJQ
 	1651 3010 0				9650=1	5	AJP
 	1359 2941 0				9650=1	5	CKQ
 	1429 3324 0				9650=1	5	AIS
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/transports.tsv b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/transports.tsv
index 8ddcb18da11..a52cead9ea7 100644
--- a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/transports.tsv
+++ b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/transports.tsv
@@ -1492,7 +1492,24 @@
 3666 3809 0	3685 3756 0	Jump on;Rubber cap mushroom;30606									
 3663 3808 0	3685 3756 0	Jump on;Rubber cap mushroom;30606									
 3664 3808 0	3685 3756 0	Jump on;Rubber cap mushroom;30606									
-3665 3808 0	3685 3756 0	Jump on;Rubber cap mushroom;30606									
+3665 3808 0	3685 3756 0	Jump on;Rubber cap mushroom;30606
+# Magic Mushtree - Fossil Island Mycelium Transportation System
+# House on the Hill mushtree (ID 30920)
+3764 3880 1	3760 3758 0	Use;Magic Mushtree;30920
+3764 3880 1	3676 3755 0	Use;Magic Mushtree;30920
+3764 3880 1	3676 3871 0	Use;Magic Mushtree;30920
+# Verdant Valley mushtree (ID 30924)
+3758 3756 0	3764 3879 1	Use;Magic Mushtree;30924
+3758 3756 0	3676 3755 0	Use;Magic Mushtree;30924
+3758 3756 0	3676 3871 0	Use;Magic Mushtree;30924
+# Sticky Swamp mushtree (ID 30924)
+3677 3755 0	3764 3879 1	Use;Magic Mushtree;30924
+3677 3755 0	3760 3758 0	Use;Magic Mushtree;30924
+3677 3755 0	3676 3871 0	Use;Magic Mushtree;30924
+# Mushroom Meadow mushtree (ID 30924)
+3674 3871 0	3764 3879 1	Use;Magic Mushtree;30924
+3674 3871 0	3760 3758 0	Use;Magic Mushtree;30924
+3674 3871 0	3676 3755 0	Use;Magic Mushtree;30924
 3768 3868 1	3768 3868 0	Climb-down;Trapdoor;30725									
 3768 3868 0	3768 3868 1	Climb-up;Ladder;30727									
 3767 3868 1	3768 3868 0	Climb-down;Trapdoor;30725									
@@ -1544,33 +1561,62 @@
 2775 3234 1	2776 3235 0	Climb-down;Ship's ladder;9745									
 2808 3162 0	2808 3162 1	Climb-up;Ladder;16683								3	
 2808 3162 1	2808 3162 0	Climb-down;Ladder;16679								3	
-2743 3153 0	2713 9564 0	Enter;Dungeon entrance;34713			875 Coins
-2714 9564 0	2743 3153 0	Leave;Exit;20878
-2691 9564 0	2689 9564 0	Chop-down;Vines;21731		1351;1349;1361;1353;1355;1357;1359							
-2689 9564 0	2691 9564 0	Chop-down;Vines;21731		1351;1349;1361;1353;1355;1357;1359							
-2683 9568 0	2683 9570 0	Chop-down;Vines;21732		1351;1349;1361;1353;1355;1357;1359
-2683 9570 0	2683 9568 0	Chop-down;Vines;21732		1351;1349;1361;1353;1355;1357;1359
-2674 9479 0	2676 9479 0	Chop-down;Vines;21734		1351;1349;1361;1353;1355;1357;1359
-2676 9479 0	2674 9479 0	Chop-down;Vines;21734		1351;1349;1361;1353;1355;1357;1359
-2693 9482 0	2695 9482 0	Chop-down;Vines;21735		1351;1349;1361;1353;1355;1357;1359
-2695 9482 0	2693 9482 0	Chop-down;Vines;21735		1351;1349;1361;1353;1355;1357;1359
-2649 9591 0	2643 9595 2	Walk-up;Stairs;21722									
-2650 9591 0	2643 9595 2	Walk-up;Stairs;21722									
-2643 9595 2	2649 9591 0	Walk-down;Stairs;21724									
-2647 9557 0	2649 9562 0	Jump-from;Stepping Stone;21739								3	
-2649 9562 0	2647 9557 0	Jump-from;Stepping Stone;21738								3	
-2636 9517 0	2636 9510 2	Walk-up;Stairs;21725									
-2637 9517 0	2636 9510 2	Walk-up;Stairs;21725									
-2636 9510 2	2636 9517 0	Walk-down;Stairs;21726									
-2682 9506 0	2687 9506 0	Walk-across;Log balance;20882								3	
-2687 9506 0	2682 9506 0	Walk-across;Log balance;20884								3	
-2698 9500 0	2698 9492 0	Squeeze-through;Pipe;21727									
-2698 9492 0	2698 9500 0	Squeeze-through;Pipe;21727									
-2697 9436 0	2684 9436 0	Enter;Crevice;30198
+# Main Entrance - ID 20877
+# With coin requirement (875 coins for entry)
+2744 3154 0	2713 9564 0	Enter;Dungeon entrance;20877			875 Coins
+2744 3153 0	2713 9564 0	Enter;Dungeon entrance;20877			875 Coins
+2745 3154 0	2713 9564 0	Enter;Dungeon entrance;20877			875 Coins
+2743 3154 0	2713 9564 0	Enter;Dungeon entrance;20877			875 Coins
+# Permanent access (after paying 1.5m one-time fee)
+2744 3154 0	2713 9564 0	Enter;Dungeon entrance;20877
+2744 3153 0	2713 9564 0	Enter;Dungeon entrance;20877
+2745 3154 0	2713 9564 0	Enter;Dungeon entrance;20877
+2743 3154 0	2713 9564 0	Enter;Dungeon entrance;20877
+# Exit from inside
+2713 9564 0	2744 3153 0	Leave;Exit;20878
+# Vines (axe IDs: bronze, iron, steel, black, mithril, adamant, rune, dragon, 3rd age, infernal, crystal)
+2689 9564 0	2691 9564 0	Chop-down;Vines;21731		1351;1349;1353;1361;1355;1357;1359;6739;20011;13241;23673;25066;28222
+2691 9564 0	2689 9564 0	Chop-down;Vines;21731		1351;1349;1353;1361;1355;1357;1359;6739;20011;13241;23673;25066;28222
+2683 9568 0	2683 9570 0	Chop-down;Vines;21732		1351;1349;1353;1361;1355;1357;1359;6739;20011;13241;23673;25066;28222
+2683 9570 0	2683 9568 0	Chop-down;Vines;21732		1351;1349;1353;1361;1355;1357;1359;6739;20011;13241;23673;25066;28222
+2674 9499 0	2672 9499 0	Chop-down;Vines;21733		1351;1349;1353;1361;1355;1357;1359;6739;20011;13241;23673;25066;28222
+2672 9499 0	2674 9499 0	Chop-down;Vines;21733		1351;1349;1353;1361;1355;1357;1359;6739;20011;13241;23673;25066;28222
+2676 9479 0	2674 9479 0	Chop-down;Vines;21734		1351;1349;1353;1361;1355;1357;1359;6739;20011;13241;23673;25066;28222
+2674 9479 0	2676 9479 0	Chop-down;Vines;21734		1351;1349;1353;1361;1355;1357;1359;6739;20011;13241;23673;25066;28222
+2693 9482 0	2695 9482 0	Chop-down;Vines;21735		1351;1349;1353;1361;1355;1357;1359;6739;20011;13241;23673;25066;28222
+2695 9482 0	2693 9482 0	Chop-down;Vines;21735		1351;1349;1353;1361;1355;1357;1359;6739;20011;13241;23673;25066;28222
+# 87 Agility Vine Shortcut
+2673 9583 0	2670 9583 2	Climb;Vine;26880
+2670 9583 2	2673 9583 0	Climb;Vine;26882
+# Stairs
+2649 9591 0	2643 9595 2	Walk-up;Stairs;21722
+2643 9595 2	2649 9591 0	Walk-down;Stairs;21724
+2636 9517 0	2636 9510 2	Walk-up;Stairs;21725
+2636 9510 2	2636 9517 0	Walk-down;Stairs;21726
+# Stepping Stones
+2647 9557 0	2649 9562 0	Jump-from;Stepping stone;21739
+2649 9562 0	2647 9557 0	Jump-from;Stepping stone;21738
+2695 9533 0	2697 9525 0	Cross;Stepping stone;19040
+2697 9525 0	2695 9533 0	Cross;Stepping stone;19040
+2690 9547 0	2682 9548 0	Cross;Stepping stone;19040
+2682 9548 0	2690 9547 0	Cross;Stepping stone;19040
+# Log Balance
+2682 9506 0	2687 9506 0	Walk-across;Log balance;20882
+2687 9506 0	2682 9506 0	Walk-across;Log balance;20884
+# Pipes
+2698 9500 0	2698 9492 0	Squeeze-through;Pipe;21727
+2698 9492 0	2698 9500 0	Squeeze-through;Pipe;21727
+2655 9573 0	2655 9566 0	Squeeze-through;Pipe;21728
+2655 9566 0	2655 9573 0	Squeeze-through;Pipe;21728
+# Crevice
 2684 9436 0	2697 9436 0	Enter;Crevice;30198
+2697 9436 0	2684 9436 0	Enter;Crevice;30198
 # Brimhaven Dungeon - CKR Fairy Ring Alternate Entrance
-2759 3062 0	2734 9478 0	Climb-down;Rope;30200
-2734 9477 0	2761 3062 0	Enter;Crevice;30201
+2761 3062 0	2734 9478 0	Climb;Rope;66
+2761 3063 0	2734 9478 0	Climb;Rope;66
+2760 3064 0	2734 9478 0	Climb;Rope;66
+2760 3061 0	2734 9478 0	Climb;Rope;66
+2734 9478 0	2760 3061 0	Use;Crevice;30201
 
 # Karamja Volcano											
 2855 3169 0	2855 9569 0	Climb-down;Rocks;11441									
@@ -5195,7 +5241,6 @@
 3066 3741 0	3187 10127 0	Jump-Down;Crevice;40386
 3066 3740 0	3187 10127 0	Jump-Down;Crevice;40386
 3066 3739 0	3187 10127 0	Jump-Down;Crevice;40386
-
 # CRASH SITE
 2026 5611 0	2128 5647 0	Enter;Cavern Entrance;28686
 2128 5647 0	2026 5611 0	Climb-up;Rope;28687
@@ -5205,11 +5250,9 @@
 2167 9308 0	2310 2919 0	Exit;Opening;40737
 2763 2951 0	2763 2951 1	Climb-up;Ladder;16683
 2763 2952 1	2763 2951 0	Climb-down;Ladder;16679
-
 # woodcutting guild
 1574 3483 1	1575 3483 0	Climb-down;Rope ladder;28858
 1575 3483 0	1574 3483 1	Climb-up;Rope ladder;28857
-
 #Grimstone Dungeon
 2902 10454 0	2902 10456 0	Climb;Uneven stone ledges;60120
-2902 10456 0	2902 10454 0	Climb;Uneven stone ledges;60120
+2902 10456 0	2902 10454 0	Climb;Uneven stone ledges;60120
\ No newline at end of file
diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/itemstats/ItemStatEffectTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/itemstats/ItemStatEffectTest.java
index ce6fdb05aea..e2a46141603 100644
--- a/runelite-client/src/test/java/net/runelite/client/plugins/itemstats/ItemStatEffectTest.java
+++ b/runelite-client/src/test/java/net/runelite/client/plugins/itemstats/ItemStatEffectTest.java
@@ -521,9 +521,9 @@ public void testBlightedOverload()
 	{
 		final Effect blightedOverload = new ItemStatChanges().get(ItemID.DEADMAN4DOSEOVERLOAD);
 
-		assertEquals(-25, skillChange(Skill.HITPOINTS, 49, 44, blightedOverload));
-		assertEquals(-25, skillChange(Skill.HITPOINTS, 64, 64, blightedOverload));
-		assertEquals(-25, skillChange(Skill.HITPOINTS, 99, 77, blightedOverload));
+		assertEquals(-10, skillChange(Skill.HITPOINTS, 49, 44, blightedOverload));
+		assertEquals(-10, skillChange(Skill.HITPOINTS, 64, 64, blightedOverload));
+		assertEquals(-10, skillChange(Skill.HITPOINTS, 99, 77, blightedOverload));
 
 		assertEquals(13, skillChange(Skill.STRENGTH, 36, 36, blightedOverload));
 		assertEquals(17, skillChange(Skill.STRENGTH, 66, 66, blightedOverload));