diff --git a/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java b/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java index acd9b74c54..fb1291273a 100644 --- a/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java +++ b/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java @@ -305,6 +305,40 @@ public class WorldPoint return Math.max(Math.abs(getX() - other.getX()), Math.abs(getY() - other.getY())); } + /** + * Gets the straight-line distance between this point and another. + *

+ * If the other point is not on the same plane, this method will return + * {@link Float#MAX_VALUE}. If ignoring the plane is wanted, use the + * {@link #distanceTo2DHypotenuse(WorldPoint)} method. + * + * @param other other point + * @return the straight-line distance + */ + public float distanceToHypotenuse(WorldPoint other) + { + if (other.plane != plane) + { + return Float.MAX_VALUE; + } + + return distanceTo2DHypotenuse(other); + } + + /** + * Find the straight-line distance from this point to another point. + *

+ * This method disregards the plane value of the two tiles and returns + * the simple distance between the X-Z coordinate pairs. + * + * @param other other point + * @return the straight-line distance + */ + public float distanceTo2DHypotenuse(WorldPoint other) + { + return (float) Math.hypot(getX() - other.getX(), getY() - other.getY()); + } + /** * Converts the passed scene coordinates to a world space */ diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java index a551a2272b..6638b1326a 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java @@ -91,6 +91,7 @@ public class WidgetID public static final int BARROWS_REWARD_GROUP_ID = 155; public static final int RAIDS_GROUP_ID = 513; public static final int MOTHERLODE_MINE_GROUP_ID = 382; + public static final int MOTHERLODE_MINE_FULL_INVENTORY_GROUP_ID = 229; public static final int EXPERIENCE_DROP_GROUP_ID = 122; public static final int PUZZLE_BOX_GROUP_ID = 306; public static final int LIGHT_BOX_GROUP_ID = 322; 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 dd7951586f..e0d3925cb6 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 @@ -99,17 +99,6 @@ import static net.runelite.api.AnimationID.MINING_DRAGON_PICKAXE_ORN; import static net.runelite.api.AnimationID.MINING_INFERNAL_PICKAXE; import static net.runelite.api.AnimationID.MINING_IRON_PICKAXE; import static net.runelite.api.AnimationID.MINING_MITHRIL_PICKAXE; -import static net.runelite.api.AnimationID.MINING_MOTHERLODE_3A; -import static net.runelite.api.AnimationID.MINING_MOTHERLODE_ADAMANT; -import static net.runelite.api.AnimationID.MINING_MOTHERLODE_BLACK; -import static net.runelite.api.AnimationID.MINING_MOTHERLODE_BRONZE; -import static net.runelite.api.AnimationID.MINING_MOTHERLODE_DRAGON; -import static net.runelite.api.AnimationID.MINING_MOTHERLODE_DRAGON_ORN; -import static net.runelite.api.AnimationID.MINING_MOTHERLODE_INFERNAL; -import static net.runelite.api.AnimationID.MINING_MOTHERLODE_IRON; -import static net.runelite.api.AnimationID.MINING_MOTHERLODE_MITHRIL; -import static net.runelite.api.AnimationID.MINING_MOTHERLODE_RUNE; -import static net.runelite.api.AnimationID.MINING_MOTHERLODE_STEEL; import static net.runelite.api.AnimationID.MINING_RUNE_PICKAXE; import static net.runelite.api.AnimationID.MINING_STEEL_PICKAXE; import static net.runelite.api.AnimationID.PISCARILIUS_CRANE_REPAIR; @@ -295,19 +284,7 @@ public class IdleNotifierPlugin extends Plugin case MINING_3A_PICKAXE: case DENSE_ESSENCE_CHIPPING: case DENSE_ESSENCE_CHISELING: - /* Mining(Motherlode) */ - case MINING_MOTHERLODE_BRONZE: - case MINING_MOTHERLODE_IRON: - case MINING_MOTHERLODE_STEEL: - case MINING_MOTHERLODE_BLACK: - case MINING_MOTHERLODE_MITHRIL: - case MINING_MOTHERLODE_ADAMANT: - case MINING_MOTHERLODE_RUNE: - case MINING_MOTHERLODE_DRAGON: - case MINING_MOTHERLODE_DRAGON_ORN: - case MINING_MOTHERLODE_INFERNAL: - case MINING_MOTHERLODE_3A: - /* Herblore */ + /* Herblore */ case HERBLORE_PESTLE_AND_MORTAR: case HERBLORE_POTIONMAKING: case HERBLORE_MAKE_TAR: diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningOverlay.java index 3c9ab79234..87e4e8a897 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningOverlay.java @@ -31,6 +31,8 @@ import java.time.Instant; import java.util.Iterator; import java.util.List; import javax.inject.Inject; +import lombok.AccessLevel; +import lombok.Getter; import net.runelite.api.Client; import net.runelite.api.Perspective; import net.runelite.api.Point; @@ -43,6 +45,13 @@ import net.runelite.client.ui.overlay.components.ProgressPieComponent; class MiningOverlay extends Overlay { + // Range of Motherlode vein respawn time not 100% confirmed but based on observation + @Getter(AccessLevel.PACKAGE) + private static final int ORE_VEIN_MAX_RESPAWN_TIME = 123; + private static final int ORE_VEIN_MIN_RESPAWN_TIME = 90; + private static final float ORE_VEIN_RANDOM_PERCENT_THRESHOLD = (float) ORE_VEIN_MIN_RESPAWN_TIME / ORE_VEIN_MAX_RESPAWN_TIME; + private static final Color DARK_GREEN = new Color(0, 100, 0); + private final Client client; private final MiningPlugin plugin; @@ -67,6 +76,8 @@ class MiningOverlay extends Overlay Instant now = Instant.now(); for (Iterator it = respawns.iterator(); it.hasNext();) { + Color pieFillColor = Color.YELLOW; + Color pieBorderColor = Color.ORANGE; RockRespawn rockRespawn = it.next(); float percent = (now.toEpochMilli() - rockRespawn.getStartTime().toEpochMilli()) / (float) rockRespawn.getRespawnTime(); WorldPoint worldPoint = rockRespawn.getWorldPoint(); @@ -84,9 +95,16 @@ class MiningOverlay extends Overlay continue; } + // Recolour pie on motherlode veins during the portion of the timer where they may respawn + if (rockRespawn.getRock() == Rock.ORE_VEIN && percent > ORE_VEIN_RANDOM_PERCENT_THRESHOLD) + { + pieFillColor = Color.GREEN; + pieBorderColor = DARK_GREEN; + } + ProgressPieComponent ppc = new ProgressPieComponent(); - ppc.setBorderColor(Color.ORANGE); - ppc.setFill(Color.YELLOW); + ppc.setBorderColor(pieBorderColor); + ppc.setFill(pieFillColor); ppc.setPosition(point); ppc.setProgress(percent); ppc.render(graphics); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningPlugin.java index cd2feb16cc..4bc5232701 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/mining/MiningPlugin.java @@ -39,7 +39,12 @@ import static net.runelite.api.ObjectID.DEPLETED_VEIN_26666; import static net.runelite.api.ObjectID.DEPLETED_VEIN_26667; import static net.runelite.api.ObjectID.DEPLETED_VEIN_26668; import static net.runelite.api.ObjectID.EMPTY_WALL; +import static net.runelite.api.ObjectID.ORE_VEIN_26661; +import static net.runelite.api.ObjectID.ORE_VEIN_26662; +import static net.runelite.api.ObjectID.ORE_VEIN_26663; +import static net.runelite.api.ObjectID.ORE_VEIN_26664; import net.runelite.api.WallObject; +import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.GameObjectDespawned; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.GameTick; @@ -158,6 +163,15 @@ public class MiningPlugin extends Plugin respawns.add(rockRespawn); break; } + case ORE_VEIN_26661: // Motherlode vein + case ORE_VEIN_26662: // Motherlode vein + case ORE_VEIN_26663: // Motherlode vein + case ORE_VEIN_26664: // Motherlode vein + { + final WorldPoint point = object.getWorldLocation(); + respawns.removeIf(rockRespawn -> rockRespawn.getWorldPoint().equals(point)); + break; + } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mining/Rock.java b/runelite-client/src/main/java/net/runelite/client/plugins/mining/Rock.java index cfa52372be..7f27596356 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/mining/Rock.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/mining/Rock.java @@ -98,7 +98,7 @@ enum Rock return inMiningGuild ? Duration.ofMinutes(6) : super.respawnTime; } }, - ORE_VEIN(Duration.ofSeconds(108), 150), + ORE_VEIN(Duration.ofSeconds(MiningOverlay.getORE_VEIN_MAX_RESPAWN_TIME()), 150), AMETHYST(Duration.ofSeconds(75), 120); private static final Map ROCKS; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeConfig.java index 10d41810e4..bc4f4269ec 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeConfig.java @@ -121,4 +121,25 @@ public interface MotherlodeConfig extends Config { return true; } + + @ConfigItem( + keyName = "notifyOnIdle", + name = "Idle notification", + description = "Sends a notification when the player stops mining" + ) + default boolean notifyOnIdle() + { + return false; + } + + @ConfigItem( + keyName = "showTargetVein", + name = "Show vein currently being mined", + description = "Highlights the vein currently being mined" + ) + default boolean showTargetVein() + { + return false; + } + } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeOverlay.java index 946b7b9c56..c43e012bfb 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeOverlay.java @@ -24,15 +24,12 @@ */ package net.runelite.client.plugins.motherlode; -import com.google.common.collect.ImmutableSet; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.time.Duration; import java.time.Instant; -import java.util.Set; import javax.inject.Inject; -import static net.runelite.api.AnimationID.*; import net.runelite.api.Client; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayPosition; @@ -43,13 +40,6 @@ import net.runelite.client.ui.overlay.components.table.TableComponent; class MotherlodeOverlay extends Overlay { - private static final Set MINING_ANIMATION_IDS = ImmutableSet.of( - MINING_MOTHERLODE_BRONZE, MINING_MOTHERLODE_IRON, MINING_MOTHERLODE_STEEL, - MINING_MOTHERLODE_BLACK, MINING_MOTHERLODE_MITHRIL, MINING_MOTHERLODE_ADAMANT, - MINING_MOTHERLODE_RUNE, MINING_MOTHERLODE_DRAGON, MINING_MOTHERLODE_DRAGON_ORN, - MINING_MOTHERLODE_INFERNAL - ); - private final Client client; private final MotherlodePlugin plugin; private final MotherlodeSession motherlodeSession; @@ -94,7 +84,7 @@ class MotherlodeOverlay extends Overlay if (config.showMiningState()) { - if (MINING_ANIMATION_IDS.contains(client.getLocalPlayer().getAnimation())) + if (plugin.isMining()) { panelComponent.getChildren().add(TitleComponent.builder() .text("Mining") diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodePlugin.java index a6f4e7778c..6f2d748c91 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodePlugin.java @@ -40,6 +40,19 @@ import java.util.Set; import javax.inject.Inject; import lombok.AccessLevel; import lombok.Getter; +import net.runelite.api.AnimationID; +import static net.runelite.api.AnimationID.IDLE; +import static net.runelite.api.AnimationID.MINING_MOTHERLODE_3A; +import static net.runelite.api.AnimationID.MINING_MOTHERLODE_ADAMANT; +import static net.runelite.api.AnimationID.MINING_MOTHERLODE_BLACK; +import static net.runelite.api.AnimationID.MINING_MOTHERLODE_BRONZE; +import static net.runelite.api.AnimationID.MINING_MOTHERLODE_DRAGON; +import static net.runelite.api.AnimationID.MINING_MOTHERLODE_DRAGON_ORN; +import static net.runelite.api.AnimationID.MINING_MOTHERLODE_INFERNAL; +import static net.runelite.api.AnimationID.MINING_MOTHERLODE_IRON; +import static net.runelite.api.AnimationID.MINING_MOTHERLODE_MITHRIL; +import static net.runelite.api.AnimationID.MINING_MOTHERLODE_RUNE; +import static net.runelite.api.AnimationID.MINING_MOTHERLODE_STEEL; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.GameObject; @@ -48,6 +61,11 @@ import net.runelite.api.InventoryID; import net.runelite.api.Item; import net.runelite.api.ItemContainer; import net.runelite.api.ItemID; +import net.runelite.api.MenuAction; +import static net.runelite.api.ObjectID.DEPLETED_VEIN_26665; +import static net.runelite.api.ObjectID.DEPLETED_VEIN_26666; +import static net.runelite.api.ObjectID.DEPLETED_VEIN_26667; +import static net.runelite.api.ObjectID.DEPLETED_VEIN_26668; import static net.runelite.api.ObjectID.ORE_VEIN_26661; import static net.runelite.api.ObjectID.ORE_VEIN_26662; import static net.runelite.api.ObjectID.ORE_VEIN_26663; @@ -55,21 +73,29 @@ import static net.runelite.api.ObjectID.ORE_VEIN_26664; import static net.runelite.api.ObjectID.ROCKFALL; import static net.runelite.api.ObjectID.ROCKFALL_26680; import net.runelite.api.Perspective; +import net.runelite.api.Player; import net.runelite.api.Varbits; import net.runelite.api.WallObject; import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.AnimationChanged; import net.runelite.api.events.ChatMessage; import net.runelite.api.events.GameObjectChanged; import net.runelite.api.events.GameObjectDespawned; import net.runelite.api.events.GameObjectSpawned; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.ItemContainerChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.events.VarbitChanged; import net.runelite.api.events.WallObjectChanged; import net.runelite.api.events.WallObjectDespawned; import net.runelite.api.events.WallObjectSpawned; +import net.runelite.api.events.WidgetLoaded; import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetID; import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.Notifier; import net.runelite.client.callback.ClientThread; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; @@ -88,6 +114,7 @@ public class MotherlodePlugin extends Plugin { private static final Set MOTHERLODE_MAP_REGIONS = ImmutableSet.of(14679, 14680, 14681, 14935, 14936, 14937, 15191, 15192, 15193); private static final Set MINE_SPOTS = ImmutableSet.of(ORE_VEIN_26661, ORE_VEIN_26662, ORE_VEIN_26663, ORE_VEIN_26664); + private static final Set DEPLETED_SPOTS = ImmutableSet.of(DEPLETED_VEIN_26665, DEPLETED_VEIN_26666, DEPLETED_VEIN_26667, DEPLETED_VEIN_26668); private static final Set MLM_ORE_TYPES = ImmutableSet.of(ItemID.RUNITE_ORE, ItemID.ADAMANTITE_ORE, ItemID.MITHRIL_ORE, ItemID.GOLD_ORE, ItemID.COAL, ItemID.GOLDEN_NUGGET); private static final Set ROCK_OBSTACLES = ImmutableSet.of(ROCKFALL, ROCKFALL_26680); @@ -99,6 +126,10 @@ public class MotherlodePlugin extends Plugin private static final int UPPER_FLOOR_HEIGHT = -500; + // The motherlode mining animation has gaps in it during which the animation switches to IDLE + // so a minimum threshold is required before the idle animation will be registered as not mining + private static final Duration ANIMATION_IDLE_DELAY = Duration.ofMillis(1800); + @Inject private OverlayManager overlayManager; @@ -126,6 +157,9 @@ public class MotherlodePlugin extends Plugin @Inject private ClientThread clientThread; + @Inject + private Notifier notifier; + @Getter(AccessLevel.PACKAGE) private boolean inMlm; @@ -146,6 +180,14 @@ public class MotherlodePlugin extends Plugin @Getter(AccessLevel.PACKAGE) private final Set rocks = new HashSet<>(); + @Getter(AccessLevel.PACKAGE) + private boolean isMining; + @Getter(AccessLevel.PACKAGE) + private WorldPoint targetVeinLocation = null; + private boolean playerHasReachedTargetVein; + private int lastAnimation = AnimationID.IDLE; + private Instant lastAnimating; + @Provides MotherlodeConfig getConfig(ConfigManager configManager) { @@ -278,6 +320,161 @@ public class MotherlodePlugin extends Plugin } } + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked menu) + { + if (!inMlm) + { + return; + } + + if (MINE_SPOTS.contains(menu.getId()) && menu.getMenuAction() == MenuAction.GAME_OBJECT_FIRST_OPTION) + { + resetIdleChecks(); + int veinX = menu.getActionParam(); + int veinY = menu.getWidgetId(); + targetVeinLocation = WorldPoint.fromScene(client, veinX, veinY, client.getPlane()); + } + } + + @Subscribe + public void onGameTick(GameTick event) + { + if (!inMlm) + { + return; + } + + checkDistanceToTargetVein(); + checkAnimationIdle(); + } + + private void checkDistanceToTargetVein() + { + if (targetVeinLocation == null) + { + return; + } + + float distanceFromTargetVein = client.getLocalPlayer().getWorldLocation().distanceToHypotenuse(targetVeinLocation); + // Player must reach the target vein first before we begin checking for the player moving away from it + if (!playerHasReachedTargetVein && distanceFromTargetVein == 1) + { + isMining = true; + playerHasReachedTargetVein = true; + } + else if (playerHasReachedTargetVein && distanceFromTargetVein > 1) + { + isMining = false; + resetIdleChecks(); + } + } + + @Subscribe + public void onAnimationChanged (AnimationChanged event) + { + if (!inMlm) + { + return; + } + + Player localPlayer = client.getLocalPlayer(); + if (localPlayer != event.getActor()) + { + return; + } + + int animation = localPlayer.getAnimation(); + + switch (animation) + { + case MINING_MOTHERLODE_BRONZE: + case MINING_MOTHERLODE_IRON: + case MINING_MOTHERLODE_STEEL: + case MINING_MOTHERLODE_BLACK: + case MINING_MOTHERLODE_MITHRIL: + case MINING_MOTHERLODE_ADAMANT: + case MINING_MOTHERLODE_RUNE: + case MINING_MOTHERLODE_DRAGON: + case MINING_MOTHERLODE_DRAGON_ORN: + case MINING_MOTHERLODE_INFERNAL: + case MINING_MOTHERLODE_3A: + lastAnimation = animation; + lastAnimating = Instant.now(); + break; + case IDLE: + lastAnimating = Instant.now(); + break; + default: + // On unknown animation simply assume the animation is invalid + lastAnimation = IDLE; + lastAnimating = null; + } + } + + private void checkAnimationIdle() + { + if (lastAnimation == IDLE) + { + return; + } + + final int animation = client.getLocalPlayer().getAnimation(); + + if (animation == IDLE) + { + if (lastAnimating != null && Instant.now().compareTo(lastAnimating.plus(ANIMATION_IDLE_DELAY)) >= 0) + { + lastAnimation = IDLE; + lastAnimating = null; + isMining = false; + resetIdleChecks(); + sendIdleNotification(); + } + } + else + { + lastAnimating = Instant.now(); + } + } + + @Subscribe + public void onWidgetLoaded(WidgetLoaded event) + { + if (!inMlm || targetVeinLocation == null) + { + return; + } + + int widgetID = event.getGroupId(); + + if (widgetID == WidgetID.MOTHERLODE_MINE_FULL_INVENTORY_GROUP_ID || widgetID == WidgetID.LEVEL_UP_GROUP_ID) + { + isMining = false; + resetIdleChecks(); + sendIdleNotification(); + } + } + + private void resetIdleChecks() + { + isMining = false; + lastAnimation = IDLE; + lastAnimating = null; + playerHasReachedTargetVein = false; + targetVeinLocation = null; + } + + private void sendIdleNotification() + { + if (!config.notifyOnIdle()) + { + return; + } + + notifier.notify(client.getLocalPlayer().getName() + " has stopped mining!"); + } + @Subscribe public void onWallObjectSpawned(WallObjectSpawned event) { @@ -287,10 +484,17 @@ public class MotherlodePlugin extends Plugin } WallObject wallObject = event.getWallObject(); - if (MINE_SPOTS.contains(wallObject.getId())) + int wallObjectId = wallObject.getId(); + if (MINE_SPOTS.contains(wallObjectId)) { veins.add(wallObject); } + else if (DEPLETED_SPOTS.contains(wallObjectId) && wallObject.getWorldLocation().equals(targetVeinLocation)) + { + isMining = false; + resetIdleChecks(); + sendIdleNotification(); + } } @Subscribe diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeRocksOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeRocksOverlay.java index ddb718ea09..bcd4d2946e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeRocksOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/motherlode/MotherlodeRocksOverlay.java @@ -40,11 +40,13 @@ import net.runelite.api.Point; import net.runelite.api.Skill; import net.runelite.api.WallObject; import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; import net.runelite.client.game.SkillIconManager; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPosition; import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.util.ImageUtil; class MotherlodeRocksOverlay extends Overlay { @@ -55,6 +57,9 @@ class MotherlodeRocksOverlay extends Overlay private final MotherlodeConfig config; private final BufferedImage miningIcon; + private final BufferedImage targetMiningIcon; + private static final Color miningIconOldColor = new Color(117, 123, 124); + private static final Color miningIconNewColor = new Color(0, 150, 0); @Inject MotherlodeRocksOverlay(Client client, MotherlodePlugin plugin, MotherlodeConfig config, SkillIconManager iconManager) @@ -66,6 +71,7 @@ class MotherlodeRocksOverlay extends Overlay this.config = config; miningIcon = iconManager.getSkillImage(Skill.MINING); + targetMiningIcon = ImageUtil.recolorImage(miningIcon, miningIconNewColor, Color -> Color.getRGB() == miningIconOldColor.getRGB()); } @Override @@ -86,7 +92,6 @@ class MotherlodeRocksOverlay extends Overlay private void renderTiles(Graphics2D graphics, Player local) { LocalPoint localLocation = local.getLocalLocation(); - if (config.showVeins()) { for (WallObject vein : plugin.getVeins()) @@ -97,7 +102,16 @@ class MotherlodeRocksOverlay extends Overlay // Only draw veins on the same level if (plugin.isUpstairs(localLocation) == plugin.isUpstairs(vein.getLocalLocation())) { - renderVein(graphics, vein); + if (WorldPoint.fromLocal(client, location).equals(plugin.getTargetVeinLocation()) + && plugin.isMining() + && config.showTargetVein()) + { + renderVein(graphics, vein, true); + } + else + { + renderVein(graphics, vein, false); + } } } } @@ -115,17 +129,20 @@ class MotherlodeRocksOverlay extends Overlay } } } - } - private void renderVein(Graphics2D graphics, WallObject vein) + private void renderVein(Graphics2D graphics, WallObject vein, Boolean shouldRecolor) { Point canvasLoc = Perspective.getCanvasImageLocation(client, vein.getLocalLocation(), miningIcon, 150); - if (canvasLoc != null) + if (canvasLoc != null && !shouldRecolor) { graphics.drawImage(miningIcon, canvasLoc.getX(), canvasLoc.getY(), null); } + else if (canvasLoc != null) + { + graphics.drawImage(targetMiningIcon, canvasLoc.getX(), canvasLoc.getY(), null); + } } private void renderRock(Graphics2D graphics, GameObject rock) diff --git a/runelite-client/src/main/java/net/runelite/client/util/ImageUtil.java b/runelite-client/src/main/java/net/runelite/client/util/ImageUtil.java index df9787234c..1f1ee8ee0c 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/ImageUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/util/ImageUtil.java @@ -405,6 +405,36 @@ public class ImageUtil return filledImage; } + /** + * Recolors pixels of the given image with the given color based on a given recolor condition + * predicate. + * + * @param image The image which should have its non-transparent pixels recolored. + * @param color The color with which to recolor pixels. + * @param recolorCondition The condition on which to recolor pixels with the given color. + * @return The given image with all pixels fulfilling the recolor condition predicate + * set to the given color. + */ + public static BufferedImage recolorImage(final BufferedImage image, final Color color, final Predicate recolorCondition) + { + final BufferedImage recoloredImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); + for (int x = 0; x < recoloredImage.getWidth(); x++) + { + for (int y = 0; y < recoloredImage.getHeight(); y++) + { + final Color pixelColor = new Color(image.getRGB(x, y), true); + if (!recolorCondition.test(pixelColor)) + { + recoloredImage.setRGB(x, y, image.getRGB(x, y)); + continue; + } + + recoloredImage.setRGB(x, y, color.getRGB()); + } + } + return recoloredImage; + } + /** * Performs a rescale operation on the image's color components. * diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/motherlode/MotherlodePluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/motherlode/MotherlodePluginTest.java index 9ceaf15247..7f4c3bcbb0 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/motherlode/MotherlodePluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/motherlode/MotherlodePluginTest.java @@ -39,6 +39,9 @@ import net.runelite.api.Varbits; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.ItemContainerChanged; import net.runelite.api.events.VarbitChanged; +import net.runelite.client.Notifier; +import net.runelite.client.config.ChatColorConfig; +import net.runelite.client.config.RuneLiteConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -87,6 +90,18 @@ public class MotherlodePluginTest @Bind private ScheduledExecutorService scheduledExecutorService; + @Mock + @Bind + private ChatColorConfig chatColorConfig; + + @Mock + @Bind + private RuneLiteConfig runeliteConfig; + + @Mock + @Bind + private Notifier notifier; + @Before public void before() {