From 36adaaf8c6770e1413ef7beaae0a70375d0e979d Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 28 Jan 2026 16:23:41 -0500 Subject: [PATCH 01/21] mes: migrate bankmain items swaps --- .../MenuEntrySwapperPlugin.java | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) 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 676abc7fc96..74321af9b6c 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 @@ -1602,7 +1602,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); @@ -1982,6 +1982,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, From 75649a1bcdc9688416bb358efaa3c831d17d8b1b Mon Sep 17 00:00:00 2001 From: pkcs21 Date: Wed, 28 Jan 2026 14:43:44 -0800 Subject: [PATCH 02/21] Add CJQ/Great Conch entry to fairy rings --- .../client/plugins/microbot/shortestpath/fairy_rings.tsv | 2 ++ 1 file changed, 2 insertions(+) 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 7e7a8a7d1d5..1f72860ca18 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 From cdd00579d77f60e7636d6de55992a2e3fce92913 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 28 Jan 2026 18:42:37 -0500 Subject: [PATCH 03/21] banktags: fix opening potion storage when a tag tab is open --- .../runelite/client/plugins/banktags/tabs/TabInterface.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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); } From 2591ac6b922f4cefcfef76f95510bf8986a328cb Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 28 Jan 2026 21:23:54 -0500 Subject: [PATCH 04/21] world hopper: reset current world ping on hop --- .../client/plugins/worldhopper/WorldHopperPlugin.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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..9dad39e64a9 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 @@ -449,13 +449,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; } } From 8ab69df4617c768b1cdbaa440580c6ead58a454a Mon Sep 17 00:00:00 2001 From: chillibloke Date: Wed, 28 Jan 2026 15:57:48 +1030 Subject: [PATCH 05/21] fix(walker): handle dialogue for inventory teleport items Add dialogue handling for multi-destination teleport items when used from inventory (not equipped). Previously, items like Slayer Ring would interact successfully but fail to handle the destination selection dialogue that appears afterward. Supported items: Slayer ring, Games necklace, Skills necklace, Ring of dueling, Ring of wealth, Amulet of glory, Combat bracelet, Digsite pendant, Necklace of passage, Giantsoul amulet. Co-Authored-By: Claude Opus 4.5 --- .../microbot/util/walker/Rs2Walker.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) 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..0de755a9883 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 @@ -1925,6 +1925,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 +1972,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. * From 454baeb76a61d05524580ad64448e94ee11a6997 Mon Sep 17 00:00:00 2001 From: chillibloke Date: Thu, 29 Jan 2026 19:27:14 +1030 Subject: [PATCH 06/21] fix(bank): check for crafting cape in inventory for guild access Allow Crafting Guild bank location to be accessible when the crafting cape is in inventory, not just when equipped. Co-Authored-By: Claude Opus 4.5 --- .../plugins/microbot/util/bank/enums/BankLocation.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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..28de1736103 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 @@ -128,7 +129,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: From 7c523722f724064efa5e8709ee4eb3ec48a10a4d Mon Sep 17 00:00:00 2001 From: Macweese <50101641+Macweese@users.noreply.github.com> Date: Mon, 19 Jan 2026 12:28:54 +0100 Subject: [PATCH 07/21] item stats: decrease blighted overload hp drain --- .../runelite/client/plugins/itemstats/ItemStatChanges.java | 2 +- .../client/plugins/itemstats/ItemStatEffectTest.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) 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..f7dd0e72d62 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, -5)), ItemID.DEADMAN1DOSEOVERLOAD, ItemID.DEADMAN2DOSEOVERLOAD, ItemID.DEADMAN3DOSEOVERLOAD, ItemID.DEADMAN4DOSEOVERLOAD); // Bandages (Castle Wars) add(new CastleWarsBandage(), ItemID.CASTLEWARS_BANDAGES); 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..28a8f2af5c3 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(-5, skillChange(Skill.HITPOINTS, 49, 44, blightedOverload)); + assertEquals(-5, skillChange(Skill.HITPOINTS, 64, 64, blightedOverload)); + assertEquals(-5, skillChange(Skill.HITPOINTS, 99, 77, blightedOverload)); assertEquals(13, skillChange(Skill.STRENGTH, 36, 36, blightedOverload)); assertEquals(17, skillChange(Skill.STRENGTH, 66, 66, blightedOverload)); From 8f6d4cfbf55021fd05a417ce9561d1feb8a6e505 Mon Sep 17 00:00:00 2001 From: irkedMATT Date: Sat, 31 Jan 2026 13:35:15 +0000 Subject: [PATCH 08/21] feat: add new bank locations with sailing requirements Add new bank locations for areas requiring sailing skill levels: - SUNBLEAK_ISLAND (requires sailing level 72) - DEEPFIN_POINT (requires sailing level 67) - DEEPFIN_MINE_MID (requires sailing level 67) - DEEPFIN_MINE_EAST (requires sailing level 68) Each location includes proper world point coordinates and member status checks integrated with the existing hasRequirements() method. --- .../microbot/util/bank/enums/BankLocation.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) 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..3916572be1a 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 @@ -38,6 +38,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 +101,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), @@ -259,6 +263,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 68 + Bank to be made + return Rs2Player.getSkillRequirement(Skill.SAILING, 67, false); default: return true; } From bcb17946aec7a0b8dad832e8e410570bebc02fae Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 31 Jan 2026 11:44:20 -0500 Subject: [PATCH 09/21] minimap: save and restore zoom level on restart --- .../client/plugins/minimap/MinimapPlugin.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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) { From d896124839ac4920af4cd9234b7d965e5e55fc97 Mon Sep 17 00:00:00 2001 From: Jordan Atwood Date: Sat, 31 Jan 2026 20:17:54 -0800 Subject: [PATCH 10/21] item stats: update blighted overload hp drain Despite [the blog post indicating overloads would drain 5 hp][1], they actually drain 2hp 5 times (down from 5hp 5 times), meaning the drain is 10hp instead. While this might be changed in the future, this should reflect what is currently ingame. [1]: https://secure.runescape.com/m=news/deadman-annihilation-overview--rewards-blog?oldschool=1 --- .../runelite/client/plugins/itemstats/ItemStatChanges.java | 2 +- .../client/plugins/itemstats/ItemStatEffectTest.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) 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 f7dd0e72d62..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, -5)), 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/test/java/net/runelite/client/plugins/itemstats/ItemStatEffectTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/itemstats/ItemStatEffectTest.java index 28a8f2af5c3..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(-5, skillChange(Skill.HITPOINTS, 49, 44, blightedOverload)); - assertEquals(-5, skillChange(Skill.HITPOINTS, 64, 64, blightedOverload)); - assertEquals(-5, 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)); From 75b1f167ce1392098dd538e65366108e827ec704 Mon Sep 17 00:00:00 2001 From: chillibloke Date: Sun, 1 Feb 2026 19:33:12 +1030 Subject: [PATCH 11/21] feat(walker): add Magic Mushtree transport and fix POH digsite pendant - Add Magic Mushtree (Mycelium Transportation System) support for Fossil Island - Fix POH mounted digsite pendant to use widget clicks like MountedXerics - Add Brimhaven Dungeon transport entries with correct object IDs - Skip tile reachability check for mushtree transports Co-Authored-By: Claude Opus 4.5 --- .../microbot/shortestpath/MagicMushtree.java | 99 +++++++++++++++++ .../util/poh/data/MountedDigsite.java | 10 +- .../microbot/util/walker/Rs2Walker.java | 10 +- .../microbot/shortestpath/transports.tsv | 103 +++++++++++++----- 4 files changed, 184 insertions(+), 38 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/MagicMushtree.java 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/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 0de755a9883..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; } 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 From e90469714eb68b781c9fa7779b90a6cb70be0cdc Mon Sep 17 00:00:00 2001 From: irkedMATT <59846844+irkedMATT@users.noreply.github.com> Date: Sun, 1 Feb 2026 11:02:59 +0000 Subject: [PATCH 12/21] Fix sailing level requirement for DEEPFIN_MINE_EAST My earlier commit contained a typo. The value has been reviewed and updated. --- .../client/plugins/microbot/util/bank/enums/BankLocation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3916572be1a..63193f8342d 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 @@ -273,7 +273,7 @@ public boolean hasRequirements() { // Requires sailing level 67 + Bank to be made return Rs2Player.getSkillRequirement(Skill.SAILING, 67, false); case DEEPFIN_MINE_EAST: - // Requires sailing level 68 + Bank to be made + // Requires sailing level 67 + Bank to be made return Rs2Player.getSkillRequirement(Skill.SAILING, 67, false); default: return true; From 8e574d6177549dfcfd9caa3cae95f834da8a129d Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 31 Jan 2026 22:03:04 -0500 Subject: [PATCH 13/21] world hopper: use tcp rtt for current world ping if icmp is blocked --- .../worldhopper/WorldHopperPlugin.java | 26 ++++++--- .../client/plugins/worldhopper/ping/Ping.java | 58 +++++++++++++++++++ .../ping/RetransmitCalculator.java | 58 +------------------ 3 files changed, 78 insertions(+), 64 deletions(-) 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 9dad39e64a9..bfeda74ff15 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; @@ -905,7 +906,24 @@ private void pingCurrentWorld() } int ping = ping(currentWorld); - log.trace("Ping for current world is: {}", currentPing); + 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 + } if (ping < 0) { @@ -918,12 +936,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) 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..42ad83672ce 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; @@ -269,4 +273,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(); From e61962d501430b6a5aceaae91ae10752ed5232cb Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 31 Jan 2026 22:29:41 -0500 Subject: [PATCH 14/21] world hopper: use tcp ping for slow world ping if icmp is blocked --- .../worldhopper/WorldHopperPlugin.java | 11 +++++---- .../client/plugins/worldhopper/ping/Ping.java | 23 +++++++++++++++---- 2 files changed, 25 insertions(+), 9 deletions(-) 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 bfeda74ff15..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 @@ -836,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)); } @@ -877,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()) @@ -905,7 +905,7 @@ private void pingCurrentWorld() return; } - int ping = ping(currentWorld); + int ping = ping(currentWorld, true); log.trace("Ping for current world is: {}", ping); FileDescriptor fd = client.getSocketFD(); @@ -923,6 +923,7 @@ private void pingCurrentWorld() if (ping < 0) { ping = rtt; // use rtt for ping if icmp is blocked + storedPings.put(currentWorld.getId(), rtt); } if (ping < 0) @@ -948,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 42ad83672ce..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 @@ -53,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 @@ -77,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) From 3be54288e106de97cc99d48227661e3295d5ebaa Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 23 Jan 2026 21:14:40 -0500 Subject: [PATCH 15/21] api: update 236 --- .../net/runelite/api/DecorativeObject.java | 4 ++ .../java/net/runelite/api/Perspective.java | 43 +++++-------------- .../java/net/runelite/api/Renderable.java | 10 +++++ .../main/java/net/runelite/api/WorldView.java | 6 +++ .../client/plugins/gpu/GpuPlugin.java | 6 +-- .../client/plugins/gpu/SceneUploader.java | 2 +- .../overlay/outline/ModelOutlineRenderer.java | 5 ++- 7 files changed, 38 insertions(+), 38 deletions(-) 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/Perspective.java b/runelite-api/src/main/java/net/runelite/api/Perspective.java
index aa3b2098c78..3623b8101f7 100644
--- a/runelite-api/src/main/java/net/runelite/api/Perspective.java
+++ b/runelite-api/src/main/java/net/runelite/api/Perspective.java
@@ -122,8 +122,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 = wv.getTileHeight(plane, point.getX(), point.getY()); // height in wv
+			height += wv.getTileHeight(wv.getPlane(), entityLocation.getX(), entityLocation.getY()); // height of we
 			height -= heightOffset;
 
 			WorldView subWv = we.getWorldView();
