alchemicalhydra

This commit is contained in:
Kyleeld
2019-04-24 01:44:14 +01:00
committed by GitHub
parent f8363b9c23
commit b5f4265012
5 changed files with 545 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
package net.runelite.client.plugins.alchemicalhydra;
import javax.inject.Singleton;
import lombok.Getter;
import lombok.Setter;
import net.runelite.api.Prayer;
import net.runelite.api.ProjectileID;
@Singleton
class Hydra
{
enum AttackStyle
{
MAGIC(ProjectileID.HYDRA_MAGIC, Prayer.PROTECT_FROM_MAGIC),
RANGED(ProjectileID.HYDRA_RANGED, Prayer.PROTECT_FROM_MISSILES);
@Getter
private int projId;
@Getter
private Prayer prayer;
AttackStyle(int projId, Prayer prayer)
{
this.projId = projId;
this.prayer = prayer;
}
}
@Getter
@Setter
private HydraPhase phase;
@Getter
@Setter
private int attackCount;
@Getter
@Setter
private int nextSwitch;
@Getter
@Setter
private int nextSpecial;
@Getter
@Setter
private AttackStyle nextAttack;
@Getter
@Setter
private AttackStyle lastAttack;
Hydra()
{
this.phase = HydraPhase.ONE;
this.nextAttack = AttackStyle.MAGIC;
this.nextSpecial = 3;
this.nextSwitch = phase.getAttacksPerSwitch();
this.attackCount = 0;
}
}

View File

@@ -0,0 +1,132 @@
package net.runelite.client.plugins.alchemicalhydra;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import javax.inject.Inject;
import javax.inject.Singleton;
import net.runelite.api.Client;
import net.runelite.api.Prayer;
import net.runelite.api.SpriteID;
import net.runelite.client.game.SpriteManager;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.components.InfoBoxComponent;
import net.runelite.client.ui.overlay.components.PanelComponent;
@Singleton
class HydraOverlay extends Overlay
{
private final HydraPlugin plugin;
private final Client client;
private final SpriteManager spriteManager;
private final PanelComponent panelComponent = new PanelComponent();
private static final Color redBgCol = new Color(156, 0, 0, 156);
private static final Color yelBgCol = new Color(200, 156, 0, 156);
private static final Color grnBgCol = new Color(0, 156, 0, 156);
@Inject
HydraOverlay(HydraPlugin plugin, Client client, SpriteManager spriteManager)
{
this.plugin = plugin;
this.client = client;
this.spriteManager = spriteManager;
setPosition(OverlayPosition.BOTTOM_RIGHT);
panelComponent.setOrientation(PanelComponent.Orientation.VERTICAL);
}
@Override
public Dimension render(Graphics2D graphics2D)
{
Hydra hydra = plugin.getHydra();
panelComponent.getChildren().clear();
if (hydra == null || client == null)
{
return null;
}
//Add spec overlay first, to keep it above pray
HydraPhase phase = hydra.getPhase();
int attackCount = hydra.getAttackCount();
int nextSpec = hydra.getNextSpecial() - attackCount;
if (nextSpec <= 3)
{
InfoBoxComponent specComponent = new InfoBoxComponent();
if (nextSpec == 0)
{
specComponent.setBackgroundColor(redBgCol);
}
else if (nextSpec == 1)
{
specComponent.setBackgroundColor(yelBgCol);
}
Image specImg = scaleImg(spriteManager.getSprite(phase.getSpecImage(), 0));
specComponent.setImage(specImg);
specComponent.setText(" " + (nextSpec)); //hacky way to not have to figure out how to move text
specComponent.setPreferredSize(new Dimension(40, 40));
panelComponent.getChildren().add(specComponent);
}
Prayer nextPrayer = hydra.getNextAttack().getPrayer();
Image prayImg = scaleImg(getPrayerImage(hydra.getNextAttack().getPrayer()));
int nextSwitch = hydra.getNextSwitch();
InfoBoxComponent prayComponent = new InfoBoxComponent();
if (nextSwitch == 1)
{
prayComponent.setBackgroundColor(client.isPrayerActive(nextPrayer) ? yelBgCol : redBgCol);
}
else
{
prayComponent.setBackgroundColor(client.isPrayerActive(nextPrayer) ? grnBgCol : redBgCol);
}
prayComponent.setImage(prayImg);
prayComponent.setText(" " + nextSwitch);
prayComponent.setColor(Color.white);
prayComponent.setPreferredSize(new Dimension(40, 40));
panelComponent.getChildren().add(prayComponent);
panelComponent.setPreferredSize(new Dimension(40, 0));
panelComponent.setBorder(new Rectangle(0, 0, 0, 0));
return panelComponent.render(graphics2D);
}
private BufferedImage getPrayerImage(Prayer pray)
{
return pray == Prayer.PROTECT_FROM_MAGIC
? spriteManager.getSprite(SpriteID.PRAYER_PROTECT_FROM_MAGIC, 0)
: spriteManager.getSprite(SpriteID.PRAYER_PROTECT_FROM_MISSILES, 0);
}
private Image scaleImg(final Image img)
{
if (img == null)
{
return null;
}
final double width = img.getWidth(null);
final double height = img.getHeight(null);
final double size = 36; // Limit size to 2 as that is minimum size not causing breakage
final double scalex = size / width;
final double scaley = size / height;
final double scale = Math.min(scalex, scaley);
final int newWidth = (int) (width * scale);
final int newHeight = (int) (height * scale);
final BufferedImage scaledImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
final Graphics g = scaledImage.createGraphics();
g.drawImage(img, 0, 0, newWidth, newHeight, null);
g.dispose();
return scaledImage;
}
}

