From 896bdd1e1f71a480e386f0b9e1c0cc3358bc2c64 Mon Sep 17 00:00:00 2001 From: chsami Date: Tue, 18 Nov 2025 06:01:46 +0100 Subject: [PATCH 01/44] feat(breakhandler): implement Break Handler V2 with profile-based login and world selection --- .../breakhandlerv2/BreakHandlerV2Config.java | 262 +++++++ .../breakhandlerv2/BreakHandlerV2Overlay.java | 211 ++++++ .../breakhandlerv2/BreakHandlerV2Plugin.java | 111 +++ .../breakhandlerv2/BreakHandlerV2Script.java | 670 ++++++++++++++++++ .../breakhandlerv2/BreakHandlerV2State.java | 77 ++ 5 files changed, 1331 insertions(+) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Config.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Plugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2State.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Config.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Config.java new file mode 100644 index 00000000000..0d7f4986897 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Config.java @@ -0,0 +1,262 @@ +package net.runelite.client.plugins.microbot.breakhandler.breakhandlerv2; + +import net.runelite.client.config.*; +import net.runelite.client.plugins.microbot.util.world.RegionPreference; +import net.runelite.client.plugins.microbot.util.world.WorldSelectionMode; + +@ConfigGroup(BreakHandlerV2Config.configGroup) +public interface BreakHandlerV2Config extends Config { + String configGroup = "break-handler-v2"; + + // ========== BREAK TIMING SECTION ========== + @ConfigSection( + name = "Break Timing", + description = "Configure break timing and duration", + position = 0 + ) + String breakTimingSettings = "breakTimingSettings"; + + @ConfigItem( + keyName = "minPlaytime", + name = "Min Playtime (minutes)", + description = "Minimum time to play before taking a break", + position = 0, + section = breakTimingSettings + ) + @Range(min = 1, max = 600) + default int minPlaytime() { + return 45; + } + + @ConfigItem( + keyName = "maxPlaytime", + name = "Max Playtime (minutes)", + description = "Maximum time to play before taking a break", + position = 1, + section = breakTimingSettings + ) + @Range(min = 1, max = 600) + default int maxPlaytime() { + return 90; + } + + @ConfigItem( + keyName = "minBreakDuration", + name = "Min Break Duration (minutes)", + description = "Minimum break duration", + position = 2, + section = breakTimingSettings + ) + @Range(min = 1, max = 600) + default int minBreakDuration() { + return 5; + } + + @ConfigItem( + keyName = "maxBreakDuration", + name = "Max Break Duration (minutes)", + description = "Maximum break duration", + position = 3, + section = breakTimingSettings + ) + @Range(min = 1, max = 600) + default int maxBreakDuration() { + return 15; + } + + // ========== BREAK BEHAVIOR SECTION ========== + @ConfigSection( + name = "Break Behavior", + description = "Configure how breaks work", + position = 1 + ) + String breakBehaviorOptions = "breakBehaviorOptions"; + + @ConfigItem( + keyName = "logoutOnBreak", + name = "Logout on Break", + description = "Logout when taking a break", + position = 0, + section = breakBehaviorOptions + ) + default boolean logoutOnBreak() { + return true; + } + + @ConfigItem( + keyName = "safetyCheck", + name = "Safety Check", + description = "Wait until not in combat/interaction before breaking (tries up to 12 times over ~60 seconds)", + position = 1, + section = breakBehaviorOptions + ) + default boolean safetyCheck() { + return true; + } + + // ========== LOGIN & WORLD SECTION ========== + @ConfigSection( + name = "Login & World Selection", + description = "Configure login and world selection behavior", + position = 2 + ) + String loginWorldSettings = "loginWorldSettings"; + + @ConfigItem( + keyName = "autoLogin", + name = "Auto Login", + description = "Automatically log back in after break using profile data", + position = 0, + section = loginWorldSettings + ) + default boolean autoLogin() { + return true; + } + + @ConfigItem( + keyName = "worldSelectionMode", + name = "World Selection Mode", + description = "How to select worlds when logging back in", + position = 1, + section = loginWorldSettings + ) + default WorldSelectionMode worldSelectionMode() { + return WorldSelectionMode.CURRENT_PREFERRED_WORLD; + } + + @ConfigItem( + keyName = "regionPreference", + name = "Region Preference", + description = "Preferred region for world selection", + position = 2, + section = loginWorldSettings + ) + default RegionPreference regionPreference() { + return RegionPreference.ANY_REGION; + } + + @ConfigItem( + keyName = "avoidEmptyWorlds", + name = "Avoid Empty Worlds", + description = "Avoid worlds with very few players", + position = 3, + section = loginWorldSettings + ) + default boolean avoidEmptyWorlds() { + return true; + } + + @ConfigItem( + keyName = "avoidOvercrowdedWorlds", + name = "Avoid Crowded Worlds", + description = "Avoid worlds with too many players", + position = 4, + section = loginWorldSettings + ) + default boolean avoidOvercrowdedWorlds() { + return true; + } + + // ========== PROFILE SETTINGS SECTION ========== + @ConfigSection( + name = "Profile Settings", + description = "Profile and account management", + position = 3 + ) + String profileSettings = "profileSettings"; + + @ConfigItem( + keyName = "useActiveProfile", + name = "Use Active Profile", + description = "Use the currently active profile for login credentials", + position = 0, + section = profileSettings + ) + default boolean useActiveProfile() { + return true; + } + + @ConfigItem( + keyName = "respectMemberStatus", + name = "Respect Member Status", + description = "Use profile's member status to select appropriate worlds", + position = 1, + section = profileSettings + ) + default boolean respectMemberStatus() { + return true; + } + + // ========== NOTIFICATIONS SECTION ========== + @ConfigSection( + name = "Notifications", + description = "Discord webhook and notification settings", + position = 4 + ) + String notificationSettings = "notificationSettings"; + + @ConfigItem( + keyName = "enableDiscordWebhook", + name = "Enable Discord Webhook", + description = "Send notifications via Discord webhook from profile", + position = 0, + section = notificationSettings + ) + default boolean enableDiscordWebhook() { + return false; + } + + @ConfigItem( + keyName = "notifyOnBreakStart", + name = "Notify on Break Start", + description = "Send notification when break starts", + position = 1, + section = notificationSettings + ) + default boolean notifyOnBreakStart() { + return true; + } + + @ConfigItem( + keyName = "notifyOnBreakEnd", + name = "Notify on Break End", + description = "Send notification when break ends", + position = 2, + section = notificationSettings + ) + default boolean notifyOnBreakEnd() { + return true; + } + + @ConfigItem( + keyName = "notifyOnLoginFail", + name = "Notify on Login Failure", + description = "Send notification when login fails", + position = 3, + section = notificationSettings + ) + default boolean notifyOnLoginFail() { + return true; + } + + // ========== OVERLAY SETTINGS ========== + @ConfigItem( + keyName = "hideOverlay", + name = "Hide Overlay", + description = "Hide the break handler overlay", + position = 0 + ) + default boolean hideOverlay() { + return false; + } + + @ConfigItem( + keyName = "showDetailedInfo", + name = "Show Detailed Info", + description = "Show detailed information in overlay", + position = 1 + ) + default boolean showDetailedInfo() { + return true; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java new file mode 100644 index 00000000000..88aa9f72ff6 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java @@ -0,0 +1,211 @@ +package net.runelite.client.plugins.microbot.breakhandler.breakhandlerv2; + +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.config.ConfigProfile; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.security.LoginManager; +import net.runelite.client.ui.overlay.OverlayPanel; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; + +import javax.inject.Inject; +import java.awt.*; +import java.time.Duration; + +/** + * Overlay for Break Handler V2 + * Displays break status, timers, and profile information + */ +@Slf4j +public class BreakHandlerV2Overlay extends OverlayPanel { + + private final BreakHandlerV2Config config; + private final BreakHandlerV2Script script; + + @Inject + public BreakHandlerV2Overlay(BreakHandlerV2Config config, BreakHandlerV2Script script) { + super(); + this.config = config; + this.script = script; + System.out.println("[DEBUG] BreakHandlerV2Overlay created with script instance hash: " + System.identityHashCode(script)); + setPosition(OverlayPosition.TOP_LEFT); + } + + @Override + public Dimension render(Graphics2D graphics) { + try { + // Check if overlay should be hidden + if (config.hideOverlay()) { + return null; + } + + panelComponent.getChildren().clear(); + + // Title + panelComponent.getChildren().add(TitleComponent.builder() + .text("Break Handler V2") + .color(Color.CYAN) + .build()); + + // Current state + BreakHandlerV2State currentState = BreakHandlerV2State.getCurrentState(); + Color stateColor = getStateColor(currentState); + + panelComponent.getChildren().add(LineComponent.builder() + .left("Status:") + .right(currentState.getDescription()) + .rightColor(stateColor) + .build()); + + // Version + panelComponent.getChildren().add(LineComponent.builder() + .left("Version:") + .right(BreakHandlerV2Script.version) + .rightColor(Color.GRAY) + .build()); + + // Time until break or break remaining + if (currentState == BreakHandlerV2State.WAITING_FOR_BREAK) { + long secondsUntilBreak = script.getTimeUntilBreak(); + //if (secondsUntilBreak >= 0) { + String timeStr = formatDuration(secondsUntilBreak); + panelComponent.getChildren().add(LineComponent.builder() + .left("Next break:") + .right(timeStr) + .rightColor(Color.GREEN) + .build()); + //} + } else if (BreakHandlerV2State.isBreakActive()) { + long secondsRemaining = script.getBreakTimeRemaining(); + if (secondsRemaining >= 0) { + String timeStr = formatDuration(secondsRemaining); + panelComponent.getChildren().add(LineComponent.builder() + .left("Break ends:") + .right(timeStr) + .rightColor(Color.ORANGE) + .build()); + } + } + + // Show detailed info if enabled + if (config.showDetailedInfo()) { + // Profile information + ConfigProfile profile = LoginManager.getActiveProfile(); + if (profile != null) { + panelComponent.getChildren().add(LineComponent.builder() + .left("Profile:") + .right(profile.getName()) + .rightColor(Color.WHITE) + .build()); + + panelComponent.getChildren().add(LineComponent.builder() + .left("Member:") + .right(profile.isMember() ? "Yes" : "No") + .rightColor(profile.isMember() ? Color.YELLOW : Color.GRAY) + .build()); + } + + // World selection mode + panelComponent.getChildren().add(LineComponent.builder() + .left("World mode:") + .right(config.worldSelectionMode().name()) + .rightColor(Color.LIGHT_GRAY) + .build()); + + // Region preference + if (config.regionPreference() != null) { + panelComponent.getChildren().add(LineComponent.builder() + .left("Region:") + .right(config.regionPreference().name()) + .rightColor(Color.LIGHT_GRAY) + .build()); + } + + // Current world if logged in + if (Microbot.isLoggedIn()) { + int currentWorld = Microbot.getClient().getWorld(); + panelComponent.getChildren().add(LineComponent.builder() + .left("Current world:") + .right(String.valueOf(currentWorld)) + .rightColor(Color.GREEN) + .build()); + } + + // Break configuration + panelComponent.getChildren().add(LineComponent.builder() + .left("Break type:") + .right(config.logoutOnBreak() ? "Logout" : "Stay logged in") + .rightColor(Color.LIGHT_GRAY) + .build()); + + // Auto-login status + panelComponent.getChildren().add(LineComponent.builder() + .left("Auto-login:") + .right(config.autoLogin() ? "Enabled" : "Disabled") + .rightColor(config.autoLogin() ? Color.GREEN : Color.RED) + .build()); + + // Discord notifications + if (config.enableDiscordWebhook()) { + panelComponent.getChildren().add(LineComponent.builder() + .left("Discord:") + .right("Enabled") + .rightColor(Color.CYAN) + .build()); + } + } + + } catch (Exception ex) { + log.error("[BreakHandlerV2Overlay] Error rendering overlay", ex); + } + + return super.render(graphics); + } + + /** + * Get color for current state + */ + private Color getStateColor(BreakHandlerV2State state) { + switch (state) { + case WAITING_FOR_BREAK: + return Color.GREEN; + case BREAK_REQUESTED: + case INITIATING_BREAK: + return Color.YELLOW; + case LOGOUT_REQUESTED: + case LOGGED_OUT: + return Color.ORANGE; + case LOGIN_REQUESTED: + case LOGGING_IN: + return Color.CYAN; + case LOGIN_EXTENDED_SLEEP: + return Color.RED; + case BREAK_ENDING: + return Color.LIGHT_GRAY; + case PROFILE_SWITCHING: + return Color.MAGENTA; + default: + return Color.WHITE; + } + } + + /** + * Format duration in seconds to human-readable string + */ + private String formatDuration(long seconds) { + Duration duration = Duration.ofSeconds(seconds); + + long hours = duration.toHours(); + long minutes = duration.toMinutesPart(); + long secs = duration.toSecondsPart(); + + if (hours > 0) { + return String.format("%dh %dm %ds", hours, minutes, secs); + } else if (minutes > 0) { + return String.format("%dm %ds", minutes, secs); + } else { + return String.format("%ds", secs); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Plugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Plugin.java new file mode 100644 index 00000000000..0d14ee15370 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Plugin.java @@ -0,0 +1,111 @@ +package net.runelite.client.plugins.microbot.breakhandler.breakhandlerv2; + +import com.google.inject.Provides; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.events.GameStateChanged; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; + +import javax.inject.Inject; +import java.awt.*; + +/** + * Break Handler V2 Plugin + * Enhanced break handler with profile-based auto-login and intelligent world selection + * + * Features: + * - Automatic login using profile data (username, password, member status) + * - Intelligent world selection based on multiple modes: + * * Current/Preferred world + * * Random accessible world + * * Regional selection + * * Best population balance + * * Best ping performance + * - Configurable break timing (min/max playtime and break duration) + * - Optional in-game breaks (pause scripts without logout) + * - Safety checks (waits for combat/interaction to end) + * - Discord webhook notifications + * - Profile-aware member/F2P world selection + * - Retry mechanism with configurable attempts and delays + * - In-game overlay showing break status and timers + * + * @version 2.0.0 + */ +@PluginDescriptor( + name = PluginDescriptor.Default + "BreakHandler V2", + description = "Advanced break handler with profile-based login and world selection", + tags = {"break", "microbot", "breakhandler", "login", "world", "profile", "v2"}, + enabledByDefault = false +) +@Slf4j +public class BreakHandlerV2Plugin extends Plugin { + + @Inject + private BreakHandlerV2Config config; + + @Inject + private BreakHandlerV2Script script; + + @Inject + private OverlayManager overlayManager; + + @Inject + private BreakHandlerV2Overlay overlay; + + @Provides + BreakHandlerV2Config provideConfig(ConfigManager configManager) { + return configManager.getConfig(BreakHandlerV2Config.class); + } + + @Override + protected void startUp() throws AWTException { + log.info("[BreakHandlerV2] Plugin starting up"); + System.out.println("[DEBUG] BreakHandlerV2Plugin.startUp() with script instance hash: " + System.identityHashCode(script)); + + // Add in-game overlay + if (overlayManager != null && overlay != null) { + overlayManager.add(overlay); + log.info("[BreakHandlerV2] In-game overlay added"); + } + + // Start the script + if (script != null) { + script.run(config); + log.info("[BreakHandlerV2] Script started"); + } + + log.info("[BreakHandlerV2] Plugin started successfully (v{})", BreakHandlerV2Script.version); + } + + @Override + protected void shutDown() { + log.info("[BreakHandlerV2] Plugin shutting down"); + + // Shutdown script + if (script != null) { + script.shutdown(); + log.info("[BreakHandlerV2] Script stopped"); + } + + // Remove in-game overlay + if (overlayManager != null && overlay != null) { + overlayManager.remove(overlay); + log.info("[BreakHandlerV2] In-game overlay removed"); + } + + log.info("[BreakHandlerV2] Plugin shut down successfully"); + } + + /** + * Handle game state changes + * Can be used to detect unexpected logouts or other state changes + */ + @Subscribe + public void onGameStateChanged(GameStateChanged event) { + // Future implementation: detect unexpected logouts, bans, etc. + log.debug("[BreakHandlerV2] Game state changed: {}", event.getGameState()); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java new file mode 100644 index 00000000000..72a6cad8a33 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java @@ -0,0 +1,670 @@ +package net.runelite.client.plugins.microbot.breakhandler.breakhandlerv2; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.GameState; +import net.runelite.client.config.ConfigProfile; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.util.discord.Rs2Discord; +import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.security.Login; +import net.runelite.client.plugins.microbot.util.security.LoginManager; +import net.runelite.client.plugins.microbot.util.world.Rs2WorldUtil; +import net.runelite.http.api.worlds.WorldRegion; + +import javax.inject.Singleton; +import java.awt.Color; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.TimeUnit; + +/** + * Break Handler V2 Script + * Enhanced break handler with profile-based login and intelligent world selection + * Version: 2.0.0 + */ +@Singleton +@Slf4j +public class BreakHandlerV2Script extends Script { + + // Instance tracking for debugging + private static int instanceCounter = 0; + private final int instanceId; + + public BreakHandlerV2Script() { + instanceId = ++instanceCounter; + System.out.println("[DEBUG] BreakHandlerV2Script instance #" + instanceId + " created. Hash: " + System.identityHashCode(this)); + } + + @Getter + private BreakHandlerV2Config config; + + // Timing variables (volatile for thread visibility from overlay/UI threads) + private volatile Instant nextBreakTime; + private volatile Instant breakEndTime; + private volatile Instant loginAttemptTime; + + // State tracking + private int loginRetryCount = 0; + private int safetyCheckAttempts = 0; + private int preBreakWorld = -1; + private ConfigProfile activeProfile; + private boolean unexpectedLogoutDetected = false; + + // Break duration in milliseconds + private long currentBreakDuration = 0; + + // Login retry backoff constants + private static final int MAX_LOGIN_ATTEMPTS = 10; + private static final int INITIAL_FAST_RETRIES = 3; + private static final int BACKOFF_BASE_DELAY_MS = 30000; // 30 seconds + + // Safety check backoff constants + private static final int MAX_SAFETY_CHECK_ATTEMPTS = 60; + private static final int SAFETY_CHECK_DELAY_MS = 5000; // 5 seconds between checks + + public static String version = "2.0.0"; + + /** + * Run the break handler script + */ + public boolean run(BreakHandlerV2Config config) { + System.out.println("[DEBUG] run() called on instance #" + instanceId + " (hash: " + System.identityHashCode(this) + ")"); + this.config = config; + BreakHandlerV2State.setState(BreakHandlerV2State.WAITING_FOR_BREAK); + + // Initialize next break time immediately to prevent null values in overlay + scheduleNextBreak(); + log.info("[BreakHandlerV2] Initial break scheduled for {}", nextBreakTime); + + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { + try { + if (!super.run()) return; + + // Load active profile + loadActiveProfile(); + + // Detect unexpected logout while waiting for break + detectUnexpectedLogout(); + + // Main state machine + switch (BreakHandlerV2State.getCurrentState()) { + case WAITING_FOR_BREAK: + handleWaitingForBreak(); + break; + case BREAK_REQUESTED: + handleBreakRequested(); + break; + case INITIATING_BREAK: + handleInitiatingBreak(); + break; + case LOGOUT_REQUESTED: + handleLogoutRequested(); + break; + case LOGGED_OUT: + handleLoggedOut(); + break; + case LOGIN_REQUESTED: + handleLoginRequested(); + break; + case LOGGING_IN: + handleLoggingIn(); + break; + case LOGIN_EXTENDED_SLEEP: + handleLoginExtendedSleep(); + break; + case BREAK_ENDING: + handleBreakEnding(); + break; + case PROFILE_SWITCHING: + handleProfileSwitching(); + break; + } + + } catch (Exception ex) { + log.error("[BreakHandlerV2] Error in main loop", ex); + } + }, 0, 1000, TimeUnit.MILLISECONDS); + + return true; + } + + /** + * Load the active profile from config manager + */ + private void loadActiveProfile() { + if (config.useActiveProfile()) { + try { + activeProfile = Microbot.getConfigManager().getProfile(); + if (activeProfile != null) { + LoginManager.setActiveProfile(activeProfile); + } + } catch (Exception ex) { + log.error("[BreakHandlerV2] Failed to load active profile", ex); + } + } + } + + /** + * Handle WAITING_FOR_BREAK state + * Schedules next break and monitors for break time + */ + private void handleWaitingForBreak() { + // Check if it's time for a break + if (nextBreakTime != null && Instant.now().isAfter(nextBreakTime)) { + log.info("[BreakHandlerV2] Break time reached, requesting break"); + transitionToState(BreakHandlerV2State.BREAK_REQUESTED); + } + } + + /** + * Handle BREAK_REQUESTED state + * Initiates break based on configuration + */ + private void handleBreakRequested() { + // If breakEndTime is already set, we're in a no-logout break waiting for it to end + if (breakEndTime != null) { + // Check if break is over + if (Instant.now().isAfter(breakEndTime)) { + log.info("[BreakHandlerV2] No-logout break ended"); + Microbot.pauseAllScripts.set(false); + transitionToState(BreakHandlerV2State.BREAK_ENDING); + } + return; + } + + // Store current world before break + if (Microbot.isLoggedIn()) { + preBreakWorld = Microbot.getClient().getWorld(); + } + + if (config.logoutOnBreak()) { + log.info("[BreakHandlerV2] Starting break (with logout)"); + transitionToState(BreakHandlerV2State.INITIATING_BREAK); + } else { + log.info("[BreakHandlerV2] Starting break (no logout - scripts paused)"); + currentBreakDuration = calculateBreakDuration(); + breakEndTime = Instant.now().plus(currentBreakDuration, ChronoUnit.MILLIS); + + sendDiscordNotification("Break Started", + "Duration: " + (currentBreakDuration / 60000) + " minutes (no logout)"); + + // Pause all scripts and stay in this state until break ends + Microbot.pauseAllScripts.set(true); + } + } + + /** + * Handle INITIATING_BREAK state + * Performs safety checks before logout with backoff retry + */ + private void handleInitiatingBreak() { + if (!Microbot.isLoggedIn()) { + log.info("[BreakHandlerV2] Already logged out, transitioning to LOGGED_OUT"); + currentBreakDuration = calculateBreakDuration(); + breakEndTime = Instant.now().plus(currentBreakDuration, ChronoUnit.MILLIS); + safetyCheckAttempts = 0; // Reset counter + transitionToState(BreakHandlerV2State.LOGGED_OUT); + return; + } + + // Safety check if enabled + if (config.safetyCheck()) { + boolean isInCombat = Rs2Player.isInCombat(); + boolean isInteracting = Rs2Player.isInteracting(); + + if (isInCombat || isInteracting) { + safetyCheckAttempts++; + + if (safetyCheckAttempts >= MAX_SAFETY_CHECK_ATTEMPTS) { + log.warn("[BreakHandlerV2] Safety check max attempts ({}) reached, forcing break", + MAX_SAFETY_CHECK_ATTEMPTS); + + String unsafeReason = isInCombat && isInteracting ? "in combat and interacting" + : isInCombat ? "in combat" + : "interacting"; + + sendDiscordNotification("Safety Check Failed", + "Failed to achieve safe conditions after " + MAX_SAFETY_CHECK_ATTEMPTS + " attempts.\n" + + "Player still " + unsafeReason + ".\n" + + "Forcing break anyway."); + + safetyCheckAttempts = 0; // Reset counter + } else { + log.debug("[BreakHandlerV2] Waiting for safe conditions... (attempt {}/{})", + safetyCheckAttempts, MAX_SAFETY_CHECK_ATTEMPTS); + sleep(SAFETY_CHECK_DELAY_MS); + return; // Stay in this state and check again + } + } else { + // Safe conditions met + if (safetyCheckAttempts > 0) { + log.info("[BreakHandlerV2] Safe conditions achieved after {} attempts", safetyCheckAttempts); + } + safetyCheckAttempts = 0; // Reset counter + } + } + + // Proceed to logout + currentBreakDuration = calculateBreakDuration(); + breakEndTime = Instant.now().plus(currentBreakDuration, ChronoUnit.MILLIS); + + sendDiscordNotification("Break Started", + "Type: Logout break\nDuration: " + (currentBreakDuration / 60000) + " minutes"); + + transitionToState(BreakHandlerV2State.LOGOUT_REQUESTED); + } + + /** + * Handle LOGOUT_REQUESTED state + * Performs logout + */ + private void handleLogoutRequested() { + if (!Microbot.isLoggedIn()) { + log.info("[BreakHandlerV2] Logout successful"); + transitionToState(BreakHandlerV2State.LOGGED_OUT); + return; + } + + try { + log.info("[BreakHandlerV2] Attempting logout..."); + Rs2Player.logout(); + sleep(2000, 3000); + } catch (Exception ex) { + log.error("[BreakHandlerV2] Error during logout", ex); + } + } + + /** + * Handle LOGGED_OUT state + * Waits for break duration to complete + */ + private void handleLoggedOut() { + if (breakEndTime == null) { + log.error("[BreakHandlerV2] Break end time not set, resetting"); + transitionToState(BreakHandlerV2State.WAITING_FOR_BREAK); + return; + } + + // Check if break is over + if (Instant.now().isAfter(breakEndTime)) { + if (config.autoLogin()) { + log.info("[BreakHandlerV2] Break ended, requesting login"); + loginRetryCount = 0; + transitionToState(BreakHandlerV2State.LOGIN_REQUESTED); + } else { + log.info("[BreakHandlerV2] Break ended, auto-login disabled"); + sendDiscordNotification("Break Ended", "Auto-login is disabled"); + transitionToState(BreakHandlerV2State.BREAK_ENDING); + } + } + } + + /** + * Handle LOGIN_REQUESTED state + * Initiates login with profile data and world selection + * Uses exponential backoff: first 3 attempts are fast, then 30s incremental delays + */ + private void handleLoginRequested() { + // Check if already logged in + if (Microbot.isLoggedIn()) { + log.info("[BreakHandlerV2] Already logged in"); + transitionToState(BreakHandlerV2State.BREAK_ENDING); + return; + } + + // Check retry limit (max 10 attempts) + if (loginRetryCount >= MAX_LOGIN_ATTEMPTS) { + log.error("[BreakHandlerV2] Max login attempts ({}) reached", MAX_LOGIN_ATTEMPTS); + sendDiscordNotification("Login Failed", + "Max login attempts (" + MAX_LOGIN_ATTEMPTS + ") reached. Giving up."); + transitionToState(BreakHandlerV2State.LOGIN_EXTENDED_SLEEP); + return; + } + + // Validate profile + if (activeProfile == null) { + log.error("[BreakHandlerV2] No active profile available for login"); + sendDiscordNotification("Login Failed", "No active profile available"); + transitionToState(BreakHandlerV2State.WAITING_FOR_BREAK); + return; + } + + // Apply backoff delay if needed (after first 3 attempts) + if (loginRetryCount >= INITIAL_FAST_RETRIES) { + int backoffDelay = calculateLoginBackoffDelay(loginRetryCount); + log.info("[BreakHandlerV2] Applying backoff delay: {} seconds", backoffDelay / 1000); + sleep(backoffDelay); + } else if (loginRetryCount > 0) { + // Small delay between initial fast retries (5 seconds) + sleep(5000); + } + + // Select world based on configuration + int targetWorld = selectWorld(); + + if (targetWorld == -1) { + log.error("[BreakHandlerV2] Failed to select valid world"); + loginRetryCount++; + return; + } + + log.info("[BreakHandlerV2] Attempting login to world {} (attempt {}/{})", + targetWorld, loginRetryCount + 1, MAX_LOGIN_ATTEMPTS); + + // Perform login + boolean loginInitiated = LoginManager.login( + activeProfile.getName(), + activeProfile.getPassword(), + targetWorld + ); + + if (loginInitiated) { + loginRetryCount++; + loginAttemptTime = Instant.now(); + transitionToState(BreakHandlerV2State.LOGGING_IN); + } else { + log.error("[BreakHandlerV2] Failed to initiate login"); + loginRetryCount++; + } + } + + /** + * Calculate exponential backoff delay for login retries + * First 3 attempts: 5s delay + * After that: 30s, 60s, 90s, 120s, etc. + */ + private int calculateLoginBackoffDelay(int attemptCount) { + if (attemptCount < INITIAL_FAST_RETRIES) { + return 5000; // 5 seconds for initial retries + } + // Exponential backoff: 30s * (attempt - 3) + int backoffMultiplier = attemptCount - INITIAL_FAST_RETRIES + 1; + return BACKOFF_BASE_DELAY_MS * backoffMultiplier; + } + + /** + * Handle LOGGING_IN state + * Monitors login progress + */ + private void handleLoggingIn() { + // Check if logged in + if (Microbot.isLoggedIn()) { + log.info("[BreakHandlerV2] Login successful"); + sendDiscordNotification("Login Successful", + "Logged into world " + Microbot.getClient().getWorld()); + transitionToState(BreakHandlerV2State.BREAK_ENDING); + return; + } + + // Check for timeout (60 seconds) + if (loginAttemptTime != null && + Instant.now().isAfter(loginAttemptTime.plusSeconds(60))) { + log.warn("[BreakHandlerV2] Login timeout, retrying"); + transitionToState(BreakHandlerV2State.LOGIN_REQUESTED); + } + } + + /** + * Handle LOGIN_EXTENDED_SLEEP state + * Extended wait after multiple failed login attempts + */ + private void handleLoginExtendedSleep() { + log.info("[BreakHandlerV2] Entering extended sleep (5 minutes)"); + sleep(300000); // 5 minutes + loginRetryCount = 0; + transitionToState(BreakHandlerV2State.WAITING_FOR_BREAK); + } + + /** + * Handle BREAK_ENDING state + * Finalizes break and schedules next break + */ + private void handleBreakEnding() { + log.info("[BreakHandlerV2] Break cycle complete"); + + // Reset variables + breakEndTime = null; + loginAttemptTime = null; + loginRetryCount = 0; + safetyCheckAttempts = 0; + preBreakWorld = -1; + unexpectedLogoutDetected = false; + + // Unpause scripts + Microbot.pauseAllScripts.set(false); + + // Schedule next break + scheduleNextBreak(); + + sendDiscordNotification("Break Ended", + "Next break scheduled for " + nextBreakTime); + + transitionToState(BreakHandlerV2State.WAITING_FOR_BREAK); + } + + /** + * Handle PROFILE_SWITCHING state + * Future implementation for multi-account support + */ + private void handleProfileSwitching() { + // Placeholder for future profile switching functionality + log.info("[BreakHandlerV2] Profile switching not yet implemented"); + transitionToState(BreakHandlerV2State.WAITING_FOR_BREAK); + } + + /** + * Detect unexpected logout (kicked, disconnected, etc.) + * Handles case where player is logged out while waiting for a scheduled break + */ + private void detectUnexpectedLogout() { + // Only check if we're in WAITING_FOR_BREAK state and have a scheduled break + if (BreakHandlerV2State.getCurrentState() != BreakHandlerV2State.WAITING_FOR_BREAK) { + unexpectedLogoutDetected = false; // Reset flag when not in WAITING_FOR_BREAK + return; + } + + if (nextBreakTime == null) { + return; + } + + // Reset flag when player is logged in + if (Microbot.isLoggedIn()) { + unexpectedLogoutDetected = false; + return; + } + + // Check if player is logged out unexpectedly + if (!Microbot.isLoggedIn() && !unexpectedLogoutDetected) { + long secondsUntilBreak = Instant.now().until(nextBreakTime, ChronoUnit.SECONDS); + + if (secondsUntilBreak > 0) { + log.warn("[BreakHandlerV2] Unexpected logout detected with {}s until scheduled break", secondsUntilBreak); + unexpectedLogoutDetected = true; // Prevent repeated detections + + // Handle based on configuration + if (config.autoLogin()) { + log.info("[BreakHandlerV2] Auto-login enabled, attempting to log back in"); + loginRetryCount = 0; + transitionToState(BreakHandlerV2State.LOGIN_REQUESTED); + } else { + log.info("[BreakHandlerV2] Auto-login disabled, pausing break timer"); + // Keep the state as WAITING_FOR_BREAK but don't count time while logged out + // The timer will resume when player logs back in manually + sendDiscordNotification("Unexpected Logout", + "Player logged out with " + (secondsUntilBreak / 60) + " minutes until break.\nAuto-login is disabled."); + } + } + } + } + + /** + * Select world based on configuration and profile + */ + private int selectWorld() { + boolean membersOnly = config.respectMemberStatus() && + activeProfile != null && + activeProfile.isMember(); + + WorldRegion region = config.regionPreference().getWorldRegion(); + + int targetWorld = -1; + + switch (config.worldSelectionMode()) { + case CURRENT_PREFERRED_WORLD: + targetWorld = preBreakWorld != -1 ? preBreakWorld : + Rs2WorldUtil.getRandomAccessibleWorld( + config.avoidEmptyWorlds(), + config.avoidOvercrowdedWorlds(), + membersOnly + ); + break; + + case RANDOM_WORLD: + targetWorld = Rs2WorldUtil.getRandomAccessibleWorld( + config.avoidEmptyWorlds(), + config.avoidOvercrowdedWorlds(), + membersOnly + ); + break; + + case REGIONAL_RANDOM: + targetWorld = Rs2WorldUtil.getRandomAccessibleWorldFromRegion( + region, + config.avoidEmptyWorlds(), + config.avoidOvercrowdedWorlds(), + membersOnly + ); + break; + + case BEST_POPULATION: + targetWorld = Rs2WorldUtil.getBestAccessibleWorldForLogin( + false, // by population, not ping + region, + config.avoidEmptyWorlds(), + config.avoidOvercrowdedWorlds(), + membersOnly + ); + break; + + case BEST_PING: + targetWorld = Rs2WorldUtil.getBestAccessibleWorldForLogin( + true, // by ping + region, + config.avoidEmptyWorlds(), + config.avoidOvercrowdedWorlds(), + membersOnly + ); + break; + } + + log.info("[BreakHandlerV2] Selected world: {} (mode: {}, members: {})", + targetWorld, config.worldSelectionMode(), membersOnly); + + return targetWorld; + } + + /** + * Schedule the next break + */ + private void scheduleNextBreak() { + int minMinutes = config.minPlaytime(); + int maxMinutes = config.maxPlaytime(); + + int playtimeMinutes = Rs2Random.between(minMinutes, maxMinutes); + nextBreakTime = Instant.now().plus(playtimeMinutes, ChronoUnit.MINUTES); + + log.info("[BreakHandlerV2] Next break in {} minutes", playtimeMinutes); + } + + /** + * Calculate break duration + */ + private long calculateBreakDuration() { + int minMinutes = config.minBreakDuration(); + int maxMinutes = config.maxBreakDuration(); + + int breakMinutes = Rs2Random.between(minMinutes, maxMinutes); + log.info("[BreakHandlerV2] Break duration: {} minutes", breakMinutes); + + return breakMinutes * 60000L; // Convert to milliseconds + } + + /** + * Transition to a new state + */ + private void transitionToState(BreakHandlerV2State newState) { + BreakHandlerV2State oldState = BreakHandlerV2State.getCurrentState(); + log.info("[BreakHandlerV2] State transition: {} -> {}", oldState, newState); + BreakHandlerV2State.setState(newState); + } + + /** + * Send Discord notification if enabled + */ + private void sendDiscordNotification(String title, String message) { + if (!config.enableDiscordWebhook()) { + return; + } + + if (activeProfile == null || activeProfile.getDiscordWebhookUrl() == null) { + return; + } + + try { + String playerName = activeProfile.getName(); + Rs2Discord.sendCustomNotification( + title, + message, + Rs2Discord.convertColorToInt(Color.CYAN), + playerName != null ? playerName : "Unknown", + "BreakHandler V2" + ); + } catch (Exception ex) { + log.error("[BreakHandlerV2] Failed to send Discord notification", ex); + } + } + + /** + * Get time until next break in seconds + */ + public long getTimeUntilBreak() { + System.out.println("[DEBUG] getTimeUntilBreak() called on instance #" + instanceId + " (hash: " + System.identityHashCode(this) + "), nextBreakTime: " + nextBreakTime); + if (nextBreakTime == null) { + return -1; + } + return Instant.now().until(nextBreakTime, ChronoUnit.SECONDS); + } + + /** + * Get time remaining in break in seconds + */ + public long getBreakTimeRemaining() { + if (breakEndTime == null) { + return -1; + } + return Instant.now().until(breakEndTime, ChronoUnit.SECONDS); + } + + @Override + public void shutdown() { + super.shutdown(); + log.info("[BreakHandlerV2] Shutting down"); + + // Reset state + BreakHandlerV2State.setState(BreakHandlerV2State.WAITING_FOR_BREAK); + Microbot.pauseAllScripts.set(false); + + // Clear timers + nextBreakTime = null; + breakEndTime = null; + loginAttemptTime = null; + + // Reset counters and flags + unexpectedLogoutDetected = false; + loginRetryCount = 0; + safetyCheckAttempts = 0; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2State.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2State.java new file mode 100644 index 00000000000..8fb6626d47c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2State.java @@ -0,0 +1,77 @@ +package net.runelite.client.plugins.microbot.breakhandler.breakhandlerv2; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * State machine for Break Handler V2 + * Manages all states and transitions for the break system + */ +@Getter +@RequiredArgsConstructor +public enum BreakHandlerV2State { + WAITING_FOR_BREAK("Waiting for break"), + BREAK_REQUESTED("Break requested"), + INITIATING_BREAK("Initiating break"), + LOGOUT_REQUESTED("Logout requested"), + LOGGED_OUT("Logged out"), + LOGIN_REQUESTED("Login requested"), + LOGGING_IN("Logging in"), + LOGIN_EXTENDED_SLEEP("Login extended sleep"), + BREAK_ENDING("Break ending"), + PROFILE_SWITCHING("Switching profile"); + + private final String description; + + private static final AtomicReference currentState = + new AtomicReference<>(WAITING_FOR_BREAK); + + /** + * Get the current state (thread-safe) + */ + public static BreakHandlerV2State getCurrentState() { + return currentState.get(); + } + + /** + * Set the current state (thread-safe) + */ + public static void setState(BreakHandlerV2State newState) { + currentState.set(newState); + } + + /** + * Check if break is currently active + */ + public static boolean isBreakActive() { + BreakHandlerV2State state = getCurrentState(); + return state == BREAK_REQUESTED || + state == INITIATING_BREAK || + state == LOGOUT_REQUESTED || + state == LOGGED_OUT || + state == LOGIN_REQUESTED || + state == LOGGING_IN || + state == LOGIN_EXTENDED_SLEEP || + state == BREAK_ENDING || + state == PROFILE_SWITCHING; + } + + /** + * Check if this is a lock state that prevents new breaks + */ + public boolean isLockState() { + return this == BREAK_REQUESTED || + this == INITIATING_BREAK || + this == LOGOUT_REQUESTED || + this == LOGGING_IN || + this == BREAK_ENDING || + this == PROFILE_SWITCHING; + } + + @Override + public String toString() { + return description; + } +} From 6e63403d2edc88b86f6de425d40167df143ac8f2 Mon Sep 17 00:00:00 2001 From: EGxo Date: Sat, 22 Nov 2025 14:02:23 -0800 Subject: [PATCH 02/44] skill calc: add vale totem actions --- .../skills/FletchingAction.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/FletchingAction.java b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/FletchingAction.java index e08c2a29514..0d3de30c7ca 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/FletchingAction.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/FletchingAction.java @@ -52,6 +52,14 @@ public enum FletchingAction implements ItemSkillAction IRON_JAVELIN(ItemID.IRON_JAVELIN, 17, 2), OAK_SHORTBOW(ItemID.OAK_SHORTBOW, 20, 16.5f), OAK_SHORTBOW_U(ItemID.UNSTRUNG_OAK_SHORTBOW, 20, 16.5f), + OAK_VALE_TOTEM(ItemID.ENT_TOTEMS_LOOT, 20, 254.8f) + { + @Override + public String getName(final ItemManager itemManager) + { + return "Oak vale totem"; + } + }, IRON_DART(ItemID.IRON_DART, 22, 3.8f), BLURITE_CROSSBOW(ItemID.XBOWS_CROSSBOW_BLURITE, 24, 16), OAK_STOCK(ItemID.XBOWS_CROSSBOW_STOCK_OAK, 24, 16), @@ -64,6 +72,14 @@ public enum FletchingAction implements ItemSkillAction STEEL_JAVELIN(ItemID.STEEL_JAVELIN, 32, 5), WILLOW_SHORTBOW(ItemID.WILLOW_SHORTBOW, 35, 33.3f), WILLOW_SHORTBOW_U(ItemID.UNSTRUNG_WILLOW_SHORTBOW, 35, 33.3f), + WILLOW_VALE_TOTEM(ItemID.ENT_TOTEMS_LOOT, 35, 634.4f) + { + @Override + public String getName(final ItemManager itemManager) + { + return "Willow vale totem"; + } + }, STEEL_DART(ItemID.STEEL_DART, 37, 7.5f), IRON_BOLTS(ItemID.XBOWS_CROSSBOW_BOLTS_IRON, 39, 1.5f), IRON_CROSSBOW(ItemID.XBOWS_CROSSBOW_IRON, 39, 22), @@ -84,9 +100,18 @@ public enum FletchingAction implements ItemSkillAction MITHRIL_JAVELIN(ItemID.MITHRIL_JAVELIN, 47, 8), MAPLE_SHORTBOW(ItemID.MAPLE_SHORTBOW, 50, 50), MAPLE_SHORTBOW_U(ItemID.UNSTRUNG_MAPLE_SHORTBOW, 50, 50), + MAPLE_VALE_TOTEM(ItemID.ENT_TOTEMS_LOOT, 50, 1007.2f) + { + @Override + public String getName(final ItemManager itemManager) + { + return "Maple vale totem"; + } + }, BARBED_BOLTS(ItemID.BARBED_BOLT, 51, 9.5f), BROAD_ARROWS(ItemID.SLAYERGUIDE_BROAD_ARROWS, 52, 10), MITHRIL_DART(ItemID.MITHRIL_DART, 52, 11.2f), + GREENMAN_STATUE(ItemID.GREENMAN_STATUE, 53, 55), MITHRIL_BOLTS(ItemID.XBOWS_CROSSBOW_BOLTS_MITHRIL, 54, 5), MAPLE_STOCK(ItemID.XBOWS_CROSSBOW_STOCK_MAPLE, 54, 32), MITHRIL_CROSSBOW(ItemID.XBOWS_CROSSBOW_MITHRIL, 54, 32), @@ -108,6 +133,14 @@ public enum FletchingAction implements ItemSkillAction DIAMOND_BOLTS(ItemID.XBOWS_CROSSBOW_BOLTS_ADAMANTITE_TIPPED_DIAMOND, 65, 7), YEW_SHORTBOW(ItemID.YEW_SHORTBOW, 65, 67.5f), YEW_SHORTBOW_U(ItemID.UNSTRUNG_YEW_SHORTBOW, 65, 67.5f), + YEW_VALE_TOTEM(ItemID.ENT_TOTEMS_LOOT, 65, 1635.2f) + { + @Override + public String getName(final ItemManager itemManager) + { + return "Yew vale totem"; + } + }, ADAMANT_DART(ItemID.ADAMANT_DART, 67, 15), RUNITE_BOLTS(ItemID.XBOWS_CROSSBOW_BOLTS_RUNITE, 69, 10), RUNE_CROSSBOW(ItemID.XBOWS_CROSSBOW_RUNITE, 69, 50), @@ -125,8 +158,24 @@ public enum FletchingAction implements ItemSkillAction MAGIC_STOCK(ItemID.XBOWS_CROSSBOW_STOCK_MAGIC, 78, 70), TOXIC_BLOWPIPE(ItemID.TOXIC_BLOWPIPE_LOADED, 78, 120), DRAGON_CROSSBOW_U(ItemID.XBOWS_CROSSBOW_UNSTRUNG_DRAGON, 78, 135), + GREENMAN_CARVING(ItemID.GREENMAN_WALL_DECORATION, 79, 70) + { + @Override + public String getName(final ItemManager itemManager) + { + return "Greenman carving"; + } + }, MAGIC_SHORTBOW(ItemID.MAGIC_SHORTBOW, 80, 83.3f), MAGIC_SHORTBOW_U(ItemID.UNSTRUNG_MAGIC_SHORTBOW, 80, 83.3f), + MAGIC_VALE_TOTEM(ItemID.ENT_TOTEMS_LOOT, 80, 3103.6f) + { + @Override + public String getName(final ItemManager itemManager) + { + return "Magic vale totem"; + } + }, RUNE_DART(ItemID.RUNE_DART, 81, 18.8f), AMETHYST_ARROW(ItemID.AMETHYST_ARROW, 82, 13.5f), DRAGON_BOLTS(ItemID.DRAGON_BOLTS_UNFEATHERED, 84, 12), @@ -134,8 +183,17 @@ public enum FletchingAction implements ItemSkillAction MAGIC_LONGBOW(ItemID.MAGIC_LONGBOW, 85, 91.5f), MAGIC_LONGBOW_U(ItemID.UNSTRUNG_MAGIC_LONGBOW, 85, 91.5f), MAGIC_SHIELD(ItemID.MAGIC_SHIELD, 87, 183), + REDWOOD_HIKING_STAFF(ItemID.REDWOOD_HIKING_STAFF, 90, 10.5f), DRAGON_ARROW(ItemID.DRAGON_ARROW, 90, 15), AMETHYST_DART(ItemID.AMETHYST_DART, 90, 21), + REDWOOD_VALE_TOTEM(ItemID.ENT_TOTEMS_LOOT, 90, 3787.2f) + { + @Override + public String getName(final ItemManager itemManager) + { + return "Redwood vale totem"; + } + }, DRAGON_JAVELIN(ItemID.DRAGON_JAVELIN, 92, 15), REDWOOD_SHIELD(ItemID.REDWOOD_SHIELD, 92, 216), DRAGON_DART(ItemID.DRAGON_DART, 95, 25), From b8afd2c422f0f68da5d1c963347de4c76412be48 Mon Sep 17 00:00:00 2001 From: EGxo Date: Sat, 22 Nov 2025 14:03:29 -0800 Subject: [PATCH 03/44] skill calc: add atlatl darts --- .../plugins/skillcalculator/skills/FletchingAction.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/FletchingAction.java b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/FletchingAction.java index 0d3de30c7ca..31ffc5edcbe 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/FletchingAction.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/FletchingAction.java @@ -151,6 +151,10 @@ public String getName(final ItemManager itemManager) DRAGONSTONE_BOLTS(ItemID.XBOWS_CROSSBOW_BOLTS_RUNITE_TIPPED_DRAGONSTONE, 71, 8.2f), YEW_SHIELD(ItemID.YEW_SHIELD, 72, 150), ONYX_BOLTS(ItemID.XBOWS_CROSSBOW_BOLTS_RUNITE_TIPPED_ONYX, 73, 9.4f), + ATLATL_DART_TIPS(ItemID.ATLATL_DART_TIPS, 74, 0.1f), + ATLATL_DART_SHAFT(ItemID.ATLATL_DART_SHAFT, 74, 0.3f), + HEADLESS_ATLATL_DART(ItemID.HEADLESS_ATLATL_DART, 74, 1), + ATLATL_DART(ItemID.ATLATL_DART, 74, 9.5f), RUNE_ARROW(ItemID.RUNE_ARROW, 75, 12.5f), AMETHYST_BROAD_BOLTS(ItemID.SLAYER_BROAD_BOLT_AMETHYST, 76, 10.6f), RUNE_JAVELIN(ItemID.RUNE_JAVELIN, 77, 12.4f), From fcbba14e71a341d053ba634406928b07c437e665 Mon Sep 17 00:00:00 2001 From: Cooper Morris Date: Sat, 22 Nov 2025 22:34:15 -0600 Subject: [PATCH 04/44] item stats: add sailing to yellow spicy stew --- .../net/runelite/client/plugins/itemstats/special/SpicyStew.java | 1 + .../java/net/runelite/client/plugins/itemstats/stats/Stats.java | 1 + 2 files changed, 2 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/special/SpicyStew.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/special/SpicyStew.java index 0b7fa5036a2..fff1003a6bc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/special/SpicyStew.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/special/SpicyStew.java @@ -70,6 +70,7 @@ public StatsChanges calculate(Client client) changes.add(statChangeOf(Stats.THIEVING, yellowBoost, client)); changes.add(statChangeOf(Stats.SLAYER, yellowBoost, client)); changes.add(statChangeOf(Stats.HUNTER, yellowBoost, client)); + changes.add(statChangeOf(Stats.SAILING, yellowBoost, client)); } /* diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/stats/Stats.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/stats/Stats.java index 8398ebff94e..666942013a7 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/stats/Stats.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/stats/Stats.java @@ -51,5 +51,6 @@ public class Stats public static final Stat RUNECRAFT = new SkillStat(Skill.RUNECRAFT); public static final Stat HUNTER = new SkillStat(Skill.HUNTER); public static final Stat CONSTRUCTION = new SkillStat(Skill.CONSTRUCTION); + public static final Stat SAILING = new SkillStat(Skill.SAILING); public static final Stat RUN_ENERGY = new EnergyStat(); } From 74757b5129275bccd9ff759e29bbf4e0f59d6191 Mon Sep 17 00:00:00 2001 From: Felanbird <41973452+Felanbird@users.noreply.github.com> Date: Sun, 23 Nov 2025 10:31:56 -0500 Subject: [PATCH 05/44] clues: update khazard crate cryptic --- .../runelite/client/plugins/cluescrolls/clues/CrypticClue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java index 8d2f6128568..11a4fdd819a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java @@ -241,7 +241,7 @@ public class CrypticClue extends ClueScroll implements NpcClueScroll, ObjectClue .build(), CrypticClue.builder() .itemId(ItemID.TRAIL_CLUE_EASY_VAGUE020) - .text("Search the crate near a cart in Port Khazard.") + .text("Search the crate near the southern general store in Port Khazard.") .location(new WorldPoint(2660, 3149, 0)) .objectId(ObjectID.CRATE) .solution("Search by the southern Khazard General Store in Port Khazard.") From 153ceba2ce2ae9292f4e20732186be9cd9b2e733 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 23 Nov 2025 20:09:59 -0500 Subject: [PATCH 06/44] gpu: fix zone reuse with roof removal --- .../src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java | 1 - 1 file changed, 1 deletion(-) 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 4b46e145c3b..95931d9ff5d 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 @@ -1621,7 +1621,6 @@ public void loadScene(WorldView worldView, Scene scene) Zone[][] newZones = new Zone[SCENE_ZONES][SCENE_ZONES]; final GameState gameState = client.getGameState(); if (prev.isInstance() == scene.isInstance() - && prev.getRoofRemovalMode() == scene.getRoofRemovalMode() && gameState == GameState.LOGGED_IN) { int[][][] prevTemplates = prev.getInstanceTemplateChunks(); From 6c97b58c78aa1b1beea7022ac51093af7fe660a3 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 24 Nov 2025 08:32:41 -0500 Subject: [PATCH 07/44] gpu: fix maploader race computing uvs --- .../plugins/gpu/FacePrioritySorter.java | 26 ++++++++++--------- .../client/plugins/gpu/GpuPlugin.java | 9 +++---- .../client/plugins/gpu/SceneUploader.java | 6 ++--- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/FacePrioritySorter.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/FacePrioritySorter.java index 628caa8fb53..2503de974a4 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/FacePrioritySorter.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/FacePrioritySorter.java @@ -26,16 +26,11 @@ import java.nio.IntBuffer; import java.util.Arrays; -import javax.inject.Inject; -import javax.inject.Singleton; -import lombok.RequiredArgsConstructor; import net.runelite.api.Client; import net.runelite.api.Model; import net.runelite.api.Perspective; import net.runelite.api.Projection; -@Singleton -@RequiredArgsConstructor(onConstructor = @__(@Inject)) class FacePrioritySorter { static final int[] distances; @@ -81,6 +76,13 @@ class FacePrioritySorter } private final Client client; + private final SceneUploader sceneUploader; + + FacePrioritySorter(Client client, SceneUploader sceneUploader) + { + this.client = client; + this.sceneUploader = sceneUploader; + } int uploadSortedModel(Projection proj, Model model, int orientation, int x, int y, int z, IntBuffer opaqueBuffer, IntBuffer alphaBuffer) { @@ -438,16 +440,16 @@ private int pushFace(Model model, int face, IntBuffer opaqueBuffer, IntBuffer al float vy3 = modelLocalY[triangleC]; float vz3 = modelLocalZ[triangleC]; - SceneUploader.computeFaceUvs(model, face); + sceneUploader.computeFaceUvs(model, face); - int su0 = (int) (SceneUploader.u0 * 256f); - int sv0 = (int) (SceneUploader.v0 * 256f); + int su0 = (int) (sceneUploader.u0 * 256f); + int sv0 = (int) (sceneUploader.v0 * 256f); - int su1 = (int) (SceneUploader.u1 * 256f); - int sv1 = (int) (SceneUploader.v1 * 256f); + int su1 = (int) (sceneUploader.u1 * 256f); + int sv1 = (int) (sceneUploader.v1 * 256f); - int su2 = (int) (SceneUploader.u2 * 256f); - int sv2 = (int) (SceneUploader.v2 * 256f); + int su2 = (int) (sceneUploader.u2 * 256f); + int sv2 = (int) (sceneUploader.v2 * 256f); int alphaBias = 0; alphaBias |= transparencies != null ? (transparencies[face] & 0xff) << 24 : 0; 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 95931d9ff5d..26bf5e0ffd7 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 @@ -126,9 +126,6 @@ public class GpuPlugin extends Plugin implements DrawCallbacks @Inject private RegionManager regionManager; - @Inject - private FacePrioritySorter facePrioritySorter; - @Inject private DrawManager drawManager; @@ -190,6 +187,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks private VAOList vaoPO; private SceneUploader clientUploader, mapUploader; + private FacePrioritySorter facePrioritySorter; static class SceneContext { @@ -278,6 +276,7 @@ protected void startUp() subs = new SceneContext[MAX_WORLDVIEWS]; clientUploader = new SceneUploader(renderCallbackManager); mapUploader = new SceneUploader(renderCallbackManager); + facePrioritySorter = new FacePrioritySorter(client, clientUploader); clientThread.invoke(() -> { try @@ -1175,7 +1174,7 @@ public void drawDynamic(Projection worldProjection, Scene scene, TileObject tile if (m.getFaceTransparencies() == null) { VAO o = vaoO.get(size); - SceneUploader.uploadTempModel(m, orient, x, y, z, o.vbo.vb); + clientUploader.uploadTempModel(m, orient, x, y, z, o.vbo.vb); } else { @@ -1256,7 +1255,7 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj else { VAO o = vaoO.get(size); - SceneUploader.uploadTempModel(m, orient, x, y, z, o.vbo.vb); + clientUploader.uploadTempModel(m, orient, x, y, z, o.vbo.vb); } } 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 a32aadb89b2..65c90c91eda 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 @@ -698,7 +698,7 @@ else if (color3 == -2) } // temp draw - static int uploadTempModel(Model model, int orientation, int x, int y, int z, IntBuffer opaqueBuffer) + int uploadTempModel(Model model, int orientation, int x, int y, int z, IntBuffer opaqueBuffer) { final int triangleCount = model.getFaceCount(); final int vertexCount = model.getVerticesCount(); @@ -865,9 +865,9 @@ static int interpolateHSL(int hsl, byte hue2, byte sat2, byte lum2, byte lerp) return (hue << 10 | sat << 7 | lum) & 65535; } - static float u0, u1, u2, v0, v1, v2; + float u0, u1, u2, v0, v1, v2; - static void computeFaceUvs(Model model, int face) + void computeFaceUvs(Model model, int face) { final float[] vertexX = model.getVerticesX(); final float[] vertexY = model.getVerticesY(); From 12115e89e9eb98daeff75316227bd0ed57940c2c Mon Sep 17 00:00:00 2001 From: NathanVegetable <101790594+NathanVegetable@users.noreply.github.com> Date: Mon, 24 Nov 2025 15:32:46 -0800 Subject: [PATCH 08/44] idle notifier: add salvaging reset animation ids --- .../client/plugins/idlenotifier/IdleNotifierPlugin.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java index 994175b92a5..5e7115e3f2d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java @@ -417,6 +417,9 @@ public void onAnimationChanged(AnimationChanged event) case AnimationID.SAILING_HUMAN_SALVAGE_HOOK_KANDARIN_2X5_IDLE01: case AnimationID.SAILING_HUMAN_SALVAGE_HOOK_KANDARIN_3X8_IDLE01: case AnimationID.SAILING_HUMAN_SALVAGE_HOOK_KANDARIN_1X3_INTERACT01: // Sort-salvage + case AnimationID.SAILING_HUMAN_SALVAGE_HOOK_KANDARIN_1X3_RESET01: + case AnimationID.SAILING_HUMAN_SALVAGE_HOOK_KANDARIN_2X5_RESET01: + case AnimationID.SAILING_HUMAN_SALVAGE_HOOK_KANDARIN_3X8_RESET01: /* Misc */ case AnimationID.PISC_REPAIR_HAMMER: case AnimationID.POH_CREATE_MAGIC_TABLET: From 0b18b1c85557af952a53f10c9e871b5f8c8b9b1b Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 25 Nov 2025 13:04:40 -0500 Subject: [PATCH 09/44] api: add unlit face color api --- .../src/main/java/net/runelite/api/DynamicObject.java | 6 ++++++ runelite-api/src/main/java/net/runelite/api/Model.java | 2 ++ .../src/main/java/net/runelite/api/hooks/DrawCallbacks.java | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/runelite-api/src/main/java/net/runelite/api/DynamicObject.java b/runelite-api/src/main/java/net/runelite/api/DynamicObject.java index bc0e583f540..a2843b528d1 100644 --- a/runelite-api/src/main/java/net/runelite/api/DynamicObject.java +++ b/runelite-api/src/main/java/net/runelite/api/DynamicObject.java @@ -53,4 +53,10 @@ public interface DynamicObject extends Renderable * @return */ Model getModelZbuf(); + + /** + * The object composition for the model returned by {@link #getModelZbuf()} + * @return + */ + ObjectComposition getRecordedObjectComposition(); } diff --git a/runelite-api/src/main/java/net/runelite/api/Model.java b/runelite-api/src/main/java/net/runelite/api/Model.java index 0b7ec4d73ba..456b82e87f4 100644 --- a/runelite-api/src/main/java/net/runelite/api/Model.java +++ b/runelite-api/src/main/java/net/runelite/api/Model.java @@ -37,6 +37,8 @@ public interface Model extends Mesh, Renderable int[] getFaceColors3(); + short[] getUnlitFaceColors(); + int getSceneId(); void setSceneId(int sceneId); diff --git a/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java b/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java index 4208ae80955..c2dae84e95a 100644 --- a/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java +++ b/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java @@ -64,6 +64,10 @@ public interface DrawCallbacks * Enable the {@link #zoneInFrustum(int, int, int, int)} callback */ int ZBUF_ZONE_FRUSTUM_CHECK = 0x20; + /** + * Enable the {@link Model#getUnlitFaceColors()} method + */ + int UNLIT_FACE_COLORS = 0x40; int PASS_OPAQUE = 0; int PASS_ALPHA = 1; From 694a3d2ff83cebe6a8f397f73043a8d9bd2d3d6a Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 25 Nov 2025 13:12:29 -0500 Subject: [PATCH 10/44] game event manager: spoof we spawn events --- .../java/net/runelite/client/util/GameEventManager.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/util/GameEventManager.java b/runelite-client/src/main/java/net/runelite/client/util/GameEventManager.java index b72e97913ed..3e3192fc397 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/GameEventManager.java +++ b/runelite-client/src/main/java/net/runelite/client/util/GameEventManager.java @@ -42,6 +42,7 @@ import net.runelite.api.Tile; import net.runelite.api.TileItem; import net.runelite.api.WallObject; +import net.runelite.api.WorldEntity; import net.runelite.api.WorldView; import net.runelite.api.events.DecorativeObjectSpawned; import net.runelite.api.events.GameObjectSpawned; @@ -51,6 +52,7 @@ import net.runelite.api.events.NpcSpawned; import net.runelite.api.events.PlayerSpawned; import net.runelite.api.events.WallObjectSpawned; +import net.runelite.api.events.WorldEntitySpawned; import net.runelite.client.callback.ClientThread; import net.runelite.client.eventbus.EventBus; @@ -207,9 +209,10 @@ private void simulateGameEvents(WorldView wv) } }); - for (WorldView sub : wv.worldViews()) + for (WorldEntity we : wv.worldEntities()) { - simulateGameEvents(sub); + eventBus.post(new WorldEntitySpawned(we)); + simulateGameEvents(we.getWorldView()); } } } \ No newline at end of file From 36d56d17ebebff647c4a7b809bdbe9f31e9334aa Mon Sep 17 00:00:00 2001 From: LlemonDuck Date: Mon, 24 Nov 2025 17:53:59 -0500 Subject: [PATCH 11/44] api: expose WorldView getTarget{Location,Orientation} --- .../java/net/runelite/api/WorldEntity.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/runelite-api/src/main/java/net/runelite/api/WorldEntity.java b/runelite-api/src/main/java/net/runelite/api/WorldEntity.java index 13dd1ec75b1..7677f7bb338 100644 --- a/runelite-api/src/main/java/net/runelite/api/WorldEntity.java +++ b/runelite-api/src/main/java/net/runelite/api/WorldEntity.java @@ -33,12 +33,39 @@ public interface WorldEntity extends CameraFocusableEntity /** * Get the location of this world entity in the top level world. + * * @return */ LocalPoint getLocalLocation(); + /** + * Get the orientation of this world entity in the top level world. + * + * @return + */ + int getOrientation(); + + /** + * Get the destination that the WorldEntity is moving toward. + * After receiving a destination from the server, the client will + * interpolate movement along this route until the next game tick + * (with some added buffer for lag compensation). + * + * @return The target {@link LocalPoint} in the top-level {@link WorldView}. + */ + LocalPoint getTargetLocation(); + + /** + * Get the target orientation of this world entity in the top level world. + * + * @return + * @see #getTargetLocation() + */ + int getTargetOrientation(); + /** * Transform a point within the world entity to the overworld + * * @param point * @return */ @@ -46,6 +73,7 @@ public interface WorldEntity extends CameraFocusableEntity /** * Return true if this worldentity is overlapped + * * @return */ boolean isHiddenForOverlap(); From da1b32174ad6dc38ae696e77f9961a064e82ee86 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Mon, 24 Nov 2025 13:10:07 -0700 Subject: [PATCH 12/44] rl-api: add world entity menu actions --- .../src/main/java/net/runelite/api/MenuAction.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/runelite-api/src/main/java/net/runelite/api/MenuAction.java b/runelite-api/src/main/java/net/runelite/api/MenuAction.java index 913d3420e7e..d53e1b96c6d 100644 --- a/runelite-api/src/main/java/net/runelite/api/MenuAction.java +++ b/runelite-api/src/main/java/net/runelite/api/MenuAction.java @@ -246,6 +246,12 @@ public enum MenuAction SET_HEADING(60), + WORLD_ENTITY_FIRST_OPTION(63), + WORLD_ENTITY_SECOND_OPTION(64), + WORLD_ENTITY_THIRD_OPTION(65), + WORLD_ENTITY_FOURTH_OPTION(66), + WORLD_ENTITY_FIFTH_OPTION(67), + /** * RuneLite menu that is a widge. * @see MenuEntry#getWidget() @@ -283,6 +289,8 @@ public enum MenuAction */ CC_OP_LOW_PRIORITY(1007), + EXAMINE_WORLD_ENTITY(1013), + /** * Menu action injected by runelite for its menu items. */ From 4995367ba139fb652249a819ed7c4a62e48fe585 Mon Sep 17 00:00:00 2001 From: AltarOSRS <220762929+AltarOSRS@users.noreply.github.com> Date: Tue, 25 Nov 2025 20:59:01 -0400 Subject: [PATCH 13/44] poh: add grid master incense burner objects --- .../main/java/net/runelite/client/plugins/poh/PohPlugin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/poh/PohPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/poh/PohPlugin.java index d24d45e4370..32fe258b5c4 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/poh/PohPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/poh/PohPlugin.java @@ -75,8 +75,8 @@ @Slf4j public class PohPlugin extends Plugin { - static final Set BURNER_UNLIT = Sets.newHashSet(ObjectID.POH_TORCH_5, ObjectID.POH_TORCH_6, ObjectID.POH_TORCH_7); - static final Set BURNER_LIT = Sets.newHashSet(ObjectID.POH_TORCH_5_LIT, ObjectID.POH_TORCH_6_LIT, ObjectID.POH_TORCH_7_LIT); + static final Set BURNER_UNLIT = Sets.newHashSet(ObjectID.POH_TORCH_5, ObjectID.POH_TORCH_6, ObjectID.POH_TORCH_7, ObjectID.POH_TORCH_8); + static final Set BURNER_LIT = Sets.newHashSet(ObjectID.POH_TORCH_5_LIT, ObjectID.POH_TORCH_6_LIT, ObjectID.POH_TORCH_7_LIT, ObjectID.POH_TORCH_8_LIT); @Getter(AccessLevel.PACKAGE) private final Map pohObjects = new HashMap<>(); From 682e787ebc631e7cbddf909285c2eadab9700839 Mon Sep 17 00:00:00 2001 From: Charlie Collard Date: Fri, 17 Oct 2025 19:39:57 +0100 Subject: [PATCH 14/44] skill calc: add marble wall construction action --- .../plugins/skillcalculator/skills/ConstructionAction.java | 1 + 1 file changed, 1 insertion(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/ConstructionAction.java b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/ConstructionAction.java index 43243f0ea24..16762fa0010 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/ConstructionAction.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/ConstructionAction.java @@ -193,6 +193,7 @@ public enum ConstructionAction implements NamedSkillAction MAHOGANY_WARDROBE("Mahogany Wardrobe", 75, 420, ItemID.POH_WARDROBE_6), GNOME_BENCH("Gnome Bench", 77, 840, ItemID.POH_SUPERIOR_GARDEN_BENCH_MAHOGANY), ARMILLARY_GLOBE("Armillary Globe", 77, 960, ItemID.POH_GLOBE_1), + MARBLE_WALL("Marble Wall", 79, 4000, ItemID.POH_FENCING7), MARBLE_PORTAL("Marble Portal", 80, 1500, ItemID.POH_PORTAL_FRAME_3), SCRYING_POOL("Scrying Pool", 80, 2000, ItemID.POH_TELEPORT_CENTREPIECE_3), BALANCE_BEAM("Balance Beam", 81, 1000, ItemID.POH_COMBAT_RING_5), From 2854cfc157a50090cce4b258a26a5c2610966cd3 Mon Sep 17 00:00:00 2001 From: MoonWolf125 Date: Fri, 17 Oct 2025 17:11:06 -0500 Subject: [PATCH 15/44] skill calc: add high-cost construction actions This commit adds actions for the Gold sink, Crystal throne, and Demonic throne. While players basically never build these items during normal gameplay, it can be useful for calculating xp gains during temporary game modes where gp is plentiful. (Leagues and Grid Master, for instance) --- .../plugins/skillcalculator/skills/ConstructionAction.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/ConstructionAction.java b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/ConstructionAction.java index 16762fa0010..5b37a806f71 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/ConstructionAction.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/ConstructionAction.java @@ -144,6 +144,7 @@ public enum ConstructionAction implements NamedSkillAction EAGLE_LECTERN("Eagle Lectern", 47, 120, ItemID.POH_LECTERN_2), SINK("Sink", 47, 300, ItemID.POH_SINK_3), MOUNTED_MYTHICAL_CAPE("Mounted Mythical Cape", 47, 370, ItemID.POH_TROPHY_MYTHICAL_CAPE), + GOLD_SINK("Gold Sink", 47, 11144, ItemID.POH_SINK_4), CHEFS_DELIGHT("Chef's Delight", 48, 224, ItemID.CHEFS_DELIGHT), TELEPORT_FOCUS("Teleport Focus", 50, 40, ItemID.POH_TELEPORT_CENTREPIECE_1), ORNAMENTAL_GLOBE("Ornamental Globe", 50, 270, ItemID.POH_GLOBE_2), @@ -203,6 +204,8 @@ public enum ConstructionAction implements NamedSkillAction SMALL_ORRERY("Small Orrery", 86, 1320, ItemID.POH_GLOBE_6), GILDED_WARDROBE("Gilded Wardrobe", 87, 720, ItemID.POH_WARDROBE_7), LARGE_ORRERY("Large Orrery", 95, 1420, ItemID.POH_GLOBE_7), + CRYSTAL_THRONE("Crystal Throne", 95, 15000, ItemID.POH_THRONE_6), + DEMONIC_THRONE("Demonic Throne", 99, 25000, ItemID.POH_THRONE_7), ; private final String name; From f9a8214d0a931ac0013bfb98234749c5c920cf6b Mon Sep 17 00:00:00 2001 From: Jordan Atwood Date: Tue, 25 Nov 2025 17:23:44 -0800 Subject: [PATCH 16/44] entity hider: use consistent wording for other players --- .../client/plugins/entityhider/EntityHiderConfig.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java index 6f72cbac883..536a788ac56 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java @@ -48,8 +48,8 @@ default boolean hideOthers() @ConfigItem( position = 2, keyName = "hidePlayers2D", - name = "Hide others 2D", - description = "Configures whether or not other players 2D elements are hidden." + name = "Hide others' 2D", + description = "Configures whether or not other players' 2D elements are hidden." ) default boolean hideOthers2D() { @@ -169,8 +169,8 @@ default boolean hideWorldEntities() @ConfigItem( position = 20, keyName = "hidePets", - name = "Hide other players' pets", - description = "Configures whether or not other player pets are hidden." + name = "Hide others' pets", + description = "Configures whether or not other players' pets are hidden." ) default boolean hidePets() { From c408b9c8d67062c0e08f2b167ad97c6e5ae9a14a Mon Sep 17 00:00:00 2001 From: Joe McKie Date: Tue, 25 Nov 2025 17:24:19 -0800 Subject: [PATCH 17/44] entity hider: clarify other players' random events are hidden --- .../client/plugins/entityhider/EntityHiderConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java index 536a788ac56..1366044d37b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java @@ -224,8 +224,8 @@ default boolean hideThralls() @ConfigItem( position = 25, keyName = "hideRandomEvents", - name = "Hide random events", - description = "Configures whether or not random events are hidden." + name = "Hide others' random events", + description = "Configures whether or not other players' random events are hidden." ) default boolean hideRandomEvents() { From d5188cb30a208802262669a0c1790068dcbc2973 Mon Sep 17 00:00:00 2001 From: Cooper Morris Date: Tue, 25 Nov 2025 23:47:00 -0600 Subject: [PATCH 18/44] worldmap: add mooring points --- .../client/plugins/worldmap/MapPoint.java | 4 +- .../plugins/worldmap/MooringLocation.java | 103 ++++++++++++++++++ .../plugins/worldmap/WorldMapConfig.java | 22 ++++ .../plugins/worldmap/WorldMapPlugin.java | 36 ++++++ 4 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MooringLocation.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MapPoint.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MapPoint.java index 161c669604a..41e74ed2244 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MapPoint.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MapPoint.java @@ -48,7 +48,9 @@ enum Type AGILITY_COURSE, AGILITY_SHORTCUT, QUEST, - RARE_TREE + RARE_TREE, + MOORING_POINT, + ; } @Getter diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MooringLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MooringLocation.java new file mode 100644 index 00000000000..57761953381 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MooringLocation.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2025, coopermor + * 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 HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.worldmap; + +import lombok.Getter; +import net.runelite.api.coords.WorldPoint; + +@Getter +public enum MooringLocation +{ + PORT_SARIM("Port Sarim", 1, new WorldPoint(3050, 3192, 0)), + THE_PANDEMONIUM("The Pandemonium", 1, new WorldPoint(3069, 2986, 0)), + LANDS_END("Land's End", 5, new WorldPoint(1506, 3402, 0)), + HOSIDIUS("Hosidius", 5, new WorldPoint(1726, 3452, 0)), + MUSA_POINT("Musa Point", 10, new WorldPoint(2960, 3147, 0)), + PORT_PISCARILLIUS("Port Piscarillius", 15, new WorldPoint(1845, 3687, 0)), + RIMMINGTON("Rimmington", 18, new WorldPoint(2905, 3226, 0)), + CATHERBY("Catherby", 20, new WorldPoint(2796, 3412, 0)), + BRIMHAVEN("Brimhaven", 25, new WorldPoint(2757, 3229, 0)), + ARDOUGNE("Ardougne", 28, new WorldPoint(2671, 3265, 0)), + PORT_KHAZARD("Port Khazard", 30, new WorldPoint(2685, 3161, 0)), + WITCHAVEN("Witchaven", 34, new WorldPoint(2746, 3304, 0)), + ENTRANA("Entrana", 36, new WorldPoint(2878, 3335, 0)), + CIVITAS_ILLA_FORTIS("Civitas illa Fortis", 38, new WorldPoint(1774, 3141, 0)), + CORSAIR_COVE("Corsair Cove", 40, new WorldPoint(2579, 2843, 0)), + DOGNOSE_ISLAND("Dognose Island", 40, new WorldPoint(3061, 2639, 0)), + CAIRN_ISLE("Cairn Isle", 42, new WorldPoint(2749, 2951, 0)), + CHINCHOMPA_ISLAND("Chinchompa Island", 42, new WorldPoint(1892, 3429, 0)), + SUNSET_COAST("Sunset Coast", 44, new WorldPoint(1511, 2975, 0)), + REMOTE_ISLAND("Remote Island", 45, new WorldPoint(2791, 2603, 0)), + THE_SUMMER_SHORE("The Summer Shore", 45, new WorldPoint(3174, 2367, 0)), + THE_LITTLE_PEARL("The Little Pearl", 45, new WorldPoint(3354, 2216, 0)), + ALDARIN("Aldarin", 46, new WorldPoint(1452, 2970, 0)), + VATRACHOS_ISLAND("Vatrachos Island", 46, new WorldPoint(1872, 2985, 0)), + THE_ONYX_CREST("The Onyx Crest", 47, new WorldPoint(2997, 2288, 0)), + RUINS_OF_UNKAH("Ruins of Unkah", 48, new WorldPoint(3143, 2824, 0)), + SHIMMERING_ATOLL("Shimmering Atoll", 49, new WorldPoint(1557, 2771, 0)), + VOID_KNIGHTS_OUTPOST("Void Knights' Outpost", 50, new WorldPoint(2651, 2678, 0)), + PORT_ROBERTS("Port Roberts", 50, new WorldPoint(1860, 3306, 0)), + ANGLERS_RETREAT("Anglers' Retreat", 51, new WorldPoint(2467, 2721, 0)), + MINOTAURS_REST("Minotaurs' Rest", 54, new WorldPoint(1958, 3117, 0)), + ISLE_OF_SOULS("Isle of Souls", 55, new WorldPoint(2282, 2823, 0)), + ISLE_OF_BONES("Isle of Bones", 56, new WorldPoint(2532, 2531, 0)), + LAGUNA_AURORAE("Laguna Aurorae", 58, new WorldPoint(1202, 2733, 0)), + CHARRED_ISLAND("Charred Island", 60, new WorldPoint(2660, 2395, 0)), + TEAR_OF_THE_SOUL("Tear of the Soul", 61, new WorldPoint(2318, 2774, 0)), + RELLEKKA("Rellekka", 62, new WorldPoint(2630, 3705, 0)), + WINTUMBER_ISLAND("Wintumber Island", 63, new WorldPoint(2058, 2606, 0)), + THE_CROWN_JEWEL("The Crown Jewel", 64, new WorldPoint(1765, 2659, 0)), + ETCETERIA("Etceteria", 65, new WorldPoint(2611, 3840, 0)), + PORT_TYRAS("Port Tyras", 66, new WorldPoint(2144, 3120, 0)), + LLEDRITH_ISLAND("Lledrith Island", 66, new WorldPoint(2097, 3188, 0)), + DEEPFIN_POINT("Deepfin Point", 67, new WorldPoint(1923, 2758, 0)), + JATIZSO("Jatizso", 68, new WorldPoint(2412, 3780, 0)), + NETIZNOT("Netiznot", 68, new WorldPoint(2308, 3783, 0)), + RAINBOWS_END("Rainbow's End", 69, new WorldPoint(2344, 2270, 0)), + PRIFDDINAS("Prifddinas", 70, new WorldPoint(2158, 3324, 0)), + SUNBLEAK_ISLAND("Sunbleak Island", 72, new WorldPoint(2189, 2327, 0)), + YNYSDAIL("Ynysdail", 73, new WorldPoint(2222, 3466, 0)), + WATERBIRTH_ISLAND("Waterbirth Island", 74, new WorldPoint(2543, 3765, 0)), + PISCATORIS("Piscatoris", 75, new WorldPoint(2303, 3690, 0)), + LUNAR_ISLE("Lunar Isle", 76, new WorldPoint(2151, 3880, 0)), + BUCCANEERS_HAVEN("Buccaneers' Haven", 76, new WorldPoint(2080, 3690, 0)), + DRUMSTICK_ISLE("Drumstick Isle", 79, new WorldPoint(2150, 3530, 0)), + WEISS("Weiss", 80, new WorldPoint(2860, 3972, 0)), + BRITTLE_ISLE("Brittle Isle", 81, new WorldPoint(1954, 4056, 0)), + GRIMSTONE("Grimstone", 87, new WorldPoint(2927, 4056, 0)), + ; + + private final String tooltip; + private final int levelReq; + private final WorldPoint location; + + MooringLocation(String description, int level, WorldPoint location) + { + this.tooltip = description + " - Level " + level; + this.location = location; + this.levelReq = level; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapConfig.java index abc6548c197..a989de626bc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapConfig.java @@ -295,4 +295,26 @@ default boolean fishingSpotTooltips() { return true; } + + @ConfigItem( + keyName = WorldMapPlugin.CONFIG_KEY_MOORING_LOCATION_TOOLTIPS, + name = "Mooring location tooltips", + description = "Indicates the level required to moor at a location.", + position = 25 + ) + default boolean mooringLocationTooltips() + { + return true; + } + + @ConfigItem( + keyName = WorldMapPlugin.CONFIG_KEY_MOORING_LOCATION_LEVEL_ICON, + name = "Indicate inaccessible mooring locations", + description = "Indicate mooring points you do not have the level to use on the icon.", + position = 26 + ) + default boolean mooringPointLevelIcon() + { + return true; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapPlugin.java index 64e9c47672c..6979831b00d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapPlugin.java @@ -105,6 +105,8 @@ public class WorldMapPlugin extends Plugin static final String CONFIG_KEY_DUNGEON_TOOLTIPS = "dungeonTooltips"; static final String CONFIG_KEY_HUNTER_AREA_TOOLTIPS = "hunterAreaTooltips"; static final String CONFIG_KEY_FISHING_SPOT_TOOLTIPS = "fishingSpotTooltips"; + static final String CONFIG_KEY_MOORING_LOCATION_TOOLTIPS = "mooringLocationTooltips"; + static final String CONFIG_KEY_MOORING_LOCATION_LEVEL_ICON = "mooringLocationShortcutIcon"; static { @@ -161,6 +163,7 @@ public class WorldMapPlugin extends Plugin private int agilityLevel = 0; private int woodcuttingLevel = 0; + private int sailingLevel = 0; private final Map questStartLocations = new EnumMap<>(Quest.class); @@ -175,6 +178,7 @@ protected void startUp() throws Exception { agilityLevel = client.getRealSkillLevel(Skill.AGILITY); woodcuttingLevel = client.getRealSkillLevel(Skill.WOODCUTTING); + sailingLevel = client.getRealSkillLevel(Skill.SAILING); updateShownIcons(); } @@ -185,6 +189,7 @@ protected void shutDown() throws Exception questStartLocations.clear(); agilityLevel = 0; woodcuttingLevel = 0; + sailingLevel = 0; } @Subscribe @@ -223,6 +228,17 @@ public void onStatChanged(StatChanged statChanged) } break; } + case SAILING: + { + // Docking at locations is not boostable + int newSailingLevel = client.getRealSkillLevel(Skill.SAILING); + if (newSailingLevel != sailingLevel) + { + sailingLevel = newSailingLevel; + updateMooringPointIcons(); + } + break; + } } } @@ -361,10 +377,30 @@ private void updateRareTreeIcons() } } + private void updateMooringPointIcons() + { + worldMapPointManager.removeIf(isType(MapPoint.Type.MOORING_POINT)); + + if (config.mooringLocationTooltips() || config.mooringPointLevelIcon()) + { + Arrays.stream(MooringLocation.values()) + .map(l -> + MapPoint.builder() + .type(MapPoint.Type.MOORING_POINT) + .worldPoint(l.getLocation()) + .image(sailingLevel > 0 && config.mooringPointLevelIcon() && l.getLevelReq() > sailingLevel ? NOPE_ICON : BLANK_ICON) + .tooltip(config.mooringLocationTooltips() ? l.getTooltip() : null) + .build() + ) + .forEach(worldMapPointManager::add); + } + } + private void updateShownIcons() { updateAgilityIcons(); updateAgilityCourseIcons(); + updateMooringPointIcons(); updateRareTreeIcons(); updateQuestStartPointIcons(); From 5b5c0a03a34ff062db59b105b82797f6d4bdbb0e Mon Sep 17 00:00:00 2001 From: coopermor Date: Tue, 25 Nov 2025 08:58:43 -0800 Subject: [PATCH 19/44] loottracker: Add shipwreck salvage --- .../plugins/loottracker/LootTrackerPlugin.java | 15 +++++++++++++++ .../loottracker/LootTrackerPluginTest.java | 17 +++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java index 87807578018..f79b274eab9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java @@ -167,6 +167,9 @@ public class LootTrackerPlugin extends Plugin static final String ZOMBIE_PIRATE_LOCKER_EVENT = "Zombie Pirate's Locker"; private static final Pattern ZOMBIE_PIRATE_LOCKER_PATTERN = Pattern.compile("You loot the locker and receive (?[\\d,]+) x (?.+)\\."); + // Shipwreck salvaging + private static final Pattern SALVAGE_PATTERN = Pattern.compile("You sort through the\\s+(?\\S+)\\s+salvage.*"); + // Seed Pack loot handling private static final String SEEDPACK_EVENT = "Seed pack"; @@ -1017,6 +1020,18 @@ public void onChatMessage(ChatMessage event) processZombiePirateLockerLoot(zombiePirateLockerMatcher); } + final Matcher shipwreckSalvagingMatcher = SALVAGE_PATTERN.matcher(message); + if (shipwreckSalvagingMatcher.matches()) + { + String tier = shipwreckSalvagingMatcher.group("tier"); + String eventName = WordUtils.capitalizeFully(tier) + " salvage"; + onInvChange((invItems, groundItems, removedItems) -> + { + addLoot(eventName, -1, LootRecordType.EVENT, null, invItems); + }); + return; + } + if (message.equals(HERBIBOAR_LOOTED_MESSAGE)) { if (processHerbiboarHerbSackLoot(event.getTimestamp())) diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java index 88c948c5188..e61158837d6 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java @@ -669,4 +669,21 @@ public void testZombiePirateLockerLoot() new ItemStack(ItemID.RUNE_SWORD, 2) )); } + + @Test + public void testLargeSalvage() + { + ItemContainer itemContainer = mock(ItemContainer.class); + when(itemContainer.getItems()).thenReturn(new Item[]{new Item(ItemID.SAILING_LARGE_SHIPWRECK_SALVAGE, 1)}); + when(client.getItemContainer(InventoryID.INV)).thenReturn(itemContainer); + + ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "You sort through the large salvage and find: 4 x Steel nails.", "", 0); + lootTrackerPlugin.onChatMessage(chatMessage); + + when(itemContainer.getItems()).thenReturn(new Item[]{new Item(ItemID.NAILS, 4)}); + + lootTrackerPlugin.onItemContainerChanged(new ItemContainerChanged(InventoryID.INV, itemContainer)); + + verify(lootTrackerPlugin).addLoot("Large salvage", -1, LootRecordType.EVENT, null, Collections.singletonList(new ItemStack(ItemID.NAILS, 4))); + } } From ad593f28eaaf601d3a025e74c89b8367d1d5d17f Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 26 Nov 2025 16:47:13 -0500 Subject: [PATCH 20/44] loot tracker: use collectInvItems for salvaging loot --- .../client/plugins/loottracker/LootTrackerPlugin.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java index f79b274eab9..dcba540882d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java @@ -1025,10 +1025,7 @@ public void onChatMessage(ChatMessage event) { String tier = shipwreckSalvagingMatcher.group("tier"); String eventName = WordUtils.capitalizeFully(tier) + " salvage"; - onInvChange((invItems, groundItems, removedItems) -> - { - addLoot(eventName, -1, LootRecordType.EVENT, null, invItems); - }); + onInvChange(collectInvItems(LootRecordType.EVENT, eventName)); return; } From db0f67e2dc520a1282f3016a36499ca10241b537 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Wed, 26 Nov 2025 13:13:31 -0700 Subject: [PATCH 21/44] api: expose wec bounds --- .../src/main/java/net/runelite/api/WorldEntityConfig.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/runelite-api/src/main/java/net/runelite/api/WorldEntityConfig.java b/runelite-api/src/main/java/net/runelite/api/WorldEntityConfig.java index 3449e928c1c..560cda5e726 100644 --- a/runelite-api/src/main/java/net/runelite/api/WorldEntityConfig.java +++ b/runelite-api/src/main/java/net/runelite/api/WorldEntityConfig.java @@ -29,4 +29,12 @@ public interface WorldEntityConfig int getId(); int getCategory(); + + int getBoundsX(); + + int getBoundsY(); + + int getBoundsWidth(); + + int getBoundsHeight(); } From 7c512242fea60295128c2e9f053b64d57807681a Mon Sep 17 00:00:00 2001 From: Max Weber Date: Wed, 26 Nov 2025 14:47:46 -0700 Subject: [PATCH 22/44] api: expose object op overrides --- .../src/main/java/net/runelite/api/TileObject.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/runelite-api/src/main/java/net/runelite/api/TileObject.java b/runelite-api/src/main/java/net/runelite/api/TileObject.java index 055081acb15..a9a1847814e 100644 --- a/runelite-api/src/main/java/net/runelite/api/TileObject.java +++ b/runelite-api/src/main/java/net/runelite/api/TileObject.java @@ -149,4 +149,16 @@ public interface TileObject */ @Nullable Shape getClickbox(); + + /** + * Get the text override for a certain action + */ + @Nullable + String getOpOverride(int index); + + /** + * Gets if an action is shown in the minimenu. If an action is {@code null} it + * will not be shown even if this method returns {@code true} + */ + boolean isOpShown(int index); } From 13286a0e9f5bddc2f36cc6463f7e32783975bda6 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Wed, 26 Nov 2025 15:42:27 -0700 Subject: [PATCH 23/44] api: expose yellow click action --- .../src/main/java/net/runelite/api/Constants.java | 4 ++++ .../src/main/java/net/runelite/api/WorldView.java | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/runelite-api/src/main/java/net/runelite/api/Constants.java b/runelite-api/src/main/java/net/runelite/api/Constants.java index ef8dca4dc5a..407831d491b 100644 --- a/runelite-api/src/main/java/net/runelite/api/Constants.java +++ b/runelite-api/src/main/java/net/runelite/api/Constants.java @@ -147,4 +147,8 @@ public class Constants * @see ItemComposition#getPrice */ public static final float HIGH_ALCHEMY_MULTIPLIER = .6f; + + public static final int CLICK_ACTION_NONE = 0; + public static final int CLICK_ACTION_WALK = 1; + public static final int CLICK_ACTION_SET_HEADING = 2; } 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 72768006371..01bf39cb8fe 100644 --- a/runelite-api/src/main/java/net/runelite/api/WorldView.java +++ b/runelite-api/src/main/java/net/runelite/api/WorldView.java @@ -27,6 +27,7 @@ import javax.annotation.Nullable; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; +import org.intellij.lang.annotations.MagicConstant; public interface WorldView { @@ -255,4 +256,12 @@ Projectile createProjectile(int id, int plane, int startX, int startY, int start */ @Nullable Projection getCanvasProjection(); + + /** + * Returns how clicking on tiles should behave for this WorldView. + * + * @return one of {@link Constants#CLICK_ACTION_NONE}, {@link Constants#CLICK_ACTION_WALK}, {@link Constants#CLICK_ACTION_SET_HEADING} + */ + @MagicConstant(intValues = {Constants.CLICK_ACTION_NONE, Constants.CLICK_ACTION_WALK, Constants.CLICK_ACTION_SET_HEADING}) + int getYellowClickAction(); } From b5358743fced43c67160059f0e17a65d2e0f2cd3 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 26 Nov 2025 21:54:15 -0500 Subject: [PATCH 24/44] overlay: cache overlay fonts --- .../client/ui/overlay/OverlayRenderer.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java index 4b8eb4ff376..f1e947e6c1f 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayRenderer.java @@ -30,6 +30,7 @@ import java.awt.Composite; import java.awt.Cursor; import java.awt.Dimension; +import java.awt.Font; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Point; @@ -96,6 +97,8 @@ public class OverlayRenderer extends MouseAdapter private final EventBus eventBus; private final ChatMessageManager chatMessageManager; + private Font font, tooltipFont, interfaceFont; + // Overlay movement variables private final Point overlayOffset = new Point(); private final Point mousePosition = new Point(); @@ -288,6 +291,11 @@ private void renderOverlays(final Graphics2D graphics, Collection overl final RenderingHints renderingHints = graphics.getRenderingHints(); final Color background = graphics.getBackground(); + // Cache overlay fonts + this.font = runeLiteConfig.fontType().getFont(); + this.tooltipFont = runeLiteConfig.tooltipFontType().getFont(); + this.interfaceFont = runeLiteConfig.interfaceFontType().getFont(); + final Rectangle clip = clipBounds(layer); graphics.setClip(clip); @@ -714,15 +722,15 @@ private void safeRender(Overlay overlay, Graphics2D graphics, Point point) // Set font based on configuration if (position == OverlayPosition.DYNAMIC || position == OverlayPosition.DETACHED) { - graphics.setFont(runeLiteConfig.fontType().getFont()); + graphics.setFont(font); } else if (position == OverlayPosition.TOOLTIP) { - graphics.setFont(runeLiteConfig.tooltipFontType().getFont()); + graphics.setFont(tooltipFont); } else { - graphics.setFont(runeLiteConfig.interfaceFontType().getFont()); + graphics.setFont(interfaceFont); } graphics.translate(point.x, point.y); From 006adf7afbfb9c2c96c4ca2af6959fbf91396ad0 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 26 Nov 2025 21:54:49 -0500 Subject: [PATCH 25/44] gpu: reduce iterator allocs --- .../runelite/client/plugins/gpu/GpuPlugin.java | 15 +++++++++------ .../java/net/runelite/client/plugins/gpu/VAO.java | 9 +++++---- .../net/runelite/client/plugins/gpu/Zone.java | 13 +++++++++---- 3 files changed, 23 insertions(+), 14 deletions(-) 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 26bf5e0ffd7..3a69969f3ae 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 @@ -1114,26 +1114,29 @@ public void drawPass(Projection projection, Scene scene, int pass) { glUniform3i(uniBase, 0, 0, 0); - var vaos = vaoO.unmap(); - for (VAO vao : vaos) + int sz = vaoO.unmap(); + for (int i = 0; i < sz; ++i) { + VAO vao = vaoO.vaos.get(i); vao.draw(); vao.reset(); } - vaos = vaoPO.unmap(); - if (!vaos.isEmpty()) + sz = vaoPO.unmap(); + if (sz > 0) { glDepthMask(false); - for (VAO vao : vaos) + for (int i = 0; i < sz; ++i) { + VAO vao = vaoPO.vaos.get(i); vao.draw(); } glDepthMask(true); glColorMask(false, false, false, false); - for (VAO vao : vaos) + for (int i = 0; i < sz; ++i) { + VAO vao = vaoPO.vaos.get(i); vao.draw(); vao.reset(); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/VAO.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/VAO.java index e9a1e9d048e..8251cf4d8b5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/VAO.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/VAO.java @@ -143,7 +143,7 @@ class VAOList private static final int VAO_SIZE = 4 * 1024 * 1024; private int curIdx; - private final List vaos = new ArrayList<>(); + final List vaos = new ArrayList<>(); VAO get(int size) { @@ -174,11 +174,12 @@ VAO get(int size) return vao; } - List unmap() + int unmap() { int sz = 0; - for (VAO vao : vaos) + for (int i = 0; i < vaos.size(); ++i) { + VAO vao = vaos.get(i); if (vao.vbo.mapped) { ++sz; @@ -186,7 +187,7 @@ List unmap() } } curIdx = 0; - return vaos.subList(0, sz); + return sz; } void free() 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 ea2c7a46d54..3f0512df4df 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 @@ -522,10 +522,14 @@ void renderAlpha(int zx, int zz, int cyaw, int cpitch, int minLevel, int current int yawcos = Perspective.COSINE[cyaw]; int pitchsin = Perspective.SINE[cpitch]; int pitchcos = Perspective.COSINE[cpitch]; - for (AlphaModel m : alphaModels) + for (int j = 0; j < alphaModels.size(); ++j) { - if ((m.flags & AlphaModel.SKIP) != 0) continue; - if (m.level != level) continue; + AlphaModel m = alphaModels.get(j); + + if ((m.flags & AlphaModel.SKIP) != 0 || m.level != level) + { + continue; + } boolean ok = false; if (level >= minLevel && level <= maxLevel) @@ -718,8 +722,9 @@ else if (lastDrawMode == STATIC_UNSORTED) void multizoneLocs(Scene scene, int zx, int zz, int cx, int cz, Zone[][] zones) { int offset = scene.getWorldViewId() == -1 ? GpuPlugin.SCENE_OFFSET >> 3 : 0; - for (AlphaModel m : alphaModels) + for (int i = 0; i < alphaModels.size(); ++i) { + AlphaModel m = alphaModels.get(i); if (m.lx == -1) { continue; From 856c94987e5aba6fba3feaaa6a073e641c850bd8 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 26 Nov 2025 22:08:21 -0500 Subject: [PATCH 26/44] implings: add region check to spawn overlay --- .../runelite/client/plugins/implings/ImplingsOverlay.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingsOverlay.java index 67472b2e49a..585a85f1e14 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/implings/ImplingsOverlay.java @@ -41,6 +41,8 @@ class ImplingsOverlay extends Overlay { + private static final int PURO_PURO = (40 << 8) | 67; + private final Client client; private final ImplingsConfig config; private final ImplingsPlugin plugin; @@ -58,6 +60,11 @@ private ImplingsOverlay(Client client, ImplingsConfig config, ImplingsPlugin plu @Override public Dimension render(Graphics2D graphics) { + if (client.getLocalPlayer().getWorldLocation().getRegionID() != PURO_PURO) + { + return null; + } + if (config.showSpawn()) { for (ImplingSpawn spawn : ImplingSpawn.values()) From 7f89baff52c9b5dc23fab135f4360e371a1ecb8f Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 27 Nov 2025 21:58:41 -0500 Subject: [PATCH 27/44] gpu: simplify back-facing cull test The canvas position doesn't really need to be computed, using the cross product of the projected points also works --- .../plugins/gpu/FacePrioritySorter.java | 33 ++++++++----------- .../client/plugins/gpu/GpuPlugin.java | 2 +- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/FacePrioritySorter.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/FacePrioritySorter.java index 2503de974a4..62f09394dff 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/FacePrioritySorter.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/FacePrioritySorter.java @@ -26,7 +26,6 @@ import java.nio.IntBuffer; import java.util.Arrays; -import net.runelite.api.Client; import net.runelite.api.Model; import net.runelite.api.Perspective; import net.runelite.api.Projection; @@ -37,8 +36,8 @@ class FacePrioritySorter static final char[] distanceFaceCount; static final char[][] distanceToFaces; - private static final float[] modelCanvasX; - private static final float[] modelCanvasY; + private static final float[] modelProjectedX; + private static final float[] modelProjectedY; static final float[] modelLocalX; static final float[] modelLocalY; @@ -61,8 +60,8 @@ class FacePrioritySorter distanceFaceCount = new char[MAX_DIAMETER]; distanceToFaces = new char[MAX_DIAMETER][ZSORT_GROUP_SIZE]; - modelCanvasX = new float[MAX_VERTEX_COUNT]; - modelCanvasY = new float[MAX_VERTEX_COUNT]; + modelProjectedX = new float[MAX_VERTEX_COUNT]; + modelProjectedY = new float[MAX_VERTEX_COUNT]; modelLocalX = new float[MAX_VERTEX_COUNT]; modelLocalY = new float[MAX_VERTEX_COUNT]; @@ -75,12 +74,10 @@ class FacePrioritySorter orderedFaces = new int[12][MAX_FACES_PER_PRIORITY]; } - private final Client client; private final SceneUploader sceneUploader; - FacePrioritySorter(Client client, SceneUploader sceneUploader) + FacePrioritySorter(SceneUploader sceneUploader) { - this.client = client; this.sceneUploader = sceneUploader; } @@ -99,10 +96,6 @@ int uploadSortedModel(Projection proj, Model model, int orientation, int x, int final int[] faceColors3 = model.getFaceColors3(); final byte[] faceRenderPriorities = model.getFaceRenderPriorities(); - final int centerX = client.getCenterX(); - final int centerY = client.getCenterY(); - final int zoom = client.get3dZoom(); - float orientSine = 0; float orientCosine = 0; if (orientation != 0) @@ -142,8 +135,8 @@ int uploadSortedModel(Projection proj, Model model, int orientation, int x, int return 0; } - modelCanvasX[v] = centerX + p[0] * zoom / p[2]; - modelCanvasY[v] = centerY + p[1] * zoom / p[2]; + modelProjectedX[v] = p[0]; + modelProjectedY[v] = p[1]; distances[v] = (int) p[2] - zero; } @@ -165,12 +158,12 @@ int uploadSortedModel(Projection proj, Model model, int orientation, int x, int final int v3 = indices3[i]; final float - aX = modelCanvasX[v1], - aY = modelCanvasY[v1], - bX = modelCanvasX[v2], - bY = modelCanvasY[v2], - cX = modelCanvasX[v3], - cY = modelCanvasY[v3]; + aX = modelProjectedX[v1], + aY = modelProjectedY[v1], + bX = modelProjectedX[v2], + bY = modelProjectedY[v2], + cX = modelProjectedX[v3], + cY = modelProjectedY[v3]; if ((aX - bX) * (cY - bY) - (cX - bX) * (aY - bY) > 0) { 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 3a69969f3ae..3db88e78615 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 @@ -276,7 +276,7 @@ protected void startUp() subs = new SceneContext[MAX_WORLDVIEWS]; clientUploader = new SceneUploader(renderCallbackManager); mapUploader = new SceneUploader(renderCallbackManager); - facePrioritySorter = new FacePrioritySorter(client, clientUploader); + facePrioritySorter = new FacePrioritySorter(clientUploader); clientThread.invoke(() -> { try From 25626651ac3872836d2dac30f46fa613b908e0e9 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 27 Nov 2025 22:02:21 -0500 Subject: [PATCH 28/44] gpu: fix pmd --- .../src/main/java/net/runelite/client/plugins/gpu/VAO.java | 2 +- .../src/main/java/net/runelite/client/plugins/gpu/Zone.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/VAO.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/VAO.java index 8251cf4d8b5..60749bad0c0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/VAO.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/VAO.java @@ -177,7 +177,7 @@ VAO get(int size) int unmap() { int sz = 0; - for (int i = 0; i < vaos.size(); ++i) + for (int i = 0; i < vaos.size(); ++i) // NOPMD: ForLoopCanBeForeach { VAO vao = vaos.get(i); if (vao.vbo.mapped) 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 3f0512df4df..326212b1524 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 @@ -522,7 +522,7 @@ void renderAlpha(int zx, int zz, int cyaw, int cpitch, int minLevel, int current int yawcos = Perspective.COSINE[cyaw]; int pitchsin = Perspective.SINE[cpitch]; int pitchcos = Perspective.COSINE[cpitch]; - for (int j = 0; j < alphaModels.size(); ++j) + for (int j = 0; j < alphaModels.size(); ++j) // NOPMD: ForLoopCanBeForeach { AlphaModel m = alphaModels.get(j); @@ -722,7 +722,7 @@ else if (lastDrawMode == STATIC_UNSORTED) void multizoneLocs(Scene scene, int zx, int zz, int cx, int cz, Zone[][] zones) { int offset = scene.getWorldViewId() == -1 ? GpuPlugin.SCENE_OFFSET >> 3 : 0; - for (int i = 0; i < alphaModels.size(); ++i) + for (int i = 0; i < alphaModels.size(); ++i) // NOPMD: ForLoopCanBeForeach { AlphaModel m = alphaModels.get(i); if (m.lx == -1) From 18262f614111fb0518a5cd8d08604a395e43bdf7 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 27 Nov 2025 22:31:05 -0500 Subject: [PATCH 29/44] gpu: enable by default --- .../src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java | 1 - 1 file changed, 1 deletion(-) 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 3db88e78615..24c67b947dc 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 @@ -94,7 +94,6 @@ @PluginDescriptor( name = "GPU", description = "Offloads rendering to GPU", - enabledByDefault = false, tags = {"fog", "draw distance"}, loadInSafeMode = false ) From bd563fe2f7a169dd357c88e33470f054f4cbe6c8 Mon Sep 17 00:00:00 2001 From: chsami Date: Fri, 28 Nov 2025 15:36:16 +0100 Subject: [PATCH 30/44] chore(pom): update microbot version to 2.0.55 --- runelite-client/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml index 9288e065638..76baa325002 100644 --- a/runelite-client/pom.xml +++ b/runelite-client/pom.xml @@ -41,7 +41,7 @@ nogit false false - 2.0.54 + 2.0.55 nogit From 4389a0a4d2a7e3395fb7a1b4ef170df09009be92 Mon Sep 17 00:00:00 2001 From: g-mason0 <19415334+g-mason0@users.noreply.github.com> Date: Sat, 29 Nov 2025 18:59:03 -0500 Subject: [PATCH 31/44] fix(api): implement missing methods in Rs2TileObjectModel --- .../util/tileobject/Rs2TileObjectModel.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tileobject/Rs2TileObjectModel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tileobject/Rs2TileObjectModel.java index f67e90dc374..105e4ccffaa 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tileobject/Rs2TileObjectModel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tileobject/Rs2TileObjectModel.java @@ -140,7 +140,19 @@ public String getName() { return tileObject.getClickbox(); } - public ObjectComposition getObjectComposition() { + @Override + public @Nullable String getOpOverride(int index) + { + return tileObject.getOpOverride(index); + } + + @Override + public boolean isOpShown(int index) + { + return tileObject.isOpShown(index); + } + + public ObjectComposition getObjectComposition() { return Microbot.getClientThread().invoke(() -> { ObjectComposition composition = Microbot.getClient().getObjectDefinition(tileObject.getId()); if(composition.getImpostorIds() != null) From f3b6c2c7cf4287b385790d20622a9efea02f4de0 Mon Sep 17 00:00:00 2001 From: Loze-Put <85952904+Loze-Put@users.noreply.github.com> Date: Sun, 30 Nov 2025 01:07:23 +0100 Subject: [PATCH 32/44] blast furnace: add lead and nickel bars --- .../net/runelite/client/plugins/blastfurnace/BarsOres.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/blastfurnace/BarsOres.java b/runelite-client/src/main/java/net/runelite/client/plugins/blastfurnace/BarsOres.java index 55d7c5ae505..9e306bcc75e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/blastfurnace/BarsOres.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/blastfurnace/BarsOres.java @@ -43,6 +43,8 @@ enum BarsOres RUNITE_ORE(VarbitID.BLAST_FURNACE_RUNITE_ORE, ItemID.RUNITE_ORE), SILVER_ORE(VarbitID.BLAST_FURNACE_SILVER_ORE, ItemID.SILVER_ORE), GOLD_ORE(VarbitID.BLAST_FURNACE_GOLD_ORE, ItemID.GOLD_ORE), + LEAD_ORE(VarbitID.BLAST_FURNACE_LEAD_ORE, ItemID.LEAD_ORE), + NICKEL_ORE(VarbitID.BLAST_FURNACE_NICKEL_ORE, ItemID.NICKEL_ORE), BRONZE_BAR(VarbitID.BLAST_FURNACE_BRONZE_BARS, ItemID.BRONZE_BAR), IRON_BAR(VarbitID.BLAST_FURNACE_IRON_BARS, ItemID.IRON_BAR), STEEL_BAR(VarbitID.BLAST_FURNACE_STEEL_BARS, ItemID.STEEL_BAR), @@ -50,7 +52,9 @@ enum BarsOres ADAMANTITE_BAR(VarbitID.BLAST_FURNACE_ADAMANTITE_BARS, ItemID.ADAMANTITE_BAR), RUNITE_BAR(VarbitID.BLAST_FURNACE_RUNITE_BARS, ItemID.RUNITE_BAR), SILVER_BAR(VarbitID.BLAST_FURNACE_SILVER_BARS, ItemID.SILVER_BAR), - GOLD_BAR(VarbitID.BLAST_FURNACE_GOLD_BARS, ItemID.GOLD_BAR); + GOLD_BAR(VarbitID.BLAST_FURNACE_GOLD_BARS, ItemID.GOLD_BAR), + LEAD_BAR(VarbitID.BLAST_FURNACE_LEAD_BARS, ItemID.LEAD_BAR), + CUPRONICKEL_BAR(VarbitID.BLAST_FURNACE_CUPRONICKEL_BARS, ItemID.CUPRONICKEL_BAR); @Getter(onMethod_ = {@Varbit}) private final int varbit; From c454f015af6b029949401841dbc2642600018476 Mon Sep 17 00:00:00 2001 From: g-mason0 <19415334+g-mason0@users.noreply.github.com> Date: Sun, 30 Nov 2025 01:38:30 -0500 Subject: [PATCH 33/44] fix(shortest-path): ardougne ship transport issue --- .../client/plugins/microbot/shortestpath/ships.tsv | 8 ++++---- .../client/plugins/microbot/shortestpath/transports.tsv | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/ships.tsv b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/ships.tsv index 027d8474bb7..6e5e350476e 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/ships.tsv +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/ships.tsv @@ -3,11 +3,11 @@ 3029 3217 0 2956 3143 1 Pay-fare;Captain Tobias;3644 30 Coins 10 Musa Point 2956 3146 0 3032 3217 1 Pay-Fare;Customs officer;3648 30 Coins 10 Port Sarim # Ardougne, Brimhaven, Rimmington -2683 3271 0 2775 3233 1 Brimhaven;Captain Barnaby;9250 30 Coins Y 6 Brimhaven -2683 3271 0 2915 3221 1 Rimmington;Captain Barnaby;9250 30 Coins Y 6 Rimmington -2772 3234 0 2683 3268 1 Ardougne;Captain Barnaby;8764 30 Coins Y 6 Ardougne +2673 3275 0 2775 3233 1 Brimhaven;Captain Barnaby;9250 30 Coins Y 6 Brimhaven +2673 3275 0 2915 3221 1 Rimmington;Captain Barnaby;9250 30 Coins Y 6 Rimmington +2772 3234 0 2683 3263 1 Ardougne;Captain Barnaby;8764 30 Coins Y 6 Ardougne 2772 3234 0 2915 3221 1 Rimmington;Captain Barnaby;8764 30 Coins Y 6 Rimmington -2915 3225 0 2683 3268 1 Ardougne;Captain Barnaby;8763 30 Coins Y 6 Ardougne +2915 3225 0 2683 3263 1 Ardougne;Captain Barnaby;8763 30 Coins Y 6 Ardougne 2915 3225 0 2775 3233 1 Brimhaven;Captain Barnaby;8763 30 Coins Y 6 Brimhaven # Rimmington, Corsair Cove 2910 3226 0 2578 2837 1 Travel;Cabin Boy Colin;7967 The Corsair Curse 6 Corsair Cove 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 aa5f7602a2a..9a748fa59e9 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 @@ -600,9 +600,7 @@ 2650 3282 0 2650 3282 1 Climb-up;Ladder;16683 2 2650 3282 1 2650 3282 0 Climb-down;Ladder;16679 2 2649 3282 1 2649 3282 2 Climb-up;Ladder;17026 2 -2649 3282 2 2649 3282 1 Climb-down;Ladder;16685 2 -2683 3271 0 2683 3268 1 Cross;Gangplank;2085 3 -2683 3268 1 2683 3271 0 Cross;Gangplank;2086 3 +2649 3282 2 2649 3282 1 Climb-down;Ladder;16685 2 2622 3291 0 2622 3291 1 Climb-up;Ladder;17026 2 2622 3291 1 2622 3291 0 Climb-down;Ladder;16685 2 2592 3339 0 2592 3338 0 Open;Door;2054 1 @@ -653,7 +651,9 @@ 2674 3305 0 2674 3306 0 Pick-lock;Door;11719 2 2674 3303 0 2674 3304 0 Open;Door;11720 1 2674 3306 0 2674 3305 0 Open;Door;11719 1 - +2683 3263 1 2683 3266 0 Cross;Gangplank;2086 1 +2683 3266 0 2683 3263 1 Cross;Gangplank;2085 1 + # Witchaven Dungeon 2697 3283 0 2696 9683 0 Climb-down;Old ruin entrance;18270 2696 9683 0 2697 3283 0 Climb-up;Exit;18354 From a8e8d6b4cf92a7e901a5cf0ceab5e6d267a432e8 Mon Sep 17 00:00:00 2001 From: chsami Date: Sun, 30 Nov 2025 22:50:00 +0100 Subject: [PATCH 34/44] fix missing methods --- .../microbot/util/tileobject/Rs2TileObjectModel.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tileobject/Rs2TileObjectModel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tileobject/Rs2TileObjectModel.java index f67e90dc374..ee1ceb37372 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tileobject/Rs2TileObjectModel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tileobject/Rs2TileObjectModel.java @@ -140,6 +140,16 @@ public String getName() { return tileObject.getClickbox(); } + @Override + public @Nullable String getOpOverride(int index) { + return ""; + } + + @Override + public boolean isOpShown(int index) { + return false; + } + public ObjectComposition getObjectComposition() { return Microbot.getClientThread().invoke(() -> { ObjectComposition composition = Microbot.getClient().getObjectDefinition(tileObject.getId()); From ac75ac8473b82da7b412ba5843b1b489bcb57fc6 Mon Sep 17 00:00:00 2001 From: chsami Date: Sun, 30 Nov 2025 23:04:27 +0100 Subject: [PATCH 35/44] refactor(breakhandler): streamline bank interaction and improve world selection logic --- .../breakhandler/breakhandlerv2/BreakHandlerV2Script.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java index 72a6cad8a33..0a1d912951f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java @@ -71,7 +71,6 @@ public BreakHandlerV2Script() { * Run the break handler script */ public boolean run(BreakHandlerV2Config config) { - System.out.println("[DEBUG] run() called on instance #" + instanceId + " (hash: " + System.identityHashCode(this) + ")"); this.config = config; BreakHandlerV2State.setState(BreakHandlerV2State.WAITING_FOR_BREAK); @@ -515,7 +514,8 @@ private int selectWorld() { switch (config.worldSelectionMode()) { case CURRENT_PREFERRED_WORLD: targetWorld = preBreakWorld != -1 ? preBreakWorld : - Rs2WorldUtil.getRandomAccessibleWorld( + Rs2WorldUtil.getRandomAccessibleWorldFromRegion( + region, config.avoidEmptyWorlds(), config.avoidOvercrowdedWorlds(), membersOnly @@ -523,7 +523,8 @@ private int selectWorld() { break; case RANDOM_WORLD: - targetWorld = Rs2WorldUtil.getRandomAccessibleWorld( + targetWorld = Rs2WorldUtil.getRandomAccessibleWorldFromRegion( + region, config.avoidEmptyWorlds(), config.avoidOvercrowdedWorlds(), membersOnly From 846be71e545a904861dae16a18574a271a27ed15 Mon Sep 17 00:00:00 2001 From: chsami Date: Sun, 30 Nov 2025 23:06:02 +0100 Subject: [PATCH 36/44] refactor(breakhandler): streamline bank interaction and improve world selection logic --- .../breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java | 1 - 1 file changed, 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java index 88aa9f72ff6..37463f01bbf 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java @@ -28,7 +28,6 @@ public BreakHandlerV2Overlay(BreakHandlerV2Config config, BreakHandlerV2Script s super(); this.config = config; this.script = script; - System.out.println("[DEBUG] BreakHandlerV2Overlay created with script instance hash: " + System.identityHashCode(script)); setPosition(OverlayPosition.TOP_LEFT); } From 2c8fe9f0888b1ebf29ba108bcabfaf86b8865be1 Mon Sep 17 00:00:00 2001 From: chsami Date: Sun, 30 Nov 2025 23:08:36 +0100 Subject: [PATCH 37/44] style(breakhandler): set preferred size for panel component --- .../breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java index 37463f01bbf..5b3e3840751 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java @@ -29,6 +29,7 @@ public BreakHandlerV2Overlay(BreakHandlerV2Config config, BreakHandlerV2Script s this.config = config; this.script = script; setPosition(OverlayPosition.TOP_LEFT); + panelComponent.setPreferredSize(new Dimension(300, 300)); } @Override @@ -39,6 +40,7 @@ public Dimension render(Graphics2D graphics) { return null; } + panelComponent.getChildren().clear(); // Title From 4f6554025b07adad5bc3bb7cc7b1a6da85012eff Mon Sep 17 00:00:00 2001 From: Jordan Atwood Date: Mon, 1 Dec 2025 12:41:27 -0800 Subject: [PATCH 38/44] worldmap: fix remote island mooring location --- .../net/runelite/client/plugins/worldmap/MooringLocation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MooringLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MooringLocation.java index 57761953381..c6a86c30aba 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MooringLocation.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MooringLocation.java @@ -50,7 +50,7 @@ public enum MooringLocation CAIRN_ISLE("Cairn Isle", 42, new WorldPoint(2749, 2951, 0)), CHINCHOMPA_ISLAND("Chinchompa Island", 42, new WorldPoint(1892, 3429, 0)), SUNSET_COAST("Sunset Coast", 44, new WorldPoint(1511, 2975, 0)), - REMOTE_ISLAND("Remote Island", 45, new WorldPoint(2791, 2603, 0)), + REMOTE_ISLAND("Remote Island", 45, new WorldPoint(2971, 2603, 0)), THE_SUMMER_SHORE("The Summer Shore", 45, new WorldPoint(3174, 2367, 0)), THE_LITTLE_PEARL("The Little Pearl", 45, new WorldPoint(3354, 2216, 0)), ALDARIN("Aldarin", 46, new WorldPoint(1452, 2970, 0)), From 31c94dea87f66c282b9e8946729c1182ea20b80f Mon Sep 17 00:00:00 2001 From: Ciaran Moran Date: Mon, 1 Dec 2025 18:51:24 +0000 Subject: [PATCH 39/44] woodcutting: Add sailing trees --- .../client/plugins/woodcutting/WoodcuttingPlugin.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingPlugin.java index 13b972f579b..e54cb22e7fb 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingPlugin.java @@ -563,6 +563,12 @@ public void onScriptPreFired(ScriptPreFired scriptPreFired) case ObjectID.FARMING_REDWOOD_TREE_PATCH_1_4: case ObjectID.FARMING_REDWOOD_TREE_PATCH_1_6: case ObjectID.FARMING_REDWOOD_TREE_PATCH_1_8: + + // sailing trees + case ObjectID.JATOBA_TREE_STUMP: + case ObjectID.CAMPHOR_TREE_UPDATE_STUMP: + case ObjectID.IRONWOOD_TREE_UPDATE_STUMP: + case ObjectID.ROSEWOOD_TREE_UPDATE_STUMP: { WorldPoint worldPoint = WorldPoint.fromCoord(locCoord); GameObject gameObject = findObject(worldPoint); From 71a8978ef90786bba0b0ba850e38f20dd14592c3 Mon Sep 17 00:00:00 2001 From: coopermor Date: Mon, 1 Dec 2025 10:29:51 -0800 Subject: [PATCH 40/44] loottracker: Add sailing chests --- .../client/plugins/loottracker/LootTrackerPlugin.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java index dcba540882d..0086322f7ac 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java @@ -204,6 +204,12 @@ public class LootTrackerPlugin extends Plugin put(5422, "Chest (Aldarin Villas)"). put(6550, "Chest (Moon key)"). put(5521, "Chest (Alchemist's signet)"). + put(12073, "Rusty chest"). + put(7470, "Rusty chest"). + put(6187, "Tarnished chest"). + put(6953, "Tarnished chest"). + put(7743, "Reinforced chest"). + put(8758, "Reinforced chest"). build(); // Chests opened with keys from slayer tasks From 632d35a2645846cbc6e18978fb5ee303f0fb0dfd Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 1 Dec 2025 21:34:17 -0500 Subject: [PATCH 41/44] Revert "gpu: simplify back-facing cull test" This reverts commit 7f89baff52c9b5dc23fab135f4360e371a1ecb8f. --- .../plugins/gpu/FacePrioritySorter.java | 33 +++++++++++-------- .../client/plugins/gpu/GpuPlugin.java | 2 +- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/FacePrioritySorter.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/FacePrioritySorter.java index 62f09394dff..2503de974a4 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/FacePrioritySorter.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/FacePrioritySorter.java @@ -26,6 +26,7 @@ import java.nio.IntBuffer; import java.util.Arrays; +import net.runelite.api.Client; import net.runelite.api.Model; import net.runelite.api.Perspective; import net.runelite.api.Projection; @@ -36,8 +37,8 @@ class FacePrioritySorter static final char[] distanceFaceCount; static final char[][] distanceToFaces; - private static final float[] modelProjectedX; - private static final float[] modelProjectedY; + private static final float[] modelCanvasX; + private static final float[] modelCanvasY; static final float[] modelLocalX; static final float[] modelLocalY; @@ -60,8 +61,8 @@ class FacePrioritySorter distanceFaceCount = new char[MAX_DIAMETER]; distanceToFaces = new char[MAX_DIAMETER][ZSORT_GROUP_SIZE]; - modelProjectedX = new float[MAX_VERTEX_COUNT]; - modelProjectedY = new float[MAX_VERTEX_COUNT]; + modelCanvasX = new float[MAX_VERTEX_COUNT]; + modelCanvasY = new float[MAX_VERTEX_COUNT]; modelLocalX = new float[MAX_VERTEX_COUNT]; modelLocalY = new float[MAX_VERTEX_COUNT]; @@ -74,10 +75,12 @@ class FacePrioritySorter orderedFaces = new int[12][MAX_FACES_PER_PRIORITY]; } + private final Client client; private final SceneUploader sceneUploader; - FacePrioritySorter(SceneUploader sceneUploader) + FacePrioritySorter(Client client, SceneUploader sceneUploader) { + this.client = client; this.sceneUploader = sceneUploader; } @@ -96,6 +99,10 @@ int uploadSortedModel(Projection proj, Model model, int orientation, int x, int final int[] faceColors3 = model.getFaceColors3(); final byte[] faceRenderPriorities = model.getFaceRenderPriorities(); + final int centerX = client.getCenterX(); + final int centerY = client.getCenterY(); + final int zoom = client.get3dZoom(); + float orientSine = 0; float orientCosine = 0; if (orientation != 0) @@ -135,8 +142,8 @@ int uploadSortedModel(Projection proj, Model model, int orientation, int x, int return 0; } - modelProjectedX[v] = p[0]; - modelProjectedY[v] = p[1]; + modelCanvasX[v] = centerX + p[0] * zoom / p[2]; + modelCanvasY[v] = centerY + p[1] * zoom / p[2]; distances[v] = (int) p[2] - zero; } @@ -158,12 +165,12 @@ int uploadSortedModel(Projection proj, Model model, int orientation, int x, int final int v3 = indices3[i]; final float - aX = modelProjectedX[v1], - aY = modelProjectedY[v1], - bX = modelProjectedX[v2], - bY = modelProjectedY[v2], - cX = modelProjectedX[v3], - cY = modelProjectedY[v3]; + aX = modelCanvasX[v1], + aY = modelCanvasY[v1], + bX = modelCanvasX[v2], + bY = modelCanvasY[v2], + cX = modelCanvasX[v3], + cY = modelCanvasY[v3]; if ((aX - bX) * (cY - bY) - (cX - bX) * (aY - bY) > 0) { 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 24c67b947dc..42025015657 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 @@ -275,7 +275,7 @@ protected void startUp() subs = new SceneContext[MAX_WORLDVIEWS]; clientUploader = new SceneUploader(renderCallbackManager); mapUploader = new SceneUploader(renderCallbackManager); - facePrioritySorter = new FacePrioritySorter(clientUploader); + facePrioritySorter = new FacePrioritySorter(client, clientUploader); clientThread.invoke(() -> { try From 7b061197796ebf154639d357617aed5f33937691 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 1 Dec 2025 21:38:10 -0500 Subject: [PATCH 42/44] gpu: simplify back-facing cull test --- .../plugins/gpu/FacePrioritySorter.java | 33 ++++++++----------- .../client/plugins/gpu/GpuPlugin.java | 2 +- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/FacePrioritySorter.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/FacePrioritySorter.java index 2503de974a4..d14f007150f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/FacePrioritySorter.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/FacePrioritySorter.java @@ -26,7 +26,6 @@ import java.nio.IntBuffer; import java.util.Arrays; -import net.runelite.api.Client; import net.runelite.api.Model; import net.runelite.api.Perspective; import net.runelite.api.Projection; @@ -37,8 +36,8 @@ class FacePrioritySorter static final char[] distanceFaceCount; static final char[][] distanceToFaces; - private static final float[] modelCanvasX; - private static final float[] modelCanvasY; + private static final float[] modelProjectedX; + private static final float[] modelProjectedY; static final float[] modelLocalX; static final float[] modelLocalY; @@ -61,8 +60,8 @@ class FacePrioritySorter distanceFaceCount = new char[MAX_DIAMETER]; distanceToFaces = new char[MAX_DIAMETER][ZSORT_GROUP_SIZE]; - modelCanvasX = new float[MAX_VERTEX_COUNT]; - modelCanvasY = new float[MAX_VERTEX_COUNT]; + modelProjectedX = new float[MAX_VERTEX_COUNT]; + modelProjectedY = new float[MAX_VERTEX_COUNT]; modelLocalX = new float[MAX_VERTEX_COUNT]; modelLocalY = new float[MAX_VERTEX_COUNT]; @@ -75,12 +74,10 @@ class FacePrioritySorter orderedFaces = new int[12][MAX_FACES_PER_PRIORITY]; } - private final Client client; private final SceneUploader sceneUploader; - FacePrioritySorter(Client client, SceneUploader sceneUploader) + FacePrioritySorter(SceneUploader sceneUploader) { - this.client = client; this.sceneUploader = sceneUploader; } @@ -99,10 +96,6 @@ int uploadSortedModel(Projection proj, Model model, int orientation, int x, int final int[] faceColors3 = model.getFaceColors3(); final byte[] faceRenderPriorities = model.getFaceRenderPriorities(); - final int centerX = client.getCenterX(); - final int centerY = client.getCenterY(); - final int zoom = client.get3dZoom(); - float orientSine = 0; float orientCosine = 0; if (orientation != 0) @@ -142,8 +135,8 @@ int uploadSortedModel(Projection proj, Model model, int orientation, int x, int return 0; } - modelCanvasX[v] = centerX + p[0] * zoom / p[2]; - modelCanvasY[v] = centerY + p[1] * zoom / p[2]; + modelProjectedX[v] = p[0] / p[2]; + modelProjectedY[v] = p[1] / p[2]; distances[v] = (int) p[2] - zero; } @@ -165,12 +158,12 @@ int uploadSortedModel(Projection proj, Model model, int orientation, int x, int final int v3 = indices3[i]; final float - aX = modelCanvasX[v1], - aY = modelCanvasY[v1], - bX = modelCanvasX[v2], - bY = modelCanvasY[v2], - cX = modelCanvasX[v3], - cY = modelCanvasY[v3]; + aX = modelProjectedX[v1], + aY = modelProjectedY[v1], + bX = modelProjectedX[v2], + bY = modelProjectedY[v2], + cX = modelProjectedX[v3], + cY = modelProjectedY[v3]; if ((aX - bX) * (cY - bY) - (cX - bX) * (aY - bY) > 0) { 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 42025015657..24c67b947dc 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 @@ -275,7 +275,7 @@ protected void startUp() subs = new SceneContext[MAX_WORLDVIEWS]; clientUploader = new SceneUploader(renderCallbackManager); mapUploader = new SceneUploader(renderCallbackManager); - facePrioritySorter = new FacePrioritySorter(client, clientUploader); + facePrioritySorter = new FacePrioritySorter(clientUploader); clientThread.invoke(() -> { try From b9437d297e613f4ae4d42276db12272445c10f63 Mon Sep 17 00:00:00 2001 From: chsami Date: Tue, 2 Dec 2025 10:13:31 +0100 Subject: [PATCH 43/44] feat(breakhandler): update window title during breaks and add duration formatting --- .../breakhandlerv2/BreakHandlerV2Script.java | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java index 0a1d912951f..613d3a618f4 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java @@ -12,10 +12,12 @@ import net.runelite.client.plugins.microbot.util.security.Login; import net.runelite.client.plugins.microbot.util.security.LoginManager; import net.runelite.client.plugins.microbot.util.world.Rs2WorldUtil; +import net.runelite.client.ui.ClientUI; import net.runelite.http.api.worlds.WorldRegion; import javax.inject.Singleton; import java.awt.Color; +import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.concurrent.TimeUnit; @@ -52,6 +54,7 @@ public BreakHandlerV2Script() { private int preBreakWorld = -1; private ConfigProfile activeProfile; private boolean unexpectedLogoutDetected = false; + private String originalWindowTitle = ""; // Break duration in milliseconds private long currentBreakDuration = 0; @@ -77,16 +80,18 @@ public boolean run(BreakHandlerV2Config config) { // Initialize next break time immediately to prevent null values in overlay scheduleNextBreak(); log.info("[BreakHandlerV2] Initial break scheduled for {}", nextBreakTime); - + // Load active profile + loadActiveProfile(); + originalWindowTitle = ClientUI.getFrame().getTitle(); mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { if (!super.run()) return; - // Load active profile - loadActiveProfile(); + // Detect unexpected logout while waiting for break detectUnexpectedLogout(); + updateWindowTitle(); // Main state machine switch (BreakHandlerV2State.getCurrentState()) { @@ -523,8 +528,7 @@ private int selectWorld() { break; case RANDOM_WORLD: - targetWorld = Rs2WorldUtil.getRandomAccessibleWorldFromRegion( - region, + targetWorld = Rs2WorldUtil.getRandomAccessibleWorld( config.avoidEmptyWorlds(), config.avoidOvercrowdedWorlds(), membersOnly @@ -632,7 +636,6 @@ private void sendDiscordNotification(String title, String message) { * Get time until next break in seconds */ public long getTimeUntilBreak() { - System.out.println("[DEBUG] getTimeUntilBreak() called on instance #" + instanceId + " (hash: " + System.identityHashCode(this) + "), nextBreakTime: " + nextBreakTime); if (nextBreakTime == null) { return -1; } @@ -668,4 +671,33 @@ public void shutdown() { loginRetryCount = 0; safetyCheckAttempts = 0; } + + private void updateWindowTitle() { + BreakHandlerV2State state = BreakHandlerV2State.getCurrentState(); + + if (getBreakTimeRemaining() > 0) { + ClientUI.getFrame().setTitle(originalWindowTitle + " - " + state.toString() + ": " + + formatDuration(Duration.ofSeconds(Math.max(0, getBreakTimeRemaining())))); + } + } + + /** + * Formats a duration with header text. + */ + public static String formatDuration(Duration duration, String header) { + return String.format(header + " %s", formatDuration(duration)); + } + + /** + * Formats a duration into HH:MM:SS format. + */ + public static String formatDuration(Duration duration) { + if (duration == null || duration.isNegative() || duration.isZero()) { + return "00:00:00"; + } + long hours = duration.toHours(); + long minutes = duration.toMinutes() % 60; + long seconds = duration.getSeconds() % 60; + return String.format("%02d:%02d:%02d", hours, minutes, seconds); + } } From 2a79aa7970cc9fad0fdcf9ca3060b4e49054fd65 Mon Sep 17 00:00:00 2001 From: chsami Date: Tue, 2 Dec 2025 17:21:56 +0100 Subject: [PATCH 44/44] chore(pom): bump microbot version to 2.0.56 --- runelite-client/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml index 76baa325002..f51497766a6 100644 --- a/runelite-client/pom.xml +++ b/runelite-client/pom.xml @@ -41,7 +41,7 @@ nogit false false - 2.0.55 + 2.0.56 nogit