This commit is contained in:
BuildTools
2019-07-29 20:32:24 -04:00
parent 182f254e28
commit a89f6030c0
5 changed files with 817 additions and 395 deletions

View File

@@ -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;
}
}

View File

@@ -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();

View File

@@ -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<GameObject, Tile> resources = new HashMap<>();
@Getter
private BufferedImage GRYM_ROOT;
public Set<Projectile> 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<TileObject, Tile> resources = new HashMap<>();
@Getter
private Set<Projectile> 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<Projectile> 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;
}
}

219
GauntletTimer.java Normal file
View File

@@ -0,0 +1,219 @@
/*
* Copyright (c) 2018, Seth <http://github.com/sethtroll>
* 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: <col=ff0000>" + elapsedTime + "<col=000000>.", 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: <col=ff0000>" + elapsedTotalTime + "<col=000000>.", null);
this.client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", "Preparation time: <col=ff0000>" + elapsedPrepTime + "<col=000000>. Player death time: <col=ff0000>" + elapsedBossTime + "<col=000000>.", 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);
}
}

153
GauntletUtils.java Normal file
View File

@@ -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;
}
}