diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorillaOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorillaOverlay.java index 53b64457eb..7d841afb5c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorillaOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/demonicgorilla/DemonicGorillaOverlay.java @@ -24,7 +24,6 @@ */ package net.runelite.client.plugins.demonicgorilla; -import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; @@ -43,6 +42,7 @@ 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; @Singleton public class DemonicGorillaOverlay extends Overlay @@ -118,31 +118,11 @@ public class DemonicGorillaOverlay extends Overlay int currentPosX = 0; for (BufferedImage icon : icons) { - graphics.setStroke(new BasicStroke(2)); - graphics.setColor(COLOR_ICON_BACKGROUND); - graphics.fillOval( - point.getX() - totalWidth / 2 + currentPosX - bgPadding, - point.getY() - icon.getHeight() / 2 - OVERLAY_ICON_DISTANCE - bgPadding, - icon.getWidth() + bgPadding * 2, - icon.getHeight() + bgPadding * 2); - - graphics.setColor(COLOR_ICON_BORDER); - graphics.drawOval( - point.getX() - totalWidth / 2 + currentPosX - bgPadding, - point.getY() - icon.getHeight() / 2 - OVERLAY_ICON_DISTANCE - bgPadding, - icon.getWidth() + bgPadding * 2, - icon.getHeight() + bgPadding * 2); - - graphics.drawImage( - icon, - point.getX() - totalWidth / 2 + currentPosX, - point.getY() - icon.getHeight() / 2 - OVERLAY_ICON_DISTANCE, - null); - - graphics.setColor(COLOR_ICON_BORDER_FILL); + OverlayUtil.setProgressIcon(graphics, point, icon, totalWidth, bgPadding, currentPosX, + COLOR_ICON_BACKGROUND, OVERLAY_ICON_DISTANCE, COLOR_ICON_BORDER, COLOR_ICON_BORDER_FILL); Arc2D.Double arc = new Arc2D.Double( point.getX() - totalWidth / 2 + currentPosX - bgPadding, - point.getY() - icon.getHeight() / 2 - OVERLAY_ICON_DISTANCE - bgPadding, + point.getY() - (float) (icon.getHeight() / 2) - OVERLAY_ICON_DISTANCE - bgPadding, icon.getWidth() + bgPadding * 2, icon.getHeight() + bgPadding * 2, 90.0, diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/AcidPathOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/AcidPathOverlay.java new file mode 100644 index 0000000000..b0ebab25a9 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/AcidPathOverlay.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2018, https://runelitepl.us + * Copyright (c) 2019, Infinitay + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.vorkath; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Polygon; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.Perspective; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +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; + +public class AcidPathOverlay extends Overlay +{ + private static final Color ACID_SPOTS_COLOR = Color.GREEN; + private static final Color ACID_FREE_PATH_COLOR = Color.PINK; + private static final Color WOOXWALK_ATTACK_SPOT_COLOR = Color.YELLOW; + private static final Color WOOXWALK_OUT_OF_REACH_SPOT_COLOR = Color.RED; + private static final int BAR_INDICATOR_SPACER = 5; + + private final Client client; + private final VorkathPlugin plugin; + + @Inject + public AcidPathOverlay(final Client client, final VorkathPlugin plugin) + { + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_SCENE); + + this.client = client; + this.plugin = plugin; + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (plugin.getVorkath() == null || plugin.getVorkath().getVorkath().getLocalLocation() == null) + { + return null; + } + + if (plugin.isIndicateAcidPools() && plugin.getAcidSpots() != null + && !plugin.getAcidSpots().isEmpty()) + { + for (WorldPoint acidWorldPoint : plugin.getAcidSpots()) + { + LocalPoint acidLocalPoint = LocalPoint.fromWorld(client, acidWorldPoint); + if (acidLocalPoint == null) + { + continue; + } + OverlayUtil.renderPolygon(graphics, Perspective.getCanvasTilePoly(client, + acidLocalPoint), ACID_SPOTS_COLOR); + } + } + + if (plugin.isIndicateAcidFreePath() && plugin.getAcidFreePath() != null + && !plugin.getAcidFreePath().isEmpty()) + { + for (WorldPoint acidFreeWorldPoint : plugin.getAcidFreePath()) + { + LocalPoint acidFreeLocalPoint = LocalPoint.fromWorld(client, acidFreeWorldPoint); + if (acidFreeLocalPoint == null) + { + continue; + } + + OverlayUtil.renderPolygon(graphics, Perspective.getCanvasTilePoly(client, + acidFreeLocalPoint), ACID_FREE_PATH_COLOR); + } + } + + if (plugin.isIndicateWooxWalkPath() && plugin.getWooxWalkPath()[0] != null + && plugin.getWooxWalkPath()[1] != null) + { + LocalPoint attackLocalPoint = LocalPoint.fromWorld(client, plugin.getWooxWalkPath()[0]); + LocalPoint outOfReachLocalPoint = LocalPoint.fromWorld(client, plugin.getWooxWalkPath()[1]); + + if (attackLocalPoint != null && outOfReachLocalPoint != null) + { + OverlayUtil.renderPolygon(graphics, Perspective.getCanvasTilePoly(client, + attackLocalPoint), Color.YELLOW); + OverlayUtil.renderPolygon(graphics, Perspective.getCanvasTilePoly(client, + outOfReachLocalPoint), Color.RED); + + if (plugin.isIndicateWooxWalkTick() && plugin.getWooxWalkBar() != null + && plugin.getWooxWalkTimer() != -1) + { + int[] xpointsAttack = { + (int) (plugin.getWooxWalkBar().getX() + plugin.getWooxWalkBar().getWidth() / 2.0 + 1), + (int) (plugin.getWooxWalkBar().getX() + plugin.getWooxWalkBar().getWidth()), + (int) (plugin.getWooxWalkBar().getX() + plugin.getWooxWalkBar().getWidth()), + (int) (plugin.getWooxWalkBar().getX() + plugin.getWooxWalkBar().getWidth() / 2 + 1) + }; + int[] xpointsOutOfReach = { + (int) plugin.getWooxWalkBar().getX(), + (int) (plugin.getWooxWalkBar().getX() + plugin.getWooxWalkBar().getWidth() / 2.0), + (int) (plugin.getWooxWalkBar().getX() + plugin.getWooxWalkBar().getWidth() / 2.0), + (int) plugin.getWooxWalkBar().getX() + }; + int[] ypointsBoth = { + (int) plugin.getWooxWalkBar().getY(), + (int) plugin.getWooxWalkBar().getY(), + (int) (plugin.getWooxWalkBar().getY() + plugin.getWooxWalkBar().getHeight()), + (int) (plugin.getWooxWalkBar().getY() + plugin.getWooxWalkBar().getHeight()) + }; + Polygon wooxWalkAttack = new Polygon(xpointsAttack, ypointsBoth, 4); + Polygon wooxWalkOutOfReach = new Polygon(xpointsOutOfReach, ypointsBoth, 4); + OverlayUtil.renderPolygon(graphics, wooxWalkAttack, WOOXWALK_ATTACK_SPOT_COLOR); + OverlayUtil.renderPolygon(graphics, wooxWalkOutOfReach, WOOXWALK_OUT_OF_REACH_SPOT_COLOR); + + long timeLeft = (System.currentTimeMillis() - plugin.getWooxWalkTimer()) % 1200; + double timeScale; + if (timeLeft <= 600) + { + timeScale = 1 - timeLeft / 600.0; + } + else + { + timeLeft -= 600; + timeScale = timeLeft / 600.0; + } + int progress = (int) Math.round(plugin.getWooxWalkBar().getWidth() * timeScale); + + int[] xpointsIndicator = { + (int) (plugin.getWooxWalkBar().getX() - plugin.getWooxWalkBar().getHeight() / 2 + progress), + (int) (plugin.getWooxWalkBar().getX() + plugin.getWooxWalkBar().getHeight() / 2 + progress), + (int) plugin.getWooxWalkBar().getX() + progress + }; + int[] ypointsIndicator = { + (int) (plugin.getWooxWalkBar().getY() - plugin.getWooxWalkBar().getHeight() - BAR_INDICATOR_SPACER), + (int) (plugin.getWooxWalkBar().getY() - plugin.getWooxWalkBar().getHeight() - BAR_INDICATOR_SPACER), + (int) (plugin.getWooxWalkBar().getY() - BAR_INDICATOR_SPACER) + }; + Polygon indicator = new Polygon(xpointsIndicator, ypointsIndicator, 3); + OverlayUtil.renderPolygon(graphics, indicator, Color.WHITE); + } + } + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/Vorkath.java b/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/Vorkath.java index 02ae424527..b2fd6551ed 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/Vorkath.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/Vorkath.java @@ -38,23 +38,12 @@ public class Vorkath static final int FIRE_BALL_ATTACKS = 25; private NPC vorkath; - private VorkathAttack lastAttack; - private Phase currentPhase; private Phase nextPhase; private Phase lastPhase; - private int attacksLeft; - enum Phase - { - UNKNOWN, - ACID, - FIRE_BALL, - SPAWN - } - public Vorkath(NPC vorkath) { this.vorkath = vorkath; @@ -107,4 +96,12 @@ public class Vorkath log.debug("[Vorkath] Update! Last Phase: {}->{}, Current Phase: {}->{}, Next Phase: {}->{}, Attacks: {}->{}", oldLastPhase, this.lastPhase, oldCurrentPhase, this.currentPhase, oldNextPhase, this.nextPhase, oldAttacksLeft, this.attacksLeft); } + + enum Phase + { + UNKNOWN, + ACID, + FIRE_BALL, + SPAWN + } } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/VorkathAttack.java b/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/VorkathAttack.java index 6a2479c57b..d1a2ec95ea 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/VorkathAttack.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/VorkathAttack.java @@ -82,9 +82,6 @@ public enum VorkathAttack */ ZOMBIFIED_SPAWN(AnimationID.VORKATH_FIRE_BOMB_OR_SPAWN_ATTACK, ProjectileID.VORKATH_SPAWN_AOE); - private final int vorkathAnimationID; - private final int projectileID; - private static final Map VORKATH_ATTACKS; private static final Map VORKATH_BASIC_ATTACKS; @@ -113,6 +110,9 @@ public enum VorkathAttack VORKATH_BASIC_ATTACKS = builder.build(); } + private final int vorkathAnimationID; + private final int projectileID; + /** * @param projectileID id of projectile * @return {@link VorkathAttack} associated with the specified projectile @@ -123,7 +123,7 @@ public enum VorkathAttack } /** - * @param projectileID + * @param projectileID id of projectile * @return true if the projectile id matches a {@link VorkathAttack#getProjectileID()} within {@link VorkathAttack#VORKATH_BASIC_ATTACKS}, * false otherwise */ diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/ZombifiedSpawnOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/VorkathConfig.java similarity index 50% rename from runelite-client/src/main/java/net/runelite/client/plugins/vorkath/ZombifiedSpawnOverlay.java rename to runelite-client/src/main/java/net/runelite/client/plugins/vorkath/VorkathConfig.java index a0cbc0451a..46a51ca8f0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/ZombifiedSpawnOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/VorkathConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, https://runelitepl.us + * Copyright (c) 2018, Jordan Atwood * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,37 +24,67 @@ */ package net.runelite.client.plugins.vorkath; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Graphics2D; -import javax.inject.Inject; -import javax.inject.Singleton; -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.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; -@Singleton -public class ZombifiedSpawnOverlay extends Overlay +@ConfigGroup("vorkath") +public interface VorkathConfig extends Config { - private final VorkathPlugin plugin; - - @Inject - public ZombifiedSpawnOverlay(final VorkathPlugin plugin) + @ConfigItem( + keyName = "indicateAcidPools", + name = "Acid Pools", + description = "Indicate the acid pools", + position = 0 + ) + default boolean indicateAcidPools() { - setPosition(OverlayPosition.DYNAMIC); - setLayer(OverlayLayer.ABOVE_SCENE); - this.plugin = plugin; + return false; } - @Override - public Dimension render(Graphics2D graphics) + @ConfigItem( + keyName = "indicateAcidFreePath", + name = "Acid Free Path", + description = "Indicate the most efficient acid free path", + position = 1 + ) + default boolean indicateAcidFreePath() { - if (plugin.getZombifiedSpawn() != null) - { - OverlayUtil.renderActorOverlayImage(graphics, plugin.getZombifiedSpawn(), VorkathPlugin.SPAWN, Color.green, 10); - } - - return null; + return true; } -} \ No newline at end of file + + @ConfigItem( + keyName = "acidFreePathMinLength", + name = "Minimum Length Acid Free Path", + description = "The minimum length of an acid free path", + position = 2, + hidden = true, + unhide = "indicateAcidFreePath" + ) + default int acidFreePathLength() + { + return 5; + } + + @ConfigItem( + keyName = "indicateWooxWalkPath", + name = "WooxWalk Path", + description = "Indicate the closest WooxWalk path", + position = 3 + ) + default boolean indicateWooxWalkPath() + { + return true; + } + + @ConfigItem( + keyName = "indicateWooxWalkTick", + name = "WooxWalk Tick", + description = "Indicate on which tile to click during each game tick", + position = 4 + ) + default boolean indicateWooxWalkTick() + { + return true; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/VorkathOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/VorkathOverlay.java index d55484adb6..11b363fa72 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/VorkathOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/VorkathOverlay.java @@ -26,7 +26,6 @@ */ package net.runelite.client.plugins.vorkath; -import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; @@ -41,6 +40,8 @@ import net.runelite.api.coords.LocalPoint; 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; @Singleton public class VorkathOverlay extends Overlay @@ -50,6 +51,18 @@ public class VorkathOverlay extends Overlay private static final Color COLOR_ICON_BORDER_FILL = new Color(219, 175, 0, 255); private static final int OVERLAY_ICON_DISTANCE = 30; private static final int OVERLAY_ICON_MARGIN = 1; + private static final BufferedImage UNKNOWN; + private static final BufferedImage ACID; + private static final BufferedImage FIRE_BALL; + private static final BufferedImage SPAWN; + + static + { + UNKNOWN = ImageUtil.getResourceStreamFromClass(VorkathPlugin.class, "magerange.png"); + ACID = ImageUtil.getResourceStreamFromClass(VorkathPlugin.class, "acid.png"); + FIRE_BALL = ImageUtil.getResourceStreamFromClass(VorkathPlugin.class, "fire_strike.png"); + SPAWN = ImageUtil.getResourceStreamFromClass(VorkathPlugin.class, "ice.png"); + } private final Client client; private final VorkathPlugin plugin; @@ -68,9 +81,9 @@ public class VorkathOverlay extends Overlay { if (plugin.getVorkath() != null) { - Vorkath vorkath = plugin.getVorkath(); + final Vorkath vorkath = plugin.getVorkath(); - LocalPoint localLocation = vorkath.getVorkath().getLocalLocation(); + final LocalPoint localLocation = vorkath.getVorkath().getLocalLocation(); if (localLocation != null) { Point point = Perspective.localToCanvas(client, localLocation, client.getPlane(), vorkath.getVorkath().getLogicalHeight() + 16); @@ -78,7 +91,7 @@ public class VorkathOverlay extends Overlay { point = new Point(point.getX(), point.getY()); - BufferedImage currentPhaseIcon = getIcon(vorkath); + final BufferedImage currentPhaseIcon = getIcon(vorkath); int totalWidth = 0; if (currentPhaseIcon != null) @@ -88,41 +101,33 @@ public class VorkathOverlay extends Overlay int bgPadding = 8; int currentPosX = 0; - graphics.setStroke(new BasicStroke(2)); - graphics.setColor(COLOR_ICON_BACKGROUND); - graphics.fillOval( - point.getX() - totalWidth / 2 + currentPosX - bgPadding, - point.getY() - currentPhaseIcon.getHeight() / 2 - OVERLAY_ICON_DISTANCE - bgPadding, - currentPhaseIcon.getWidth() + bgPadding * 2, - currentPhaseIcon.getHeight() + bgPadding * 2); + if (currentPhaseIcon == null) + { + return null; + } - graphics.setColor(COLOR_ICON_BORDER); - graphics.drawOval( - point.getX() - totalWidth / 2 + currentPosX - bgPadding, - point.getY() - currentPhaseIcon.getHeight() / 2 - OVERLAY_ICON_DISTANCE - bgPadding, - currentPhaseIcon.getWidth() + bgPadding * 2, - currentPhaseIcon.getHeight() + bgPadding * 2); + OverlayUtil.setProgressIcon(graphics, point, currentPhaseIcon, totalWidth, bgPadding, currentPosX, + COLOR_ICON_BACKGROUND, OVERLAY_ICON_DISTANCE, COLOR_ICON_BORDER, COLOR_ICON_BORDER_FILL); - graphics.drawImage( - currentPhaseIcon, - point.getX() - totalWidth / 2 + currentPosX, - point.getY() - currentPhaseIcon.getHeight() / 2 - OVERLAY_ICON_DISTANCE, - null); - - graphics.setColor(COLOR_ICON_BORDER_FILL); - Arc2D.Double arc = new Arc2D.Double( + final Arc2D.Double arc = new Arc2D.Double( point.getX() - totalWidth / 2 + currentPosX - bgPadding, - point.getY() - currentPhaseIcon.getHeight() / 2 - OVERLAY_ICON_DISTANCE - bgPadding, + point.getY() - (float) (currentPhaseIcon.getHeight() / 2) - OVERLAY_ICON_DISTANCE - bgPadding, currentPhaseIcon.getWidth() + bgPadding * 2, currentPhaseIcon.getHeight() + bgPadding * 2, 90.0, -360.0 * getAttacksLeftProgress(), - Arc2D.OPEN); + Arc2D.OPEN + ); graphics.draw(arc); } } } + if (plugin.getZombifiedSpawn() != null) + { + OverlayUtil.renderActorOverlayImage(graphics, plugin.getZombifiedSpawn(), SPAWN, Color.green, 10); + } + return null; } @@ -135,13 +140,13 @@ public class VorkathOverlay extends Overlay switch (vorkath.getCurrentPhase()) { case UNKNOWN: - return VorkathPlugin.UNKNOWN; + return UNKNOWN; case ACID: - return VorkathPlugin.ACID; + return ACID; case FIRE_BALL: - return VorkathPlugin.FIRE_BALL; + return FIRE_BALL; case SPAWN: - return VorkathPlugin.SPAWN; + return SPAWN; } return null; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/VorkathPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/VorkathPlugin.java index 58cefc7efe..cb34ccb825 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/VorkathPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/vorkath/VorkathPlugin.java @@ -27,28 +27,47 @@ package net.runelite.client.plugins.vorkath; import com.google.inject.Inject; +import com.google.inject.Provides; import com.google.inject.Singleton; -import java.awt.image.BufferedImage; +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import lombok.AccessLevel; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Actor; import net.runelite.api.Client; +import net.runelite.api.GameObject; import net.runelite.api.NPC; -import net.runelite.api.NpcID; +import net.runelite.api.ObjectID; +import net.runelite.api.Projectile; +import net.runelite.api.ProjectileID; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.AnimationChanged; +import net.runelite.api.events.ClientTick; +import net.runelite.api.events.ConfigChanged; +import net.runelite.api.events.GameObjectDespawned; +import net.runelite.api.events.GameObjectSpawned; +import net.runelite.api.events.GameTick; import net.runelite.api.events.NpcDespawned; import net.runelite.api.events.NpcSpawned; import net.runelite.api.events.ProjectileMoved; +import net.runelite.api.events.ProjectileSpawned; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +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.plugins.PluginType; import net.runelite.client.ui.overlay.OverlayManager; -import net.runelite.client.util.ImageUtil; import org.apache.commons.lang3.ArrayUtils; @PluginDescriptor( name = "Vorkath Helper", - description = "Count vorkath attacks, and which phase is coming next", + description = "Count vorkath attacks, indicate next phase, wooxwalk timer, indicate path through acid", tags = {"combat", "overlay", "pve", "pvm"}, type = PluginType.PVM, enabledByDefault = false @@ -61,93 +80,133 @@ public class VorkathPlugin extends Plugin @Inject private Client client; - @Inject private OverlayManager overlayManager; - @Inject private VorkathOverlay overlay; - @Inject - private ZombifiedSpawnOverlay SpawnOverlay; - - @Getter + private AcidPathOverlay acidPathOverlay; + @Inject + private VorkathConfig config; + @Getter(AccessLevel.PACKAGE) private Vorkath vorkath; - - @Getter + @Getter(AccessLevel.PACKAGE) private NPC zombifiedSpawn; + @Getter(AccessLevel.PACKAGE) + private List acidSpots = new ArrayList<>(); + @Getter(AccessLevel.PACKAGE) + private List acidFreePath = new ArrayList<>(); + @Getter(AccessLevel.PACKAGE) + private WorldPoint[] wooxWalkPath = new WorldPoint[2]; + @Getter(AccessLevel.PACKAGE) + private long wooxWalkTimer = -1; + @Getter(AccessLevel.PACKAGE) + private Rectangle wooxWalkBar; + private int lastAcidSpotsSize = 0; + // Config values + @Getter(AccessLevel.PACKAGE) + private boolean indicateAcidPools; + @Getter(AccessLevel.PACKAGE) + private boolean indicateAcidFreePath; + @Getter(AccessLevel.PACKAGE) + private boolean indicateWooxWalkPath; + @Getter(AccessLevel.PACKAGE) + private boolean indicateWooxWalkTick; + private int acidFreePathLength; - /** - * The last projectile's starting movement cycle - */ - private int lastProjectileCycle; - - static final BufferedImage UNKNOWN; - static final BufferedImage ACID; - static final BufferedImage FIRE_BALL; - static final BufferedImage SPAWN; - - static + @Provides + VorkathConfig provideConfig(ConfigManager configManager) { - UNKNOWN = ImageUtil.getResourceStreamFromClass(VorkathPlugin.class, "magerange.png"); - ACID = ImageUtil.getResourceStreamFromClass(VorkathPlugin.class, "acid.png"); - FIRE_BALL = ImageUtil.getResourceStreamFromClass(VorkathPlugin.class, "fire_strike.png"); - SPAWN = ImageUtil.getResourceStreamFromClass(VorkathPlugin.class, "ice.png"); + return configManager.getConfig(VorkathConfig.class); + } + + @Override + protected void startUp() + { + updateConfig(); + } + + @Override + protected void shutDown() + { + reset(); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (!event.getGroup().equals("vorkath")) + { + return; + } + + updateConfig(); } @Subscribe public void onNpcSpawned(NpcSpawned event) { - if (isAtVorkath()) + if (!isAtVorkath()) { - if (isVorkath(event.getNpc().getId())) - { - vorkath = new Vorkath(event.getNpc()); - lastProjectileCycle = -1; - overlayManager.add(overlay); - } - else if (isZombifiedSpawn(event.getNpc().getId())) - { - zombifiedSpawn = event.getNpc(); - overlayManager.add(SpawnOverlay); - } + return; + } + + final NPC npc = event.getNpc(); + + if (npc.getName() == null) + { + return; + } + + if (npc.getName().equals("Vorkath")) + { + vorkath = new Vorkath(npc); + overlayManager.add(overlay); + } + else if (npc.getName().equals("Zombified Spawn")) + { + zombifiedSpawn = npc; } } @Subscribe public void onNpcDespawned(NpcDespawned event) { - if (isAtVorkath()) - { - if (isVorkath(event.getNpc().getId())) - { - vorkath = null; - lastProjectileCycle = -1; - overlayManager.remove(overlay); - } - else if (isZombifiedSpawn(event.getNpc().getId())) - { - zombifiedSpawn = null; - overlayManager.remove(SpawnOverlay); - } - } - } - - @Subscribe - public void onProjectileMoved(ProjectileMoved event) - { - // Only capture initial projectile - if (!isAtVorkath() || event.getProjectile().getStartMovementCycle() == lastProjectileCycle) + if (!isAtVorkath()) { return; } - VorkathAttack vorkathAttack = VorkathAttack.getVorkathAttack(event.getProjectile().getId()); + final NPC npc = event.getNpc(); + + if (npc.getName() == null) + { + return; + } + + if (npc.getName().equals("Vorkath")) + { + reset(); + } + else if (npc.getName().equals("Zombified Spawn")) + { + zombifiedSpawn = null; + } + } + + @Subscribe + public void onProjectileSpawned(ProjectileSpawned event) + { + if (!isAtVorkath()) + { + return; + } + + final Projectile proj = event.getProjectile(); + final VorkathAttack vorkathAttack = VorkathAttack.getVorkathAttack(proj.getId()); + if (vorkathAttack != null) { - /*log.debug("[Projectile ({})] Game Tick: {}, Game Cycle: {}, Starting Cyle: {} Last Cycle: {}, Initial Projectile?: {}", - vorkathAttack, client.getTickCount(), client.getGameCycle(), event.getProjectile().getStartMovementCycle(), - lastProjectileCycle, event.getProjectile().getStartMovementCycle() == client.getGameCycle());*/ if (VorkathAttack.isBasicAttack(vorkathAttack.getProjectileID()) && vorkath.getAttacksLeft() > 0) { vorkath.setAttacksLeft(vorkath.getAttacksLeft() - 1); @@ -155,47 +214,94 @@ public class VorkathPlugin extends Plugin else if (vorkathAttack == VorkathAttack.ACID) { vorkath.updatePhase(Vorkath.Phase.ACID); - // Sets the phase's progress indicator to done vorkath.setAttacksLeft(0); } else if (vorkathAttack == VorkathAttack.FIRE_BALL) { vorkath.updatePhase(Vorkath.Phase.FIRE_BALL); - // Decrement to account for this fire ball vorkath.setAttacksLeft(vorkath.getAttacksLeft() - 1); } else if (vorkathAttack == VorkathAttack.FREEZE_BREATH && vorkath.getLastAttack() != VorkathAttack.ZOMBIFIED_SPAWN) { - // Filters out second invisible freeze attack that is immediately after the Zombified Spawn vorkath.updatePhase(Vorkath.Phase.SPAWN); - // Sets progress of the phase to half vorkath.setAttacksLeft(vorkath.getAttacksLeft() - (vorkath.getAttacksLeft() / 2)); } else if (vorkathAttack == VorkathAttack.ZOMBIFIED_SPAWN || (vorkath.getLastAttack() == VorkathAttack.ZOMBIFIED_SPAWN)) { - // Also consumes the second invisible freeze attack that is immediately after the Zombified Spawn - // Sets progress of the phase to done as there are no more attacks within this phase vorkath.setAttacksLeft(0); } else { - // Vorkath fired a basic attack AND there are no more attacks left, typically after phases are over vorkath.updatePhase(vorkath.getNextPhase()); - // Decrement to account for this basic attack vorkath.setAttacksLeft(vorkath.getAttacksLeft() - 1); } log.debug("[Vorkath ({})] {}", vorkathAttack, vorkath); vorkath.setLastAttack(vorkathAttack); - lastProjectileCycle = event.getProjectile().getStartMovementCycle(); + } + } + + @Subscribe + public void onProjectileMoved(ProjectileMoved event) + { + if (!isAtVorkath()) + { + return; + } + + final Projectile proj = event.getProjectile(); + final LocalPoint loc = event.getPosition(); + + if (proj.getId() == ProjectileID.VORKATH_POISON_POOL_AOE) + { + addAcidSpot(WorldPoint.fromLocal(client, loc)); + } + } + + @Subscribe + public void onGameObjectSpawned(GameObjectSpawned event) + { + if (!isAtVorkath()) + { + return; + } + + final GameObject obj = event.getGameObject(); + + if (obj.getId() == ObjectID.ACID_POOL || obj.getId() == ObjectID.ACID_POOL_32000) + { + addAcidSpot(obj.getWorldLocation()); + } + } + + @Subscribe + public void onGameObjectDespawned(GameObjectDespawned event) + { + if (!isAtVorkath()) + { + return; + } + + final GameObject obj = event.getGameObject(); + + if (obj.getId() == ObjectID.ACID_POOL || obj.getId() == ObjectID.ACID_POOL_32000) + { + acidSpots.remove(obj.getWorldLocation()); } } @Subscribe public void onAnimationChanged(AnimationChanged event) { - if (isAtVorkath() && vorkath != null && event.getActor().equals(vorkath.getVorkath()) - && event.getActor().getAnimation() == VorkathAttack.SLASH_ATTACK.getVorkathAnimationID()) + if (!isAtVorkath()) + { + return; + } + + final Actor actor = event.getActor(); + + if (isAtVorkath() && vorkath != null && actor.equals(vorkath.getVorkath()) + && actor.getAnimation() == VorkathAttack.SLASH_ATTACK.getVorkathAnimationID()) { if (vorkath.getAttacksLeft() > 0) { @@ -203,15 +309,85 @@ public class VorkathPlugin extends Plugin } else { - // No more attacks left, typically after phases are over vorkath.updatePhase(vorkath.getNextPhase()); - // Decrement to account for this basic attack vorkath.setAttacksLeft(vorkath.getAttacksLeft() - 1); } log.debug("[Vorkath (SLASH_ATTACK)] {}", vorkath); } } + @Subscribe + public void onGameTick(GameTick event) + { + if (!isAtVorkath()) + { + return; + } + + // Update the acid free path every tick to account for player movement + if (this.indicateAcidFreePath && !acidSpots.isEmpty()) + { + calculateAcidFreePath(); + } + + // Start the timer when the player walks into the WooxWalk zone + if (this.indicateWooxWalkPath && this.indicateWooxWalkTick && wooxWalkPath[0] != null && wooxWalkPath[1] != null) + { + final WorldPoint playerLoc = client.getLocalPlayer().getWorldLocation(); + + if (playerLoc.getX() == wooxWalkPath[0].getX() && playerLoc.getY() == wooxWalkPath[0].getY() + && playerLoc.getPlane() == wooxWalkPath[0].getPlane()) + { + if (wooxWalkTimer == -1) + { + wooxWalkTimer = System.currentTimeMillis() - 400; + } + } + else if (playerLoc.getX() == wooxWalkPath[1].getX() && playerLoc.getY() == wooxWalkPath[1].getY() + && playerLoc.getPlane() == wooxWalkPath[1].getPlane()) + { + if (wooxWalkTimer == -1) + { + wooxWalkTimer = System.currentTimeMillis() - 1000; + } + } + else if (wooxWalkTimer != -1) + { + wooxWalkTimer = -1; + } + } + } + + @Subscribe + private void onClientTick(ClientTick event) + { + if (acidSpots.size() != lastAcidSpotsSize) + { + if (acidSpots.size() == 0) + { + overlayManager.remove(acidPathOverlay); + acidFreePath.clear(); + Arrays.fill(wooxWalkPath, null); + wooxWalkTimer = -1; + } + else + { + if (this.indicateAcidFreePath) + { + calculateAcidFreePath(); + } + if (this.indicateWooxWalkPath) + { + calculateWooxWalkPath(); + } + + overlayManager.add(acidPathOverlay); + } + + lastAcidSpotsSize = acidSpots.size(); + } + } + /** * @return true if the player is in the Vorkath region, false otherwise */ @@ -220,28 +396,209 @@ public class VorkathPlugin extends Plugin return ArrayUtils.contains(client.getMapRegions(), VORKATH_REGION); } - /** - * @param npcID - * @return true if the npc is Vorkath, false otherwise - */ - private boolean isVorkath(int npcID) + private void addAcidSpot(WorldPoint acidSpotLocation) { - // Could be done with a a simple name check instead... - return npcID == NpcID.VORKATH || - npcID == NpcID.VORKATH_8058 || - npcID == NpcID.VORKATH_8059 || - npcID == NpcID.VORKATH_8060 || - npcID == NpcID.VORKATH_8061; + if (!acidSpots.contains(acidSpotLocation)) + { + acidSpots.add(acidSpotLocation); + } } - /** - * @param npcID - * @return true if the npc is a Zombified Spawn, otherwise false - */ - private boolean isZombifiedSpawn(int npcID) + private void calculateAcidFreePath() { - // Could be done with a a simple name check instead... - return npcID == NpcID.ZOMBIFIED_SPAWN || - npcID == NpcID.ZOMBIFIED_SPAWN_8063; + acidFreePath.clear(); + + final int[][][] directions = { + { + {0, 1}, {0, -1} // Positive and negative Y + }, + { + {1, 0}, {-1, 0} // Positive and negative X + } + }; + + List bestPath = new ArrayList<>(); + double bestClicksRequired = 99; + + final WorldPoint playerLoc = client.getLocalPlayer().getWorldLocation(); + final WorldPoint vorkLoc = vorkath.getVorkath().getWorldLocation(); + final int maxX = vorkLoc.getX() + 14; + final int minX = vorkLoc.getX() - 8; + final int maxY = vorkLoc.getY() - 1; + final int minY = vorkLoc.getY() - 8; + + // Attempt to search an acid free path, beginning at a location + // adjacent to the player's location (including diagonals) + for (int x = -1; x < 2; x++) + { + for (int y = -1; y < 2; y++) + { + final WorldPoint baseLocation = new WorldPoint(playerLoc.getX() + x, + playerLoc.getY() + y, playerLoc.getPlane()); + + if (acidSpots.contains(baseLocation) || baseLocation.getY() < minY || baseLocation.getY() > maxY) + { + continue; + } + + // Search in X and Y direction + for (int d = 0; d < directions.length; d++) + { + // Calculate the clicks required to start walking on the path + double currentClicksRequired = Math.abs(x) + Math.abs(y); + if (currentClicksRequired < 2) + { + currentClicksRequired += Math.abs(y * directions[d][0][0]) + Math.abs(x * directions[d][0][1]); + } + if (d == 0) + { + // Prioritize a path in the X direction (sideways) + currentClicksRequired += 0.5; + } + + List currentPath = new ArrayList<>(); + currentPath.add(baseLocation); + + // Positive X (first iteration) or positive Y (second iteration) + for (int i = 1; i < 25; i++) + { + final WorldPoint testingLocation = new WorldPoint(baseLocation.getX() + i * directions[d][0][0], + baseLocation.getY() + i * directions[d][0][1], baseLocation.getPlane()); + + if (acidSpots.contains(testingLocation) || testingLocation.getY() < minY || testingLocation.getY() > maxY + || testingLocation.getX() < minX || testingLocation.getX() > maxX) + { + break; + } + + currentPath.add(testingLocation); + } + + // Negative X (first iteration) or positive Y (second iteration) + for (int i = 1; i < 25; i++) + { + final WorldPoint testingLocation = new WorldPoint(baseLocation.getX() + i * directions[d][1][0], + baseLocation.getY() + i * directions[d][1][1], baseLocation.getPlane()); + + if (acidSpots.contains(testingLocation) || testingLocation.getY() < minY || testingLocation.getY() > maxY + || testingLocation.getX() < minX || testingLocation.getX() > maxX) + { + break; + } + + currentPath.add(testingLocation); + } + + if (currentPath.size() >= this.acidFreePathLength && currentClicksRequired < bestClicksRequired + || (currentClicksRequired == bestClicksRequired && currentPath.size() > bestPath.size())) + { + bestPath = currentPath; + bestClicksRequired = currentClicksRequired; + } + } + } + } + + if (bestClicksRequired != 99) + { + acidFreePath = bestPath; + } } -} + + private void calculateWooxWalkPath() + { + wooxWalkTimer = -1; + + updateWooxWalkBar(); + + final WorldPoint playerLoc = client.getLocalPlayer().getWorldLocation(); + final WorldPoint vorkLoc = vorkath.getVorkath().getWorldLocation(); + final int maxX = vorkLoc.getX() + 14; + final int minX = vorkLoc.getX() - 8; + final int baseX = playerLoc.getX(); + final int baseY = vorkLoc.getY() - 5; + final int middleX = vorkLoc.getX() + 3; + + // Loop through the arena tiles in the x-direction and + // alternate between positive and negative x direction + for (int i = 0; i < 50; i++) + { + // Make sure we always choose the spot closest to + // the middle of the arena + int directionRemainder = 0; + if (playerLoc.getX() < middleX) + { + directionRemainder = 1; + } + + int deviation = (int) Math.floor(i / 2.0); + if (i % 2 == directionRemainder) + { + deviation = -deviation; + } + + final WorldPoint attackLocation = new WorldPoint(baseX + deviation, baseY, playerLoc.getPlane()); + final WorldPoint outOfRangeLocation = new WorldPoint(baseX + deviation, baseY - 1, playerLoc.getPlane()); + + if (acidSpots.contains(attackLocation) || acidSpots.contains(outOfRangeLocation) + || attackLocation.getX() < minX || attackLocation.getX() > maxX) + { + continue; + } + + wooxWalkPath[0] = attackLocation; + wooxWalkPath[1] = outOfRangeLocation; + + break; + } + } + + private void updateWooxWalkBar() + { + // Update the WooxWalk tick indicator's dimensions + // based on the canvas dimensions + final Widget exp = client.getWidget(WidgetInfo.EXPERIENCE_TRACKER); + + if (exp == null) + { + return; + } + + final Rectangle screen = exp.getBounds(); + + int width = (int) Math.floor(screen.getWidth() / 2.0); + if (width % 2 == 1) + { + width++; + } + int height = (int) Math.floor(width / 20.0); + if (height % 2 == 1) + { + height++; + } + final int x = (int) Math.floor(screen.getX() + width / 2.0); + final int y = (int) Math.floor(screen.getY() + screen.getHeight() - 2 * height); + wooxWalkBar = new Rectangle(x, y, width, height); + } + + private void updateConfig() + { + this.indicateAcidPools = config.indicateAcidPools(); + this.indicateAcidFreePath = config.indicateAcidFreePath(); + this.indicateWooxWalkPath = config.indicateWooxWalkPath(); + this.indicateWooxWalkTick = config.indicateWooxWalkTick(); + this.acidFreePathLength = config.acidFreePathLength(); + } + + private void reset() + { + overlayManager.remove(overlay); + overlayManager.remove(acidPathOverlay); + vorkath = null; + acidSpots.clear(); + acidFreePath.clear(); + Arrays.fill(wooxWalkPath, null); + wooxWalkTimer = -1; + zombifiedSpawn = null; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java index 541196d646..49a18caba6 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java @@ -393,4 +393,30 @@ public class OverlayUtil OverlayUtil.drawStrokeAndFillPoly(graphics, color, outlineWidth, outlineAlpha, fillAlpha, tilePoly); } } + + public static void setProgressIcon(Graphics2D graphics, Point point, BufferedImage currentPhaseIcon, int totalWidth, int bgPadding, int currentPosX, Color colorIconBackground, int overlayIconDistance, Color colorIconBorder, Color colorIconBorderFill) + { + graphics.setStroke(new BasicStroke(2)); + graphics.setColor(colorIconBackground); + graphics.fillOval( + point.getX() - totalWidth / 2 + currentPosX - bgPadding, + point.getY() - currentPhaseIcon.getHeight() / 2 - overlayIconDistance - bgPadding, + currentPhaseIcon.getWidth() + bgPadding * 2, + currentPhaseIcon.getHeight() + bgPadding * 2); + + graphics.setColor(colorIconBorder); + graphics.drawOval( + point.getX() - totalWidth / 2 + currentPosX - bgPadding, + point.getY() - currentPhaseIcon.getHeight() / 2 - overlayIconDistance - bgPadding, + currentPhaseIcon.getWidth() + bgPadding * 2, + currentPhaseIcon.getHeight() + bgPadding * 2); + + graphics.drawImage( + currentPhaseIcon, + point.getX() - totalWidth / 2 + currentPosX, + point.getY() - currentPhaseIcon.getHeight() / 2 - overlayIconDistance, + null); + + graphics.setColor(colorIconBorderFill); + } }