View File

@@ -0,0 +1,42 @@
package net.runelite.client.plugins.alchemicalhydra;
import lombok.Getter;
import net.runelite.api.AnimationID;
import net.runelite.api.ProjectileID;
import net.runelite.api.SpriteID;
enum HydraPhase
{
ONE(3, AnimationID.HYDRA_1_1, AnimationID.HYDRA_1_2, ProjectileID.HYDRA_POISON, 0, SpriteID.BIG_ASS_GUTHIX_SPELL),
TWO(3, AnimationID.HYDRA_2_1, AnimationID.HYDRA_2_2, 0, AnimationID.HYDRA_LIGHTNING, SpriteID.BIG_SPEC_TRANSFER),
THREE(3, AnimationID.HYDRA_3_1, AnimationID.HYDRA_3_2, 0, AnimationID.HYDRA_FIRE, SpriteID.BIG_SUPERHEAT),
FOUR(1, AnimationID.HYDRA_4_1, AnimationID.HYDRA_4_2, ProjectileID.HYDRA_POISON, 0, SpriteID.BIG_ASS_GUTHIX_SPELL);
@Getter
private int attacksPerSwitch;
@Getter
private int deathAnim1;
@Getter
private int deathAnim2;
@Getter
private int specProjectileId;
@Getter
private int specAnimationId;
@Getter
private int specImage;
HydraPhase(int attacksPerSwitch, int deathAnim1, int deathAnim2, int specProjectileId, int specAnimationId, int specImage)
{
this.attacksPerSwitch = attacksPerSwitch;
this.deathAnim1 = deathAnim1;
this.deathAnim2 = deathAnim2;
this.specProjectileId = specProjectileId;
this.specAnimationId = specAnimationId;
this.specImage = specImage;
}
}

View File

