Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
)
@Slf4j
public class FornBirdhouseRunsPlugin extends Plugin {
final static String version = "1.1.2";
final static String version = "1.1.3";
@Provides
FornBirdhouseRunsConfig provideConfig(ConfigManager configManager) {
return configManager.getConfig(FornBirdhouseRunsConfig.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory;
import net.runelite.client.plugins.microbot.util.player.Rs2Player;
import net.runelite.client.plugins.microbot.util.walker.Rs2Walker;
import net.runelite.client.plugins.microbot.shortestpath.Restriction;
import net.runelite.client.plugins.microbot.shortestpath.ShortestPathPlugin;

import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel;

Expand Down Expand Up @@ -147,7 +149,8 @@ public boolean run() {

if (!Rs2Walker.disableTeleports && isOnFossilIsland()) {
Rs2Walker.disableTeleports = true;
log.info("On Fossil Island — disabling teleports for remaining walks");
blockRubberCapMushrooms();
log.info("On Fossil Island — disabling teleports and rubber cap mushrooms for remaining walks");
}

boolean advanced = true;
Expand Down Expand Up @@ -309,12 +312,24 @@ private void emptyNests() {
public void shutdown() {
super.shutdown();
Rs2Walker.disableTeleports = false;
ShortestPathPlugin.getPathfinderConfig().setRestrictedTiles();
initialized = false;
botStatus = states.TELEPORTING;
lastObservedStatus = null;
stateEnteredAtMs = 0L;
}

private static void blockRubberCapMushrooms() {
Restriction[] restrictions = new Restriction[] {
new Restriction(3663, 3808, 0),
new Restriction(3664, 3808, 0),
new Restriction(3665, 3808, 0),
new Restriction(3666, 3809, 0),
new Restriction(3666, 3810, 0)
};
ShortestPathPlugin.getPathfinderConfig().setRestrictedTiles(restrictions);
}

/** Throttle for arrivedAndStill log lines (one per second per target). */
private long lastArrivedLogMs;
private WorldPoint lastArrivedLogTarget;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,6 @@
import javax.inject.Inject;
import java.awt.*;

/**
* =====================================================================
* Butterfly Catcher
* Author: StonksCode
* =====================================================================
*
* Automates butterfly and moth catching for Hunter XP training.
*
* Supported species:
* - Ruby Harvest (net lvl 5 / bare lvl 15)
* - Sapphire Glacialis (net lvl 25 / bare lvl 35)
* - Snowy Knight (net lvl 35 / bare lvl 45)
* - Black Warlock (net lvl 45 / bare lvl 55)
* - Sunlight Moth (net lvl 65 / bare lvl 75)
* - Moonlight Moth (net lvl 75 / bare lvl 85)
*
* Catch modes:
* BAREHANDED — catch and release for XP; nothing enters inventory.
* BUTTERFLY_NET — equip a butterfly net or magic butterfly net before
* starting; allows catching at 10 levels lower than
* the barehanded requirement.
*
* Usage:
* 1. Stand near a spawn of your chosen species.
* 2. If using Butterfly Net mode, equip your net first.
* 3. Select your species and catch mode in the config panel.
* 4. Start the plugin — it will run indefinitely with no banking.
* =====================================================================
*/
@PluginDescriptor(
name = PluginConstants.STKS + "Butterfly Catcher",
description = "Automates butterfly and moth catching for Hunter XP. Stand near a spawn, pick your species and mode, start the plugin.",
Expand All @@ -52,7 +23,10 @@
@Slf4j
public class ButterflyCatcherPlugin extends Plugin {

public static final String version = "1.0.0";
// v1.0.1 — fix: corrected NPC IDs for Ruby Harvest (5556), Sapphire Glacialis (5555),
// and Snowy Knight (5554). Original IDs (5525/5526/5527) were wrong,
// causing those three species to never find targets and do nothing.
public static final String version = "1.0.1";

@Inject
private ButterflyCatcherConfig config;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@
* ButterflyType
*
* Defines all catchable butterflies and moths from the Hunter skill.
* Data sourced from: https://oldschool.runescape.wiki/w/Butterfly_(Hunter)
* NPC IDs verified against: https://oldschool.runescape.wiki/w/Butterfly_(Hunter)
*
* Level requirements:
* The game requires a Hunter level 10 ABOVE the net level for barehanded catching.
* This enum stores the NET level as the baseline; the script derives the
* barehanded level by adding 10.
*
* Species Net Barehanded
* ─────────────────── ──── ──────────
* Ruby Harvest 5 15
* Sapphire Glacialis 25 35
* Snowy Knight 35 45
* Black Warlock 45 55
* Sunlight Moth 65 75
* Moonlight Moth 75 85
* Species Net Barehanded NPC ID
* ─────────────────── ──── ────────── ──────
* Ruby Harvest 5 15 5556
* Sapphire Glacialis 25 35 5555
* Snowy Knight 35 45 5554
* Black Warlock 45 55 5553
* Sunlight Moth 65 75 12770
* Moonlight Moth 75 85 12771, 12772, 12773
*
* Item IDs:
* Butterfly net : 10010
Expand All @@ -32,28 +32,28 @@ public enum ButterflyType {

RUBY_HARVEST(
"Ruby Harvest",
new int[]{ 5525 },
new int[]{ 5556 }, // was 5525 (wrong — wiki confirms 5556)
5,
10012,
10009
),
SAPPHIRE_GLACIALIS(
"Sapphire Glacialis",
new int[]{ 5526 },
new int[]{ 5555 }, // was 5526 (wrong — wiki confirms 5555)
25,
10012,
10011
),
SNOWY_KNIGHT(
"Snowy Knight",
new int[]{ 5527 },
new int[]{ 5554 }, // was 5527 (wrong — wiki confirms 5554)
35,
10012,
10013
),
BLACK_WARLOCK(
"Black Warlock",
new int[]{ 5553 },
new int[]{ 5553 }, // correct
45,
10012,
10010
Expand All @@ -65,7 +65,7 @@ public enum ButterflyType {
*/
SUNLIGHT_MOTH(
"Sunlight Moth",
new int[]{ 12770 },
new int[]{ 12770 }, // correct
65,
10012,
28890
Expand All @@ -78,7 +78,7 @@ public enum ButterflyType {
*/
MOONLIGHT_MOTH(
"Moonlight Moth",
new int[]{ 12771, 12772, 12773 },
new int[]{ 12771, 12772, 12773 }, // correct
75,
10012,
28893
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
@PluginDependency(ClueScrollPlugin.class)
public class ClueSolverPlugin extends Plugin {

final static String version = "1.0.2";
final static String version = "1.0.5";
@Inject
private ClueSolverScript clueSolverScript;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.Player;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.GameTick;
import net.runelite.client.eventbus.EventBus;
Expand Down Expand Up @@ -79,15 +78,16 @@ public void onGameTick(GameTick event) {
}

private void processGameTick(GameTick event) {
Player player = client.getLocalPlayer();
if (player == null)
return;
// v1.0.5 fix: client-thread-safe location read. processGameTick runs in the
// background executor; direct client.getLocalPlayer() throws IllegalStateException.
net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe();
if (playerLocation == null) return;

switch (state) {
case WALKING_TO_LOCATION:
if (hasArrived(player)) {
if (playerLocation.equals(location)) {
transitionToInteractionState();
} else if (isWithinRadius(location, player.getWorldLocation(), 3)) {
} else if (isWithinRadius(location, playerLocation, 3)) {
Rs2Walker.walkFastCanvas(location);
}
break;
Expand Down Expand Up @@ -196,10 +196,6 @@ private boolean handleDialogue() {
return false;
}

private boolean hasArrived(Player player) {
return player.getWorldLocation().equals(location);
}

private boolean isWithinRadius(WorldPoint targetLocation, WorldPoint playerLocation, int radius) {
int deltaX = Math.abs(targetLocation.getX() - playerLocation.getX());
int deltaY = Math.abs(targetLocation.getY() - playerLocation.getY());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.coords.WorldPoint;
import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin;
import net.runelite.client.plugins.microbot.Microbot;
import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin;

import java.util.concurrent.CompletableFuture;
Expand Down Expand Up @@ -65,4 +67,19 @@ protected void completeTask(boolean success) {
protected boolean preTaskCheck() {
return client != null && client.getLocalPlayer() != null;
}

/**
* v1.0.3 fix: thread-safe player location read.
*
* <p>Subclasses submit {@code processGameTick} to a background executor, so direct
* {@code client.getLocalPlayer().getWorldLocation()} calls from those code paths throw
* {@code IllegalStateException: must be called on client thread}. Use this helper instead --
* it hops to the client thread, reads the location, and returns null if the player isn't available.
*/
protected WorldPoint getPlayerLocationSafe() {
return Microbot.getClientThread().runOnClientThreadOptional(() -> {
if (client == null || client.getLocalPlayer() == null) return null;
return client.getLocalPlayer().getWorldLocation();
}).orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.ItemID;
import net.runelite.api.NPC;
import net.runelite.api.Player;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.GameTick;
import net.runelite.client.eventbus.EventBus;
Expand Down Expand Up @@ -95,12 +93,13 @@ public void onGameTick(GameTick event) {
}

private void processGameTick(GameTick event) {
Player player = client.getLocalPlayer();
if (player == null) return;
// v1.0.5 fix: client-thread-safe location read.
net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe();
if (playerLocation == null) return;

switch (state) {
case WALKING_TO_LOCATION:
if (isWithinRadius(location, player.getWorldLocation(), 5)) {
if (isWithinRadius(location, playerLocation, 5)) {
log.info("Arrived at coordinate clue location.");
state = (enemy != null) ? State.FIGHTING_ENEMY : State.DIGGING;
}
Expand Down Expand Up @@ -153,8 +152,10 @@ private boolean waitForEnemyDefeat(Rs2NpcModel targetNpc) {
}

private boolean prepareToDig() {
Player player = client.getLocalPlayer();
if (!isWithinRadius(location, player.getWorldLocation(), 1)) {
// v1.0.5 fix: client-thread-safe location read. Called from processGameTick (background executor).
net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe();
if (playerLocation == null) return false;
if (!isWithinRadius(location, playerLocation, 1)) {
log.info("Adjusting position to exact location.");
Rs2Walker.walkFastCanvas(location);
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.NPC;
import net.runelite.api.Player;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.GameTick;
import net.runelite.client.eventbus.EventBus;
Expand Down Expand Up @@ -85,8 +83,14 @@ public void onGameTick(GameTick event) {
}

private void processGameTick(GameTick event) {
Player player = client.getLocalPlayer();
WorldPoint playerLocation = player.getWorldLocation();
// v1.0.3 fix: read player location via the client-thread-safe helper instead of
// calling client.getLocalPlayer().getWorldLocation() directly from the background
// executor (which throws IllegalStateException: must be called on client thread).
WorldPoint playerLocation = getPlayerLocationSafe();
if (playerLocation == null) {
log.debug("Player location unavailable; will retry next tick");
return;
}
WorldPoint clueLocation = clue.getLocation(clueScrollPlugin);

switch (state) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,15 @@ private void processGameTick(GameTick event) {

private void handleWalkingToLocation() {
WorldPoint location = clue.getLocation(clueScrollPlugin);
if (client.getLocalPlayer().getWorldLocation().equals(location)) {
// v1.0.3 fix: read player location via the client-thread-safe helper instead of
// calling client.getLocalPlayer().getWorldLocation() directly from the background
// executor (which throws IllegalStateException: must be called on client thread).
WorldPoint playerLocation = getPlayerLocationSafe();
if (playerLocation == null) {
log.debug("Player location unavailable; will retry next tick");
return;
}
if (playerLocation.equals(location)) {
log.info("Arrived at Emote Clue location.");
state = State.PERFORMING_EMOTES;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.NPC;
import net.runelite.api.Player;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.GameTick;
import net.runelite.client.eventbus.EventBus;
Expand Down Expand Up @@ -85,12 +83,13 @@ public void onGameTick(GameTick event) {
}

private void processGameTick(GameTick event) {
Player player = client.getLocalPlayer();
if (player == null) return;
// v1.0.5 fix: client-thread-safe location read.
net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe();
if (playerLocation == null) return;

switch (state) {
case WALKING_TO_LOCATION:
if (isWithinRadius(location, player.getWorldLocation(), 5)) {
if (isWithinRadius(location, playerLocation, 5)) {
log.info("Arrived at music clue location.");
state = State.PLAYING_SONG;
}
Expand Down
Loading
Loading