@@ -661,30 +661,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.
 	 *
@@ -751,10 +727,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;
@@ -768,10 +745,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);
@@ -786,10 +763,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/plugins/gpu/GpuPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java
index 5a08fc3a64f..f9a18cd3c85 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
@@ -52,7 +52,6 @@
 import net.runelite.api.GameState;
 import net.runelite.api.Model;
 import net.runelite.api.Perspective;
-import net.runelite.api.Player;
 import net.runelite.api.Projection;
 import net.runelite.api.Renderable;
 import net.runelite.api.Scene;
@@ -1231,12 +1230,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/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);
 			}
 		}

From db791dbb8c91cfaedf4bf7523b838c2a8e62b892 Mon Sep 17 00:00:00 2001
From: Adam 
Date: Fri, 23 Jan 2026 20:05:40 -0500
Subject: [PATCH 16/21] gpu: remove face priority sorting from alpha models

---
 .../net/runelite/client/plugins/gpu/Zone.java | 56 +++----------------
 1 file changed, 7 insertions(+), 49 deletions(-)

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;

From 016c03c3de29659c86f1d304a26d3de51d80ff1d Mon Sep 17 00:00:00 2001
From: Adam 
Date: Sun, 1 Feb 2026 16:27:38 -0500
Subject: [PATCH 17/21] cache: update 236

