From 585f604190ee565c89ebf18aa240120a6657a999 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Sat, 18 Apr 2026 09:11:37 -0400 Subject: [PATCH] fix(ValeTotem): correct bow selection and withdraw race (#400) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Select the configured bow option by name via Rs2Widget.handleProcessingInterface so the hotkey is resolved from the live fletching interface rather than a hardcoded child ID. The previous scheme mapped SHORTBOW → child 15 with hotkey-index 1, but widget 270,13's sparse dynamic children could produce "3" at that slot for some log types, causing shortbow selection to press the longbow key. - Bank withdraws now sleepUntil on the concrete predicate (hasKnife, hasLogBasket, log count reaches requested target) instead of the generic waitForInventoryChanges + re-check. Rs2Bank.withdrawOne/withdrawX return immediately after queueing the menu click, so under server lag the follow-up check would read stale state and spuriously report "no knife available" right after the knife was withdrawn. Bumps plugin to 1.0.10. --- .../microbot/valetotems/ValeTotemPlugin.java | 2 +- .../valetotems/handlers/BankingHandler.java | 100 ++++++++++-------- .../valetotems/handlers/FletchingHandler.java | 25 ++--- 3 files changed, 72 insertions(+), 55 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java index dd6731bcb0..1848816778 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java @@ -27,7 +27,7 @@ ) @Slf4j public class ValeTotemPlugin extends Plugin { - static final String version = "1.0.9"; + static final String version = "1.0.10"; @Inject private ValeTotemConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/BankingHandler.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/BankingHandler.java index 973710a3bb..6b97da0f2a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/BankingHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/BankingHandler.java @@ -21,6 +21,7 @@ import static net.runelite.client.plugins.microbot.util.Global.sleep; import static net.runelite.client.plugins.microbot.util.Global.sleepGaussian; +import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; import java.util.Random; import net.runelite.client.plugins.microbot.valetotems.enums.TotemLocation; @@ -176,26 +177,22 @@ public static boolean withdrawRequiredItems(GameSession gameSession) { int logId = InventoryUtils.getLogId(); String logTypeName = config != null ? config.logType().getDisplayName() : "Yew Logs"; - // Withdraw logs (materials check already performed, so this should succeed) + // Rs2Bank.withdrawX only queues the menu click — it returns immediately and the item + // can lag several ticks behind. Generic waitForInventoryChanges + hasRequiredItems + // was racing and reporting shortages right after a successful withdraw. Microbot.log("Withdrawing " + logsToWithdraw + " " + logTypeName + " from bank"); - boolean withdrew = Rs2Bank.withdrawX(logId, logsToWithdraw); - if (withdrew) { - Microbot.log("Successfully withdrew " + logsToWithdraw + " " + logTypeName); - Rs2Inventory.waitForInventoryChanges(3000); - } else { - Microbot.log("Failed to withdraw logs from bank"); + int logsBefore = InventoryUtils.getLogCount(); + int expectedLogs = logsBefore + logsToWithdraw; + if (!Rs2Bank.withdrawX(logId, logsToWithdraw)) { + Microbot.log("Failed to queue log withdrawal"); return false; } - - // Final verification - boolean hasRequired = InventoryUtils.hasRequiredItems(); - if (hasRequired) { - Microbot.log("Successfully completed item withdrawal - all required items now in inventory"); - } else { - Microbot.log("Warning: Required items verification failed after withdrawal"); + if (!sleepUntil(() -> InventoryUtils.getLogCount() >= expectedLogs, 3000)) { + Microbot.log("Log withdrawal timed out (have " + InventoryUtils.getLogCount() + "/" + expectedLogs + ")"); + return false; } - - return hasRequired; + Microbot.log("Successfully withdrew " + logsToWithdraw + " " + logTypeName); + return InventoryUtils.hasRequiredItems(); } catch (Exception e) { Microbot.log("Error withdrawing required items: " + e.getMessage()); @@ -603,14 +600,11 @@ public static boolean ensureLogBasketAvailable() { // Check if log basket is in bank and withdraw it if (Rs2Bank.hasItem(InventoryUtils.LOG_BASKET_ID)) { Microbot.log("Withdrawing log basket from bank for extended route"); - boolean withdrew = Rs2Bank.withdrawOne(InventoryUtils.LOG_BASKET_ID); - if (withdrew) { - Rs2Inventory.waitForInventoryChanges(3000); - return InventoryUtils.hasLogBasket(); - } else { - Microbot.log("Failed to withdraw log basket from bank"); + if (!Rs2Bank.withdrawOne(InventoryUtils.LOG_BASKET_ID)) { + Microbot.log("Failed to queue log basket withdrawal"); return false; } + return sleepUntil(InventoryUtils::hasLogBasket, 3000); } Microbot.log("No log basket found in bank"); @@ -667,32 +661,37 @@ private static boolean ensureKnifeInInventory(GameSession gameSession) { return true; } - // Try to withdraw fletching knife first (prioritized) + // Try to withdraw fletching knife first (prioritized). + // Rs2Bank.withdrawOne returns true as soon as the click is queued; we must wait + // on the concrete predicate (hasKnife), not a generic inventory-change wait, + // or a slow server tick causes a stale "no knife" read right after we withdrew — + // the code then used to fall through and try the regular knife, which isn't in + // the bank either, and report a critical shortage on a knife that was in-flight. if (Rs2Bank.hasItem(InventoryUtils.FLETCHING_KNIFE_ID)) { Microbot.log("Withdrawing Fletching knife from bank (prioritized)"); if (Rs2Bank.withdrawOne(InventoryUtils.FLETCHING_KNIFE_ID)) { - Rs2Inventory.waitForInventoryChanges(3000); - if (InventoryUtils.hasKnife()) { + if (sleepUntil(InventoryUtils::hasKnife, 3000)) { Microbot.log("Successfully withdrew Fletching knife"); return true; } - } else { - Microbot.log("Failed to withdraw Fletching knife from bank"); + Microbot.log("Fletching knife withdrawal timed out"); + return false; } + Microbot.log("Failed to queue Fletching knife withdrawal"); } // Try to withdraw regular knife as fallback if (Rs2Bank.hasItem(InventoryUtils.KNIFE_ID)) { Microbot.log("Withdrawing regular knife from bank"); if (Rs2Bank.withdrawOne(InventoryUtils.KNIFE_ID)) { - Rs2Inventory.waitForInventoryChanges(3000); - if (InventoryUtils.hasKnife()) { + if (sleepUntil(InventoryUtils::hasKnife, 3000)) { Microbot.log("Successfully withdrew regular knife"); return true; } - } else { - Microbot.log("Failed to withdraw regular knife from bank"); + Microbot.log("Regular knife withdrawal timed out"); + return false; } + Microbot.log("Failed to queue regular knife withdrawal"); } // No knife found anywhere - critical error @@ -717,42 +716,49 @@ private static boolean ensureKnifeAndLogBasketInInventory(GameSession gameSessio return false; } - // Ensure we have a knife (prioritizing fletching knife) + // Ensure we have a knife (prioritizing fletching knife). Wait on the concrete + // predicate so a slow inventory update doesn't get misread as "no knife in bank". if (!InventoryUtils.hasKnife()) { if (Rs2Bank.hasItem(InventoryUtils.FLETCHING_KNIFE_ID)) { Microbot.log("Withdrawing Fletching knife from bank (prioritized)"); if (!Rs2Bank.withdrawOne(InventoryUtils.FLETCHING_KNIFE_ID)) { - Microbot.log("Failed to withdraw Fletching knife"); + Microbot.log("Failed to queue Fletching knife withdrawal"); return false; } } else if (Rs2Bank.hasItem(InventoryUtils.KNIFE_ID)) { Microbot.log("Withdrawing regular knife from bank"); if (!Rs2Bank.withdrawOne(InventoryUtils.KNIFE_ID)) { - Microbot.log("Failed to withdraw knife"); + Microbot.log("Failed to queue knife withdrawal"); return false; } } else { handleCriticalMaterialShortage(gameSession, "No knife available (checked inventory and bank for both Fletching knife and regular knife)"); return false; } - Rs2Inventory.waitForInventoryChanges(3000); + if (!sleepUntil(InventoryUtils::hasKnife, 3000)) { + Microbot.log("Knife withdrawal timed out"); + return false; + } } // Ensure we have a log basket if (!InventoryUtils.hasLogBasket()) { if (Rs2Bank.hasItem(InventoryUtils.LOG_BASKET_ID)) { if (!Rs2Bank.withdrawOne(InventoryUtils.LOG_BASKET_ID)) { - Microbot.log("Failed to withdraw log basket"); + Microbot.log("Failed to queue log basket withdrawal"); return false; } } else { handleCriticalMaterialShortage(gameSession, "No log basket available (checked inventory and bank)"); return false; } - Rs2Inventory.waitForInventoryChanges(3000); + if (!sleepUntil(InventoryUtils::hasLogBasket, 3000)) { + Microbot.log("Log basket withdrawal timed out"); + return false; + } } - return InventoryUtils.hasKnife() && InventoryUtils.hasLogBasket(); + return true; } catch (Exception e) { Microbot.log("Error ensuring knife and log basket in inventory: " + e.getMessage()); @@ -832,13 +838,18 @@ private static boolean performLogBasketFillingOperation(net.runelite.client.plug // Step 1: Take inventory full of logs (leaving space for knife and basket) int logsToWithdraw = InventoryUtils.getOptimalLogBasketLogAmountForExtendedRoute(gameSession) - InventoryUtils.getLogCount(); int logId = InventoryUtils.getLogId(); + int logsBeforeFirst = InventoryUtils.getLogCount(); if (!Rs2Bank.withdrawX(logId, logsToWithdraw)) { - Microbot.log("Failed to withdraw logs to fill inventory"); + Microbot.log("Failed to queue log withdrawal for basket-fill step"); return false; } - Rs2Inventory.waitForInventoryChanges(3000); + int expectedAfterFirst = logsBeforeFirst + logsToWithdraw; + if (!sleepUntil(() -> InventoryUtils.getLogCount() >= expectedAfterFirst, 3000)) { + Microbot.log("Log withdrawal timed out in basket-fill step"); + return false; + } // Step 2: Close bank Rs2Bank.closeBank(); @@ -861,11 +872,16 @@ private static boolean performLogBasketFillingOperation(net.runelite.client.plug int logsStillNeeded = InventoryUtils.getOptimalLogAmountForExtendedRoute(gameSession); if (logsStillNeeded > 0) { Microbot.log("Withdrawing additional " + logsStillNeeded + " logs for extended route"); + int logsBeforeSecond = InventoryUtils.getLogCount(); if (!Rs2Bank.withdrawX(logId, logsStillNeeded)) { - Microbot.log("Failed to withdraw additional logs"); + Microbot.log("Failed to queue additional log withdrawal"); + return false; + } + int expectedAfterSecond = logsBeforeSecond + logsStillNeeded; + if (!sleepUntil(() -> InventoryUtils.getLogCount() >= expectedAfterSecond, 3000)) { + Microbot.log("Additional log withdrawal timed out"); return false; } - Rs2Inventory.waitForInventoryChanges(3000); } Microbot.log("Log basket filling operation completed successfully"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/FletchingHandler.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/FletchingHandler.java index ad3c988511..4081f24135 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/FletchingHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/FletchingHandler.java @@ -295,19 +295,20 @@ public static boolean selectBow(int quantity) { } sleepGaussian(300,100); - - // Click the configured bow option using the mapper - if (config != null) { - int bowChildId = FletchingItemMapper.getFletchingInterfaceChildId(config.logType(), config.bowType()); - String description = FletchingItemMapper.getFletchingDescriptionWithShortcut(config.logType(), config.bowType()); - interactWithWidget(bowChildId, description); - Microbot.log("Selected " + FletchingItemMapper.getFletchingDescription(config.logType(), config.bowType())); - } else { - // Fallback to yew longbow - interactWithWidget(16, "Yew Longbow (u) (expected key: 3)"); - Microbot.log("Selected Yew Longbow (u) - fallback"); + + // Select the configured bow option by name — resolves the correct hotkey dynamically + // against the actual fletching interface layout, instead of assuming fixed child IDs. + // The previous child-ID scheme mapped SHORTBOW → child 15 with hotkey-index 1, but the + // sparse dynamic-children layout of widget 270,13 in skillmulti could produce "3" at + // that slot for some log types, so shortbow-selected was pressing the longbow key. + String bowAction = (config != null && config.bowType() == ValeTotemConfig.BowType.SHORTBOW) + ? "shortbow" : "longbow"; + if (!Rs2Widget.handleProcessingInterface(bowAction)) { + Microbot.log("Failed to select " + bowAction + " from fletching interface"); + return false; } - + Microbot.log("Selected " + bowAction); + sleepGaussian(200,100); return true;