From a89f6030c06af0e8169965763fadb8642d01682b Mon Sep 17 00:00:00 2001 From: BuildTools Date: Mon, 29 Jul 2019 20:32:24 -0400 Subject: [PATCH] Overhaul --- GauntletConfig.java | 121 +++++++++--- GauntletOverlay.java | 290 +++++++++++++++-------------- GauntletPlugin.java | 429 ++++++++++++++++++++----------------------- GauntletTimer.java | 219 ++++++++++++++++++++++ GauntletUtils.java | 153 +++++++++++++++ 5 files changed, 817 insertions(+), 395 deletions(-) create mode 100644 GauntletTimer.java create mode 100644 GauntletUtils.java diff --git a/GauntletConfig.java b/GauntletConfig.java index 0a3a7e1..e2f7d31 100644 --- a/GauntletConfig.java +++ b/GauntletConfig.java @@ -11,6 +11,7 @@ package net.runelite.client.plugins.gauntlet; import net.runelite.client.config.Config; import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Range; @ConfigGroup("Gauntlet") @@ -18,18 +19,18 @@ public interface GauntletConfig extends Config { @ConfigItem( position = 0, - keyName = "highlightResources", - name = "Highlight Resources", + keyName = "highlightResourcesColor", + name = "Highlight Resources (Color)", description = "Highlights all the resources in each room with a color." ) - default boolean highlightResources() { + default boolean highlightResourcesColor() { return true; } @ConfigItem( position = 1, keyName = "highlightResourcesIcons", - name = "Highlight Icons", + name = "Highlight Resources (Icon)", description = "Highlights all the icons in each room with an icon." ) default boolean highlightResourcesIcons() { @@ -38,16 +39,6 @@ public interface GauntletConfig extends Config { @ConfigItem( position = 2, - keyName = "countPlayerAttacks", - name = "Count Player Attacks", - description = "Count the player attacks until the boss switches their prayer." - ) - default boolean countPlayerAttacks() { - return true; - } - - @ConfigItem( - position = 3, keyName = "countBossAttacks", name = "Count Boss Attacks", description = "Count the attacks until the boss switches their style." @@ -57,32 +48,106 @@ public interface GauntletConfig extends Config { } @ConfigItem( - position = 4, - keyName = "showAttackStyle", - name = "Show Attack Styles", - description = "Mark orb with an individual attack style." + position = 3, + keyName = "countPlayerAttacks", + name = "Count Player Attacks", + description = "Count the player attacks until the boss switches their prayer." ) - default boolean showAttackStyle() { + default boolean countPlayerAttacks() { + return true; + } + + @ConfigItem( + position = 4, + keyName = "uniquePrayerAudio", + name = "Unique Prayer Audio", + description = "Plays a unique sound whenever the boss is about to shut down your prayer." + ) + default boolean uniquePrayerAudio() { return true; } @ConfigItem( position = 5, - keyName = "overlayBoss", - name = "Overlay the Boss", - description = "Overlay the boss with the color attack style." + keyName = "uniquePrayerVisual", + name = "Unique Prayer Visual", + description = "Prayer attacks will have a unique overlay visual." ) - default boolean overlayBoss() { - return false; + default boolean uniquePrayerVisual() { + return true; } @ConfigItem( position = 6, - keyName = "alertPrayerDrain", - name = "Play Tune on Prayer Attack", - description = "Plays a sound whenever the boss is about to shut down your prayer." + keyName = "uniqueAttackVisual", + name = "Unique Magic & Range Visuals", + description = "Magic and Range attacks will have a unique overlay visual." ) - default boolean alertPrayerDrain() { + default boolean uniqueAttackVisual() { + return false; + } + + @ConfigItem( + position = 7, + keyName = "overlayBoss", + name = "Overlay the Boss (Color)", + description = "Overlay the boss with an color denoting it's current attack style." + ) + default boolean overlayBoss() { return true; } + + @ConfigItem( + position = 8, + keyName = "overlayBossPrayer", + name = "Overlay the Boss (Icon)", + description = "Overlay the boss with an icon denoting it's current attack style." + ) + default boolean overlayBossPrayer() { + return false; + } + + @ConfigItem( + position = 9, + keyName = "overlayTornadoes", + name = "Show Tornado Decay", + description = "Display the amount of ticks left until the tornadoes decay." + ) + default boolean overlayTornadoes() { + return true; + } + + @ConfigItem( + position = 10, + keyName = "displayTimerWidget", + name = "Show Custom Timer (Widget)", + description = "Display a timer widget that tracks your gauntlet progress." + ) + default boolean displayTimerWidget() { + return true; + } + + @ConfigItem( + position = 11, + keyName = "displayTimerChat", + name = "Show Custom Timer (Chat)", + description = "Display a chat message that tracks your gauntlet progress." + ) + default boolean displayTimerChat() { + return true; + } + + @Range( + min = 1, + max = 50 + ) + @ConfigItem( + position = 12, + keyName = "iconSize", + name = "Global Icon Size", + description = "Globally change the size of icons." + ) + default int iconSize() { + return 20; + } } diff --git a/GauntletOverlay.java b/GauntletOverlay.java index 04e18ab..4520ce8 100644 --- a/GauntletOverlay.java +++ b/GauntletOverlay.java @@ -16,6 +16,7 @@ import net.runelite.api.Projectile; import net.runelite.api.Tile; import net.runelite.api.TileObject; 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; @@ -47,169 +48,190 @@ public class GauntletOverlay extends Overlay { @Override public Dimension render(Graphics2D graphics) { - if (this.plugin.inBoss()) { - if (config.showAttackStyle()) { - for (Projectile projectile : this.client.getProjectiles()) { - BufferedImage icon; - switch (projectile.getId()) { - case 1707: - case 1708: - icon = plugin.getATTACK_MAGE(); - break; - case 1711: - case 1712: - icon = plugin.getATTACK_RANGE(); - break; - case 1713: - case 1714: - icon = plugin.getATTACK_PRAYER(); - break; - default: - icon = null; - break; - } + // Save resources. There's nothing to render if the user is not in a raid. + if (!GauntletUtils.inRaid(client)) + return null; - if (icon == null) - continue; + if (GauntletUtils.inBoss(client)) { // This section handles the visuals when the player is in the boss room. + // This section handles the projectile overlays. + for (Projectile projectile : this.client.getProjectiles()) { + int id = projectile.getId(); - int x = (int) projectile.getX(); - int y = (int) projectile.getY(); + BufferedImage icon = null; - LocalPoint point = new LocalPoint(x, y); - Point loc = Perspective.getCanvasImageLocation(client, point, icon, 0); + if (GauntletUtils.arrayContainsInteger(GauntletUtils.PROJECTILE_MAGIC, id) && config.uniqueAttackVisual()) + icon = plugin.imageAttackMage; + else if (GauntletUtils.arrayContainsInteger(GauntletUtils.PROJECTILE_RANGE, id) && config.uniqueAttackVisual()) + icon = plugin.imageAttackRange; + else if (GauntletUtils.arrayContainsInteger(GauntletUtils.PROJECTILE_PRAYER, id) && config.uniquePrayerVisual()) + icon = plugin.imageAttackPrayer; - if (loc == null) - continue; + if (icon == null) + continue; - graphics.drawImage(icon, loc.getX(), loc.getY(), null); - } - } + int x = (int) projectile.getX(); + int y = (int) projectile.getY(); - BufferedImage attackIcon = null; - if (config.countBossAttacks()) { - switch (plugin.getCurrentStyle()) { - case MAGIC: - attackIcon = plugin.getATTACK_MAGE(); - break; - case RANGE: - attackIcon = plugin.getATTACK_RANGE(); - break; - default: - attackIcon = plugin.getATTACK_PRAYER(); - break; - } + LocalPoint point = new LocalPoint(x, y); + Point loc = Perspective.getCanvasImageLocation(client, point, icon, 0); + + if (loc == null) + continue; + + graphics.drawImage(icon, loc.getX(), loc.getY(), null); } for (NPC npc : this.client.getNpcs()) { - String name = npc.getName(); - if (name == null || !npc.getName().matches("(Crystalline|Corrupted) Hunllef")) - continue; + // Draws graphics on tornadoes. + if (config.overlayTornadoes() && plugin.tornadoesActive && GauntletUtils.isTornado(npc)) { + String textOverlay = Integer.toString(plugin.tornadoTicks); - if (config.overlayBoss()) { - Polygon polygon = npc.getConvexHull(); - - if (polygon != null) { - Color color; - switch (plugin.getCurrentStyle()) { - case MAGIC: - color = Color.CYAN; - break; - case RANGE: - color = Color.GREEN; - break; - default: - color = Color.WHITE; - break; - } - - graphics.draw(polygon); - graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 50)); - graphics.fill(polygon); - } - } - - if (attackIcon != null) { - LocalPoint point = npc.getLocalLocation(); - Point imageLoc = Perspective.getCanvasImageLocation(client, point, attackIcon, npc.getLogicalHeight() / 2); - - if (imageLoc == null) - continue; - - graphics.drawImage(attackIcon, imageLoc.getX(), imageLoc.getY(), null); - - String message = Integer.toString(plugin.getAttacksLeft()); - if (config.countPlayerAttacks()) { - message += " | " + plugin.getPlayerCounter(); - } - - Point textLoc = Perspective.getCanvasTextLocation(client, graphics, point, message, npc.getLogicalHeight() / 2); + Point textLoc = Perspective.getCanvasTextLocation(client, graphics, npc.getLocalLocation(), textOverlay, 0); if (textLoc == null) continue; - textLoc = new Point(textLoc.getX(), textLoc.getY() + 35); - Font oldFont = graphics.getFont(); graphics.setFont(new Font("Arial", Font.BOLD, 20)); Point pointShadow = new Point(textLoc.getX() + 1, textLoc.getY() + 1); - OverlayUtil.renderTextLocation(graphics, pointShadow, message, Color.BLACK); - OverlayUtil.renderTextLocation(graphics, textLoc, message, Color.CYAN); + OverlayUtil.renderTextLocation(graphics, pointShadow, textOverlay, Color.BLACK); + OverlayUtil.renderTextLocation(graphics, textLoc, textOverlay, Color.YELLOW); graphics.setFont(oldFont); } - } - } else { - if (config.highlightResources()) { - LocalPoint playerLocation = client.getLocalPlayer().getLocalLocation(); - Point mousePosition = client.getMouseCanvasPosition(); - for (TileObject object : plugin.getResources().keySet()) { - Tile tile = plugin.getResources().get(object); - if (tile.getPlane() == client.getPlane() - && object.getLocalLocation().distanceTo(playerLocation) < MAX_DISTANCE) { -// Area objectClickbox = object.getClickbox(); // Don't use Convex Hull; you'll lag. + // Draws the graphics on the boss. + if (GauntletUtils.isBoss(npc)) { + + final LocalPoint point = npc.getLocalLocation(); + + // Overlay the boss with a color on it's convex hull. + if (config.overlayBoss()) { + Polygon polygon = npc.getConvexHull(); - Polygon polygon = object.getCanvasTilePoly(); if (polygon != null) { - Color color = SystemColor.YELLOW; - BufferedImage icon; - - if (config.highlightResourcesIcons()) - switch (object.getId()) { - case 36064: // Crystal Deposit - case 35967: - icon = plugin.getCRYSTAL_DEPOSIT(); - break; - case 36066: // Phren Roots - case 35969: - icon = plugin.getPHREN_ROOTS(); - break; - case 36068: // Fishing Spot - case 35971: - icon = plugin.getFISHING_SPOT(); - break; - case 36070: // Grym Root - case 35973: - icon = plugin.getGRYM_ROOT(); - break; - case 36072: // Linum Tirinum - case 35975: - icon = plugin.getLINUM_TIRINUM(); - break; - default: - icon = null; - break; - } - else - icon = null; - - graphics.setColor(color); + Color color; + switch (plugin.currentPhase) { + case MAGIC: + color = Color.CYAN; + break; + case RANGE: + color = Color.GREEN; + break; + default: + color = Color.WHITE; + break; + } graphics.draw(polygon); graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 50)); graphics.fill(polygon); + } + } + + // Overlay of the boss with an icon denoting it's current attack style. + if (config.overlayBossPrayer()) { + BufferedImage attackIcon = null; + + switch (plugin.currentPhase) { + case MAGIC: + attackIcon = plugin.imageAttackMage; + break; + case RANGE: + attackIcon = plugin.imageAttackRange; + break; + default: + attackIcon = null; + break; + } + + if (attackIcon != null) { + Point imageLoc = Perspective.getCanvasImageLocation(client, point, attackIcon, npc.getLogicalHeight() / 2); + + if (imageLoc == null) + continue; + + graphics.drawImage(attackIcon, imageLoc.getX(), imageLoc.getY(), null); + } + } + + // This section handles any text overlays. + String textOverlay = new String(); + + // Handles the counter for the boss. + if (config.countBossAttacks()) { + textOverlay = Integer.toString(plugin.bossCounter); + } + + // Handles the counter for the player. + if (config.countPlayerAttacks()) { + if (textOverlay.length() > 0) + textOverlay += " | "; + textOverlay += Integer.toString(plugin.playerCounter); + } + + // Handles drawing the text onto the boss. + if (textOverlay.length() > 0) { + Point textLoc = Perspective.getCanvasTextLocation(client, graphics, point, textOverlay, npc.getLogicalHeight() / 2); + + if (textLoc == null) + continue; + + textLoc = new Point(textLoc.getX(), textLoc.getY() + 35); + + Font oldFont = graphics.getFont(); + + graphics.setFont(new Font("Arial", Font.BOLD, 20)); + Point pointShadow = new Point(textLoc.getX() + 1, textLoc.getY() + 1); + + OverlayUtil.renderTextLocation(graphics, pointShadow, textOverlay, Color.BLACK); + OverlayUtil.renderTextLocation(graphics, textLoc, textOverlay, Color.CYAN); + + graphics.setFont(oldFont); + } + } + } + } else { + // This section overlays all resources. + LocalPoint playerLocation = client.getLocalPlayer().getLocalLocation(); + + for (TileObject object : plugin.resources.keySet()) { + Tile tile = plugin.resources.get(object); + if (tile.getPlane() == client.getPlane() + && object.getLocalLocation().distanceTo(playerLocation) < MAX_DISTANCE) { + + // Don't use Convex Hull Clickbox. As the room start to fill up, your FPS will dip. + Polygon polygon = object.getCanvasTilePoly(); + + if (polygon != null) { + // This section will highlight the resource with color. + if (config.highlightResourcesColor()) { + Color color = SystemColor.YELLOW; + + graphics.setColor(color); + graphics.draw(polygon); + graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 50)); + graphics.fill(polygon); + } + + // This section will overlay the resource with an icon. + if (config.highlightResourcesIcons()) { + int id = object.getId(); + BufferedImage icon = null; + + if (GauntletUtils.arrayContainsInteger(GauntletUtils.CRYSTAL_DEPOSIT, id)) { + icon = plugin.imageCrystalDeposit; + } else if (GauntletUtils.arrayContainsInteger(GauntletUtils.PHREN_ROOTS, id)) { + icon = plugin.imagePhrenRoots; + } else if (GauntletUtils.arrayContainsInteger(GauntletUtils.FISHING_SPOTS, id)) { + icon = plugin.imageFishingSpot; + } else if (GauntletUtils.arrayContainsInteger(GauntletUtils.GRYM_ROOTS, id)) { + icon = plugin.imageGrymRoot; + } else if (GauntletUtils.arrayContainsInteger(GauntletUtils.LINUM_TIRINUM, id)) { + icon = plugin.imageLinumTirinum; + } if (icon != null) { Rectangle bounds = polygon.getBounds(); diff --git a/GauntletPlugin.java b/GauntletPlugin.java index 848b6ba..aad97ec 100644 --- a/GauntletPlugin.java +++ b/GauntletPlugin.java @@ -11,38 +11,31 @@ package net.runelite.client.plugins.gauntlet; import com.google.inject.Provides; import lombok.AccessLevel; import lombok.Getter; -import lombok.Setter; import net.runelite.api.Actor; import net.runelite.api.Client; +import net.runelite.api.GameObject; import net.runelite.api.GameState; import net.runelite.api.HeadIcon; import net.runelite.api.NPC; import net.runelite.api.NPCComposition; import net.runelite.api.Player; import net.runelite.api.Projectile; +import net.runelite.api.Skill; import net.runelite.api.SoundEffectID; -import net.runelite.api.SoundEffectVolume; import net.runelite.api.Tile; -import net.runelite.api.TileObject; import net.runelite.api.events.AnimationChanged; -import net.runelite.api.events.DecorativeObjectChanged; -import net.runelite.api.events.DecorativeObjectDespawned; -import net.runelite.api.events.DecorativeObjectSpawned; +import net.runelite.api.events.ConfigChanged; 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.GameTick; -import net.runelite.api.events.GroundObjectChanged; -import net.runelite.api.events.GroundObjectDespawned; -import net.runelite.api.events.GroundObjectSpawned; import net.runelite.api.events.NpcDespawned; import net.runelite.api.events.NpcSpawned; -import net.runelite.api.events.WallObjectChanged; -import net.runelite.api.events.WallObjectDespawned; -import net.runelite.api.events.WallObjectSpawned; +import net.runelite.api.events.VarbitChanged; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.SkillIconManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.ui.overlay.OverlayManager; @@ -64,29 +57,32 @@ import java.util.Set; public class GauntletPlugin extends Plugin { - @Getter - private BufferedImage CRYSTAL_DEPOSIT; + public BufferedImage imageCrystalDeposit; + public BufferedImage imagePhrenRoots; + public BufferedImage imageFishingSpot; + public BufferedImage imageGrymRoot; + public BufferedImage imageLinumTirinum; - @Getter - private BufferedImage PHREN_ROOTS; + public BufferedImage imageAttackRange; + public BufferedImage imageAttackMage; + public BufferedImage imageAttackPrayer; - @Getter - private BufferedImage FISHING_SPOT; + public final Map resources = new HashMap<>(); - @Getter - private BufferedImage GRYM_ROOT; + public Set projectiles = new HashSet<>(); - @Getter - private BufferedImage LINUM_TIRINUM; + public int bossCounter = 0; // Attacks until the boss changes attack styles. + public BossAttackPhase currentPhase = BossAttackPhase.UNKNOWN; - @Getter - private BufferedImage ATTACK_RANGE; + public int playerCounter = 6; // Attacks until the boss changes prayer. - @Getter - private BufferedImage ATTACK_MAGE; + public enum BossAttackPhase { + MAGIC, RANGE, UNKNOWN; + } - @Getter - private BufferedImage ATTACK_PRAYER; + public enum BossAttack { + MAGIC, RANGE, PRAYER, LIGHTNING; + } @Inject private Client client; @@ -98,149 +94,178 @@ public class GauntletPlugin extends Plugin { @Inject private GauntletOverlay overlay; + @Inject + private SkillIconManager iconManager; + @Inject private GauntletConfig config; - @Getter - private final Map resources = new HashMap<>(); - - @Getter - private Set projectiles = new HashSet<>(); - @Provides GauntletConfig getConfig(ConfigManager configManager) { return configManager.getConfig(GauntletConfig.class); } - @Getter - @Setter - private int attacksLeft; + @Inject + private GauntletTimer timer; - @Getter - @Setter - private int playerCounter; + private boolean timerVisible = true; - @Getter - @Setter - private Style currentStyle; - - public static enum Style { - MAGIC, RANGE, UNKNOWN; - } - - public static enum Attack { - MAGIC, RANGE, PRAYER, LIGHTNING; - } + public boolean tornadoesActive = false; + public int tornadoTicks = GauntletUtils.TORNADO_TICKS; @Override protected void startUp() { - CRYSTAL_DEPOSIT = ImageUtil.getResourceStreamFromClass(getClass(), "/skill_icons/mining.png"); - PHREN_ROOTS = ImageUtil.getResourceStreamFromClass(getClass(), "/skill_icons/woodcutting.png"); - FISHING_SPOT = ImageUtil.getResourceStreamFromClass(getClass(), "/skill_icons/fishing.png"); - GRYM_ROOT = ImageUtil.getResourceStreamFromClass(getClass(), "/skill_icons/herblore.png"); - LINUM_TIRINUM = ImageUtil.getResourceStreamFromClass(getClass(), "/skill_icons/farming.png"); + loadImages(config.iconSize()); - ATTACK_MAGE = ImageUtil.getResourceStreamFromClass(getClass(), "/skill_icons/magic.png"); - ATTACK_RANGE = ImageUtil.getResourceStreamFromClass(getClass(), "/skill_icons/ranged.png"); - ATTACK_PRAYER = ImageUtil.getResourceStreamFromClass(getClass(), "/skill_icons/prayer.png"); + resetCounters(); - playerCounter = 6; - attacksLeft = 0; - currentStyle = Style.UNKNOWN; - projectiles.clear(); + timerVisible = config.displayTimerWidget(); + timer.resetStates(); + timer.initStates(); + + if (timerVisible) { + overlayManager.add(timer); + } overlayManager.add(overlay); } @Override protected void shutDown() { - playerCounter = 6; - attacksLeft = 0; - currentStyle = Style.UNKNOWN; - projectiles.clear(); + resetCounters(); + timer.resetStates(); + + if (timerVisible) { + overlayManager.remove(timer); + timerVisible = false; + } overlayManager.remove(overlay); + } - CRYSTAL_DEPOSIT = null; - PHREN_ROOTS = null; - FISHING_SPOT = null; - GRYM_ROOT = null; - LINUM_TIRINUM = null; + /** + * Called when images need to be resized. + */ + private void loadImages(int imageSize) { + imageCrystalDeposit = ImageUtil.resizeImage(iconManager.getSkillImage(Skill.MINING, true), imageSize, imageSize); + imagePhrenRoots = ImageUtil.resizeImage(iconManager.getSkillImage(Skill.WOODCUTTING, true), imageSize, imageSize); + imageFishingSpot = ImageUtil.resizeImage(iconManager.getSkillImage(Skill.FISHING, true), imageSize, imageSize); + imageGrymRoot = ImageUtil.resizeImage(iconManager.getSkillImage(Skill.HERBLORE, true), imageSize, imageSize); + imageLinumTirinum = ImageUtil.resizeImage(iconManager.getSkillImage(Skill.FARMING, true), imageSize, imageSize); - ATTACK_MAGE = null; - ATTACK_RANGE = null; - ATTACK_PRAYER = null; + imageAttackMage = ImageUtil.resizeImage(iconManager.getSkillImage(Skill.MAGIC, true), imageSize, imageSize); + imageAttackRange = ImageUtil.resizeImage(iconManager.getSkillImage(Skill.RANGED, true), imageSize, imageSize); + imageAttackPrayer = ImageUtil.resizeImage(iconManager.getSkillImage(Skill.PRAYER, true), imageSize, imageSize); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) { + if (event.getGroup() == null || event.getKey() == null || !event.getGroup().equals("Gauntlet")) + return; + + if (event.getKey().equals("displayTimerWidget")) { + if (config.displayTimerWidget() && !timerVisible) { + overlayManager.add(timer); + timerVisible = true; + } else if (!config.displayTimerWidget() && timerVisible) { + overlayManager.remove(timer); + timerVisible = false; + } + } else if (event.getKey().equals("iconSize")) { + loadImages(config.iconSize()); + } + } + + @Subscribe + public void onVarbitChanged(VarbitChanged event) { + // This handles the timer based on varp states. + timer.checkStates(true); } @Subscribe public void onNpcSpawned(NpcSpawned event) { NPC npc = event.getNpc(); - if (npc == null || npc.getName() == null || !npc.getName().matches("(Crystalline|Corrupted) Hunllef")) - return; - - playerCounter = 6; - attacksLeft = 0; - currentStyle = Style.UNKNOWN; - projectiles.clear(); + if (GauntletUtils.isBoss(npc)) + resetCounters(); } @Subscribe public void onNpcDespawned(NpcDespawned event) { NPC npc = event.getNpc(); - if (npc == null || npc.getName() == null || !npc.getName().matches("(Crystalline|Corrupted) Hunllef")) - return; + if (GauntletUtils.isBoss(npc)) + resetCounters(); + } + + /** + * Reset all boss and player related counter resources. + */ + private void resetCounters() { + bossCounter = 0; + currentPhase = BossAttackPhase.UNKNOWN; playerCounter = 6; - attacksLeft = 0; - currentStyle = Style.UNKNOWN; + + tornadoesActive = false; + tornadoTicks = GauntletUtils.TORNADO_TICKS; + projectiles.clear(); } - public void doAttack(Attack style) { - if (style == Attack.PRAYER) { - if (config.alertPrayerDrain()) { - client.playSoundEffect(SoundEffectID.MAGIC_SPLASH_BOING, SoundEffectVolume.MEDIUM_HIGH); + /** + * Method is called when an attack is performed by the boss. + * + * @param style BossAttack ; the attack performed by the boss + */ + public void doAttack(BossAttack style) { + // Prayer attacks are magic related. We only care if it's prayer to play the unique sound. + // This section will change all PRAYER attacks to MAGIC. + if (style == BossAttack.PRAYER) { + if (config.uniquePrayerAudio()) { + client.playSoundEffect(SoundEffectID.MAGIC_SPLASH_BOING); } - style = Attack.MAGIC; + + style = BossAttack.MAGIC; } - if (style == Attack.LIGHTNING) { - attacksLeft--; - } else if (style == Attack.RANGE) { - if (currentStyle != Style.RANGE) { - currentStyle = Style.RANGE; - attacksLeft = 3; + // This section will decrement the boss attack counter by 1. + if (style == BossAttack.LIGHTNING) { + bossCounter--; + } else if (style == BossAttack.RANGE) { + if (currentPhase != BossAttackPhase.RANGE) { + currentPhase = BossAttackPhase.RANGE; + bossCounter = 3; } else { - attacksLeft--; + bossCounter--; } - } else if (style == Attack.MAGIC) { - if (currentStyle != Style.MAGIC) { - currentStyle = Style.MAGIC; - attacksLeft = 3; + } else if (style == BossAttack.MAGIC) { + if (currentPhase != BossAttackPhase.MAGIC) { + currentPhase = BossAttackPhase.MAGIC; + bossCounter = 3; } else { - attacksLeft--; + bossCounter--; } } - if (attacksLeft <= 0) { - Style newStyle; + // This section will reset the boss attack counter if necessary. + if (bossCounter <= 0) { + BossAttackPhase nextPhase; - switch (currentStyle) { + switch (currentPhase) { case MAGIC: - attacksLeft = 4; - newStyle = Style.RANGE; + bossCounter = 4; + nextPhase = BossAttackPhase.RANGE; break; case RANGE: - attacksLeft = 4; - newStyle = Style.MAGIC; + bossCounter = 4; + nextPhase = BossAttackPhase.MAGIC; break; default: - attacksLeft = 0; - newStyle = Style.UNKNOWN; + bossCounter = 0; + nextPhase = BossAttackPhase.UNKNOWN; break; } - currentStyle = newStyle; + currentPhase = nextPhase; } } @@ -248,49 +273,30 @@ public class GauntletPlugin extends Plugin { public void onAnimationChanged(AnimationChanged event) { Actor actor = event.getActor(); - if (actor instanceof Player && this.inBoss()) { + // This section handles the player counter. + if (actor instanceof Player && GauntletUtils.inBoss(client)) { Player p = (Player) actor; if (p.getName().equals(client.getLocalPlayer().getName())) { int id = p.getAnimation(); if (id != -1) { - int[] all_styles = new int[]{395, 401, 400, 401, 386, 390, 422, 423, 401, 428, 440, 426, 1167}; + // Detect the overhead prayer that the boss is using and determine the list of animations that the boss is currently immune to. int[] wrong_style = new int[]{}; for (NPC npc : this.client.getNpcs()) { - if (npc != null && npc.getName() != null && npc.getName().matches("(Crystalline|Corrupted) Hunllef")) { + if (GauntletUtils.isBoss(npc)) { NPCComposition comp = npc.getComposition(); if (comp != null) { HeadIcon prayer = comp.getOverheadIcon(); if (prayer != null) { switch (prayer) { case MELEE: - wrong_style = new int[]{ - 395, // Axe Slash - 401, // Axe Crush - - 400, // Pick Crush - 401, // Pick Stab - - 386, // Harpoon Stab - 390, // Harpoon Slash - - 422, // Unarmed Punch - 423, // Unarmed Kick - - 401, // Crystal Scepter - 428, // Crystal Halberd Jab & Fend - 440 // Crystal Halberd Swipe - }; + wrong_style = GauntletUtils.MELEE_ANIMATIONS; break; case RANGED: - wrong_style = new int[]{ - 426 // Crystal Bow - }; + wrong_style = GauntletUtils.RANGE_ANIMATIONS; break; case MAGIC: - wrong_style = new int[]{ - 1167 // Crystal Staff - }; + wrong_style = GauntletUtils.MAGE_ANIMATIONS; break; default: wrong_style = new int[]{}; @@ -303,32 +309,25 @@ public class GauntletPlugin extends Plugin { } } - outerloop: - for (int action : all_styles) { - if (action == id) { - for (int wrong_action : wrong_style) { - if (action == wrong_action) - break outerloop; - } - - playerCounter--; - if (playerCounter <= 0) { - playerCounter = 6; - } - - break outerloop; + // Check that the player performed an attack animation that the boss isn't immune to. + if (GauntletUtils.arrayContainsInteger(GauntletUtils.PLAYER_ANIMATIONS, id) && !GauntletUtils.arrayContainsInteger(wrong_style, id)) { + // Attack is valid. Decrement the player attack counter and reset it if necessary. + playerCounter--; + if (playerCounter <= 0) { + playerCounter = 6; } } } } } + // This section handles the boss attack counter if they perform a lightning attack. if (actor instanceof NPC) { NPC npc = (NPC) actor; - if (npc != null && npc.getName() != null && npc.getName().matches("(Crystalline|Corrupted) Hunllef")) { + if (GauntletUtils.isBoss(npc)) { int id = npc.getAnimation(); - if (id == 8418) { - this.doAttack(Attack.LIGHTNING); + if (id == GauntletUtils.BOSS_ANIMATION_LIGHTNING) { + this.doAttack(BossAttack.LIGHTNING); } } } @@ -336,87 +335,63 @@ public class GauntletPlugin extends Plugin { @Subscribe public void onGameTick(GameTick event) { + // This handles the timer based on player health. + timer.checkStates(false); + + // This section handles the boss attack counter if they perform a projectile attack. Set newProjectiles = new HashSet<>(); + for (Projectile projectile : client.getProjectiles()) { newProjectiles.add(projectile); if (!projectiles.contains(projectile)) { int id = projectile.getId(); - if (id == 1707 || id == 1708) { - this.doAttack(Attack.MAGIC); - } else if (id == 1713 || id == 1714) { - this.doAttack(Attack.PRAYER); - } else if (id == 1711 || id == 1712) { - this.doAttack(Attack.RANGE); + if (GauntletUtils.arrayContainsInteger(GauntletUtils.PROJECTILE_MAGIC, id)) { + this.doAttack(BossAttack.MAGIC); + } else if (GauntletUtils.arrayContainsInteger(GauntletUtils.PROJECTILE_PRAYER, id)) { + this.doAttack(BossAttack.PRAYER); + } else if (GauntletUtils.arrayContainsInteger(GauntletUtils.PROJECTILE_RANGE, id)) { + this.doAttack(BossAttack.RANGE); } } } projectiles.clear(); projectiles = newProjectiles; + + // This section handles lightning decay. + if (!this.tornadoesActive) { + for (NPC npc : client.getNpcs()) { + if (GauntletUtils.isTornado(npc)) { + tornadoesActive = true; + tornadoTicks = GauntletUtils.TORNADO_TICKS; + break; + } + } + } else { + tornadoTicks--; + if (tornadoTicks <= 0) { + tornadoesActive = false; + tornadoTicks = GauntletUtils.TORNADO_TICKS; + } + } } @Subscribe public void onGameObjectSpawned(GameObjectSpawned event) { - onTileObject(event.getTile(), null, event.getGameObject()); + onGameObject(event.getTile(), null, event.getGameObject()); } @Subscribe public void onGameObjectChanged(GameObjectChanged event) { - onTileObject(event.getTile(), event.getPrevious(), event.getGameObject()); + onGameObject(event.getTile(), event.getPrevious(), event.getGameObject()); } @Subscribe public void onGameObjectDespawned(GameObjectDespawned event) { - onTileObject(event.getTile(), event.getGameObject(), null); + onGameObject(event.getTile(), event.getGameObject(), null); } - @Subscribe - public void onGroundObjectSpawned(GroundObjectSpawned event) { - onTileObject(event.getTile(), null, event.getGroundObject()); - } - - @Subscribe - public void onGroundObjectChanged(GroundObjectChanged event) { - onTileObject(event.getTile(), event.getPrevious(), event.getGroundObject()); - } - - @Subscribe - public void onGroundObjectDespawned(GroundObjectDespawned event) { - onTileObject(event.getTile(), event.getGroundObject(), null); - } - - @Subscribe - public void onWallObjectSpawned(WallObjectSpawned event) { - onTileObject(event.getTile(), null, event.getWallObject()); - } - - @Subscribe - public void onWallObjectChanged(WallObjectChanged event) { - onTileObject(event.getTile(), event.getPrevious(), event.getWallObject()); - } - - @Subscribe - public void onWallObjectDespawned(WallObjectDespawned event) { - onTileObject(event.getTile(), event.getWallObject(), null); - } - - @Subscribe - public void onDecorativeObjectSpawned(DecorativeObjectSpawned event) { - onTileObject(event.getTile(), null, event.getDecorativeObject()); - } - - @Subscribe - public void onDecorativeObjectChanged(DecorativeObjectChanged event) { - onTileObject(event.getTile(), event.getPrevious(), event.getDecorativeObject()); - } - - @Subscribe - public void onDecorativeObjectDespawned(DecorativeObjectDespawned event) { - onTileObject(event.getTile(), event.getDecorativeObject(), null); - } - - @Subscribe public void onGameStateChanged(GameStateChanged event) { if (event.getGameState() == GameState.LOADING) { @@ -424,7 +399,14 @@ public class GauntletPlugin extends Plugin { } } - private void onTileObject(Tile tile, TileObject oldObject, TileObject newObject) { + /** + * Called when a GameObject spawns, changes, or despawns. + * + * @param tile Tile + * @param oldObject TileObject + * @param newObject TileObject + */ + private void onGameObject(Tile tile, GameObject oldObject, GameObject newObject) { resources.remove(oldObject); if (newObject == null) { @@ -433,27 +415,8 @@ public class GauntletPlugin extends Plugin { int id = newObject.getId(); - int[] ids = { - 36068, // Fishing Spot (Harpoon) - 35967, - 36066, // Phren Roots (Axe) - 35969, - 36070, // Grym Root - 35971, - 36072, // Linum Tirinum - 35973, - 36064, // Crystal Deposit - 35975 - }; - - for (int i : ids) { - if (i == id) { - resources.put(newObject, tile); - } + if (GauntletUtils.arrayContainsInteger(GauntletUtils.RESOURCE_IDS, id)) { + resources.put(newObject, tile); } } - - public boolean inBoss() { - return client.getVarbitValue(client.getVarps(), 9177) == 1; - } } diff --git a/GauntletTimer.java b/GauntletTimer.java new file mode 100644 index 0000000..0b2924b --- /dev/null +++ b/GauntletTimer.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2018, Seth + * 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.gauntlet; + +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.Player; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayMenuEntry; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayPriority; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.PanelComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; + +import javax.inject.Inject; +import java.awt.*; + +import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG; +import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE; + +class GauntletTimer extends Overlay { + + private final Client client; + + private final GauntletPlugin plugin; + private final GauntletConfig config; + + private final PanelComponent panelComponent = new PanelComponent(); + + public enum RaidState { + UNKNOWN, IN_RAID, IN_BOSS; + } + + public long timeRaidStart = -1L; + public long timeBossEnter = -1L; + + public RaidState currentState = RaidState.UNKNOWN; + + /** + * Resets the timer. + */ + public void resetStates() { + timeRaidStart = -1L; + timeBossEnter = -1L; + + currentState = RaidState.UNKNOWN; + } + + /** + * This is called when the player resets the plugin mid-raid. We do not want to confuse the timer. + */ + public void initStates() { + timeRaidStart = -1L; + timeBossEnter = -1L; + + if (client.getGameState() != GameState.STARTING && client.getGameState() != GameState.UNKNOWN) { + if (GauntletUtils.inRaid(client)) { + currentState = RaidState.IN_RAID; + if (GauntletUtils.inBoss(client)) { + currentState = RaidState.IN_BOSS; + } + } else + currentState = RaidState.UNKNOWN; + } + } + + /** + * Converts the different between two epoch times into minutes:seconds format. + * + * @param epochA long + * @param epochB long + * @return String + */ + private String calculateElapsedTime(long epochA, long epochB) { + long max = Math.max(epochA, epochB); + long min = Math.min(epochA, epochB); + + long elapsedEpoch = max - min; + long seconds = elapsedEpoch / 1000L; + + long minutes = seconds / 60L; + seconds = seconds % 60; + + if (seconds == 0) { + return minutes + ":00"; + } + + if (seconds < 10) { + return minutes + ":0" + seconds; + } + + return minutes + ":" + seconds; + } + + /** + * Called when varbit changes. See if the the raid state has changed. + */ + public void checkStates(boolean checkVarps) { + final Player p = client.getLocalPlayer(); + + if(p == null) + return; + + if (checkVarps) { + if (currentState == RaidState.UNKNOWN && GauntletUtils.inRaid(client) && !GauntletUtils.inBoss(client) && p.getHealthRatio() != 0) { // Player has started a new raid. + currentState = RaidState.IN_RAID; + timeRaidStart = System.currentTimeMillis(); + } else if (currentState == RaidState.IN_RAID) { + if (GauntletUtils.inRaid(client)) { + if (GauntletUtils.inBoss(client)) { // Player has begun the boss fight. + printPrepTime(); + currentState = RaidState.IN_BOSS; + timeBossEnter = System.currentTimeMillis(); + } + } else { // Player has died or left the raid. + printPrepTime(); + resetStates(); + } + } else if (currentState == RaidState.IN_BOSS) { + if (!GauntletUtils.inBoss(client) || !GauntletUtils.inRaid(client)) { // Player has killed the boss. + resetStates(); + } + } + } else { + if (currentState == RaidState.IN_BOSS) { + if (p.getHealthRatio() == 0) { // Boss has killed the player. + printBossTime(); + resetStates(); + } + } + } + } + + private void printPrepTime() { + if (!config.displayTimerChat() || timeRaidStart == -1L) + return; + + String elapsedTime = calculateElapsedTime(System.currentTimeMillis(), timeRaidStart); + this.client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", "Preparation time: " + elapsedTime + ".", null); + } + + private void printBossTime() { + if (!config.displayTimerChat() || timeRaidStart == -1L || timeBossEnter == -1L) + return; + + String elapsedBossTime = calculateElapsedTime(System.currentTimeMillis(), timeBossEnter); + String elapsedPrepTime = calculateElapsedTime(timeRaidStart, timeBossEnter); + String elapsedTotalTime = calculateElapsedTime(System.currentTimeMillis(), timeRaidStart); + + this.client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", "Challenge duration: " + elapsedTotalTime + ".", null); + this.client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", "Preparation time: " + elapsedPrepTime + ". Player death time: " + elapsedBossTime + ".", null); + } + + @Inject + public GauntletTimer(Client client, GauntletPlugin plugin, GauntletConfig config) { + super(plugin); + + setPosition(OverlayPosition.ABOVE_CHATBOX_RIGHT); + setPriority(OverlayPriority.HIGH); + + this.client = client; + this.plugin = plugin; + this.config = config; + + getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Gauntlet Timer Overlay")); + } + + @Override + public Dimension render(Graphics2D graphics) { + if (currentState == RaidState.UNKNOWN || timeRaidStart == -1L) { + return null; + } + + panelComponent.getChildren().clear(); + + panelComponent.getChildren().add(TitleComponent.builder().text("Gauntlet Timer").color(Color.WHITE).build()); + + String elapsedPrepTime, elapsedBossTime, elapsedTotalTime; + elapsedTotalTime = calculateElapsedTime(System.currentTimeMillis(), timeRaidStart); + + if (currentState == RaidState.IN_RAID) { + elapsedPrepTime = calculateElapsedTime(timeRaidStart, System.currentTimeMillis()); + elapsedBossTime = "0:00"; + } else { + elapsedPrepTime = calculateElapsedTime(timeRaidStart, timeBossEnter); + elapsedBossTime = calculateElapsedTime(System.currentTimeMillis(), timeBossEnter); + } + + panelComponent.getChildren().add(LineComponent.builder().left("Preparation").right(elapsedPrepTime).build()); + panelComponent.getChildren().add(LineComponent.builder().left("Boss Fight").right(elapsedBossTime).build()); + panelComponent.getChildren().add(LineComponent.builder().left("Total Time").right(elapsedTotalTime).build()); + + return panelComponent.render(graphics); + } +} diff --git a/GauntletUtils.java b/GauntletUtils.java new file mode 100644 index 0000000..0ad41ff --- /dev/null +++ b/GauntletUtils.java @@ -0,0 +1,153 @@ +package net.runelite.client.plugins.gauntlet; + +import net.runelite.api.Client; +import net.runelite.api.NPC; + +public class GauntletUtils { + + public static final int[] MELEE_ANIMATIONS = new int[]{ + 395, // Axe Slash + 401, // Axe Crush + + 400, // Pick Crush + 401, // Pick Stab + + 386, // Harpoon Stab + 390, // Harpoon Slash + + 422, // Unarmed Punch + 423, // Unarmed Kick + + 401, // Crystal Scepter + 428, // Crystal Halberd Jab & Fend + 440 // Crystal Halberd Swipe + }; + + public static final int[] RANGE_ANIMATIONS = new int[]{ + 426 // Crystal Bow + }; + + public static final int[] MAGE_ANIMATIONS = new int[]{ + 1167 // Crystal Staff + }; + + public static final int[] PLAYER_ANIMATIONS = concatIntArray(MELEE_ANIMATIONS, RANGE_ANIMATIONS, MAGE_ANIMATIONS); + + public static final int[] CRYSTAL_DEPOSIT = new int[]{ + 36064, // Normal Mining Spot + 35975 // Corrupted Mining Spot + }; + + public static final int[] PHREN_ROOTS = new int[]{ + 36066, // Normal Woodcutting Spot + 35969 // Corrupted Woodcutting Spot + }; + + public static final int[] FISHING_SPOTS = new int[]{ + 36068, // Normal Fishing Spot + 35967 // Corrupted Fishing Spot + }; + public static final int[] GRYM_ROOTS = new int[]{ + 36070, // Normal Herblore Spot + 35971 // Corrupted Herblore Spot + }; + + public static final int[] LINUM_TIRINUM = new int[]{ + 36072, // Normal Farming Spot + 35973, // Corrupted Farming Spot + }; + + public static final int[] RESOURCE_IDS = concatIntArray(CRYSTAL_DEPOSIT, PHREN_ROOTS, FISHING_SPOTS, GRYM_ROOTS, LINUM_TIRINUM); + + public static final int[] PROJECTILE_MAGIC = new int[]{1707, 1708}; + public static final int[] PROJECTILE_RANGE = new int[]{1711, 1712}; + public static final int[] PROJECTILE_PRAYER = new int[]{1713, 1714}; + + public static final int VARP_BOSS_ROOM = 9177; + public static final int VARP_RAID_ROOM = 9178; + + public static final int BOSS_ANIMATION_LIGHTNING = 8418; + + public static final int[] TORNADO_IDS = new int[]{9025, 9039}; + public static final int TORNADO_TICKS = 20; + + /** + * Concatenate all integers in the array into a single array. + * + * @param arrays array of int arrays + * @return int array + */ + public static int[] concatIntArray(int[]... arrays) { + int length = 0; + + for (int[] array : arrays) { + length += array.length; + } + + int[] returnArray = new int[length]; + int currentIndex = 0; + + for (int[] array : arrays) { + for (int value : array) { + returnArray[currentIndex] = value; + currentIndex++; + } + } + + return returnArray; + } + + /** + * Determines whether or not an integer array contains a specific value. + * + * @param intArray int[] + * @param value int + * @return boolean + */ + public static boolean arrayContainsInteger(int[] intArray, int value) { + for (int i : intArray) + if (i == value) + return true; + return false; + } + + /** + * Returns whether or not a NPC is a tornado. + * + * @param npc NPC + * @return boolean + */ + public static boolean isTornado(NPC npc) { + return npc != null && arrayContainsInteger(TORNADO_IDS, npc.getId()); + } + + /** + * Returns whether or not a NPC is the boss. + * + * @param npc NPC + * @return boolean + */ + public static boolean isBoss(NPC npc) { + return npc != null && npc.getName() != null && npc.getName().matches("(Crystalline|Corrupted) Hunllef"); + } + + /** + * Returns whether or not the player is current in the Gauntlet. + * + * @param client Client + * @return boolean + */ + public static boolean inRaid(Client client) { + return client.getVarbitValue(client.getVarps(), VARP_RAID_ROOM) == 1; + } + + /** + * Returns whether or not the player is currently in the boss room. + * + * @param client Client + * @return boolean + */ + public static boolean inBoss(Client client) { + return client.getVarbitValue(client.getVarps(), VARP_BOSS_ROOM) == 1; + } +}