---
 .../net/runelite/cache/definitions/NpcDefinition.java     | 2 ++
 .../net/runelite/cache/definitions/ObjectDefinition.java  | 1 +
 .../net/runelite/cache/definitions/loaders/NpcLoader.java | 8 ++++++++
 .../runelite/cache/definitions/loaders/ObjectLoader.java  | 4 ++++
 4 files changed, 15 insertions(+)

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();

From 93d0b922b2706bd7424c1b6d1f0563d4f7b32e6e Mon Sep 17 00:00:00 2001
From: Max Weber 
Date: Sun, 14 Sep 2025 22:12:22 -0600
Subject: [PATCH 18/21] client: do not reference Applet

---
 .../main/java/net/runelite/api/Client.java    |   3 +-
 .../net/runelite/api/ClientConfiguration.java |  34 ++++
 .../java/net/runelite/api/GameEngine.java     |   3 +
 .../java/net/runelite/client/RuneLite.java    |  10 +-
 .../net/runelite/client/RuneLiteModule.java   |   8 -
 .../net/runelite/client/rs/ClientLoader.java  |   3 +-
 .../net/runelite/client/rs/RSAppletStub.java  | 191 +++++-------------
 7 files changed, 90 insertions(+), 162 deletions(-)
 create mode 100644 runelite-api/src/main/java/net/runelite/api/ClientConfiguration.java

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 41e4ba4afca..b841ff4f307 100644
--- a/runelite-api/src/main/java/net/runelite/api/Client.java
+++ b/runelite-api/src/main/java/net/runelite/api/Client.java
@@ -146,8 +146,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/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-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java
index 83b5409314d..05ea29d9ac9 100644
--- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java
+++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java
@@ -32,7 +32,6 @@
 import com.google.inject.Guice;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