@@ -0,0 +1,248 @@
package net.runelite.client.plugins.alchemicalhydra;
import java.util.Arrays;
import java.util.HashSet;
import javax.inject.Inject;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Actor;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.NpcID;
import net.runelite.api.Projectile;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.events.AnimationChanged;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.NpcSpawned;
import net.runelite.api.events.ProjectileMoved;
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;
@PluginDescriptor(
name = "Alchemical Hydra",
description = "Show what to pray against hydra",
tags = {"Hydra", "Lazy", "4 headed asshole"},
type = PluginType.PVM
)
@Slf4j
public class HydraPlugin extends Plugin
{
@Getter
private HashSet<LocalPoint> poisonPoints = new HashSet<>();
@Getter
private Hydra hydra;
private boolean inHydraInstance;
private int lastAttackTick;
private int lastPoisonTick;
private static final int[] HYDRA_REGIONS = {
5279, 5280,
5535, 5536
};
@Inject
private Client client;
@Inject
private OverlayManager overlayManager;
@Inject
private HydraOverlay overlay;
@Inject
private HydraPoisonOverlay poisonOverlay;
@Override
protected void startUp()
{
inHydraInstance = checkArea();
lastAttackTick = -1;
poisonPoints.clear();
}
@Override
protected void shutDown()
{
inHydraInstance = false;
hydra = null;
poisonPoints.clear();
removeOverlays();
lastAttackTick = -1;
}
@Subscribe
private void onGameStateChanged(GameStateChanged state)
{
if (state.getGameState() != GameState.LOGGED_IN)
{
return;
}
inHydraInstance = checkArea();
if (inHydraInstance)
{
hydra = new Hydra();
log.debug("Entered hydra instance");
addOverlays();
}
else if (hydra != null)
{
removeOverlays();
hydra = null;
log.debug("Left hydra instance");
}
}
@Subscribe
private void onNpcSpawned(NpcSpawned event)
{
if (!inHydraInstance || event.getNpc().getId() != NpcID.ALCHEMICAL_HYDRA)
{
return;
}
hydra = new Hydra();
log.debug("Hydra spawned");
addOverlays();
}
@Subscribe
public void onAnimationChanged(AnimationChanged animationChanged)
{
Actor actor = animationChanged.getActor();
if (!inHydraInstance || hydra == null || actor == client.getLocalPlayer())
{
return;
}
HydraPhase phase = hydra.getPhase();
// Using the first animation sometimes fucks shit up, so just use 2
if ( /* actor.getAnimation() == phase.getDeathAnim1() || */ actor.getAnimation() == phase.getDeathAnim2())
{
switch (phase)
{
case ONE:
changePhase(HydraPhase.TWO);
log.debug("Hydra phase 2");
return;
case TWO:
changePhase(HydraPhase.THREE);
log.debug("Hydra phase 3");
return;
case THREE:
changePhase(HydraPhase.FOUR);
log.debug("Hydra phase 4");
return;
case FOUR:
hydra = null;
poisonPoints.clear();
log.debug("Hydra dead");
removeOverlays();
return;
default:
log.debug("Tried some weird shit");
break;
}
if (actor.getAnimation() == phase.getDeathAnim1() && phase == HydraPhase.THREE)
{
changePhase(HydraPhase.FOUR);
}
}
else if (actor.getAnimation() == phase.getSpecAnimationId() && phase.getSpecAnimationId() != 0)
{
hydra.setNextSpecial(hydra.getNextSpecial() + 9);
}
if (!poisonPoints.isEmpty() && lastPoisonTick + 10 < client.getTickCount())
{
poisonPoints.clear();
}
}
@Subscribe
public void onProjectileMoved(ProjectileMoved event)
{
if (!inHydraInstance || hydra == null
|| client.getGameCycle() >= event.getProjectile().getStartMovementCycle())
{
return;
}
Projectile projectile = event.getProjectile();
int id = projectile.getId();
if (hydra.getPhase().getSpecProjectileId() != 0 && hydra.getPhase().getSpecProjectileId() == id)
{
poisonPoints.add(event.getPosition());
hydra.setNextSpecial(hydra.getNextSpecial() + 9);
lastPoisonTick = client.getTickCount();
}
else if (client.getTickCount() != lastAttackTick
&& (id == Hydra.AttackStyle.MAGIC.getProjId() || id == Hydra.AttackStyle.RANGED.getProjId()))
{
handleAttack(id);
lastAttackTick = client.getTickCount();
}
}
private boolean checkArea()
{
return Arrays.equals(client.getMapRegions(), HYDRA_REGIONS) && client.isInInstancedRegion();
}
private void addOverlays()
{
overlayManager.add(overlay);
overlayManager.add(poisonOverlay);
}
private void removeOverlays()
{
overlayManager.remove(overlay);
overlayManager.remove(poisonOverlay);
}
private void changePhase(HydraPhase newPhase)
{
hydra.setPhase(newPhase);
hydra.setNextSpecial(3);
hydra.setAttackCount(0);
if (newPhase == HydraPhase.FOUR)
{
switchStyles();
hydra.setNextSwitch(newPhase.getAttacksPerSwitch());
}
}
private void switchStyles()
{
hydra.setNextAttack(hydra.getLastAttack() == Hydra.AttackStyle.MAGIC
? Hydra.AttackStyle.RANGED
: Hydra.AttackStyle.MAGIC);
}
private void handleAttack(int id)
{
hydra.setNextSwitch(hydra.getNextSwitch() - 1);
hydra.setAttackCount(hydra.getAttackCount() + 1);
hydra.setLastAttack(hydra.getNextAttack());
if (id != hydra.getNextAttack().getProjId())
{
switchStyles();
}
else if (hydra.getNextSwitch() <= 0)
{
switchStyles();
hydra.setNextSwitch(hydra.getPhase().getAttacksPerSwitch());
}
}
}

View File

@@ -0,0 +1,61 @@
package net.runelite.client.plugins.alchemicalhydra;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.geom.Area;
import java.util.HashSet;
import javax.inject.Inject;
import javax.inject.Singleton;
import net.runelite.api.Client;
import static net.runelite.api.Perspective.getCanvasTileAreaPoly;
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;
@Singleton
class HydraPoisonOverlay extends Overlay
{
private final HydraPlugin plugin;
private final Client client;
@Inject
public HydraPoisonOverlay(Client client, HydraPlugin plugin)
{
setPosition(OverlayPosition.DYNAMIC);
setLayer(OverlayLayer.UNDER_WIDGETS);
this.plugin = plugin;
this.client = client;
}
@Override
public Dimension render(Graphics2D graphics)
{
final HashSet<LocalPoint> initialPoints = plugin.getPoisonPoints();
Area poisonTiles = new Area();
for (LocalPoint point : initialPoints)
{
Polygon poly = getCanvasTileAreaPoly(client, point, 3);
if (poly == null)
{
break;
}
poisonTiles.add(new Area(poly));
}
if (poisonTiles.isEmpty())
{
return null;
}
graphics.setPaintMode();
graphics.setColor(new Color(255, 0, 0, 75));
graphics.draw(poisonTiles);
graphics.setColor(new Color(255, 0, 0, 30));
graphics.fill(poisonTiles);
return null;
}
}