-import java.applet.Applet;
 import java.io.File;
 import java.io.IOException;
 import java.lang.management.ManagementFactory;
@@ -76,7 +75,6 @@
 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;
@@ -155,7 +153,6 @@ public class RuneLite
 	private Gson gson;
 
 	@Inject
-	@Nullable
 	private Client client;
 
 	@Inject
@@ -304,15 +301,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 1939ac89011..f4c7bac208d 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.io.IOException;
 import java.util.HashMap;
@@ -148,13 +147,6 @@ else if (entry.getValue() instanceof Boolean)
 			.to(DeferredEventBus.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/rs/ClientLoader.java b/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java
index 563f29019af..02b2cedf6f9 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

From 8fcf932c30c30543ccef4803ffa73521d38a8d07 Mon Sep 17 00:00:00 2001
From: RuneLite updater 
Date: Mon, 2 Feb 2026 15:46:26 +0000
Subject: [PATCH 19/21] Release 1.12.15

---
 gradle.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle.properties b/gradle.properties
index 5fec3e5a4c9..35b295d8dc3 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -28,6 +28,6 @@ org.gradle.parallel=true
 org.gradle.caching=false
 
 project.build.group=net.runelite
-project.build.version=1.12.15-SNAPSHOT
+project.build.version=1.12.15
 
 glslang.path=

From a973684dc09dce4753daf75c7e33a9c36a2e04df Mon Sep 17 00:00:00 2001
From: RuneLite updater 
Date: Mon, 2 Feb 2026 15:46:27 +0000
Subject: [PATCH 20/21] Bump for 1.12.16-SNAPSHOT

[ci skip]
---
 gradle.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle.properties b/gradle.properties
index 35b295d8dc3..09588402acf 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -28,6 +28,6 @@ org.gradle.parallel=true
 org.gradle.caching=false
 
 project.build.group=net.runelite
-project.build.version=1.12.15
+project.build.version=1.12.16-SNAPSHOT
 
 glslang.path=

From ea4f6d5176d409f211cf35ffa15fa34a0d3dc0c3 Mon Sep 17 00:00:00 2001
From: Adam 
Date: Mon, 2 Feb 2026 15:45:37 -0500
Subject: [PATCH 21/21] api: fix localToCanvas() on boats

---
 runelite-api/src/main/java/net/runelite/api/Perspective.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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 3623b8101f7..c8cd4e1b5d7 100644
--- a/runelite-api/src/main/java/net/runelite/api/Perspective.java
+++ b/runelite-api/src/main/java/net/runelite/api/Perspective.java
@@ -122,8 +122,8 @@ public static Point localToCanvas(@Nonnull Client client, @Nonnull LocalPoint po
 			}
 
 			LocalPoint entityLocation = we.getLocalLocation();
-			int height = wv.getTileHeight(plane, point.getX(), point.getY()); // height in wv
-			height += wv.getTileHeight(wv.getPlane(), entityLocation.getX(), entityLocation.getY()); // 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();