Add poison tracking for other players/npcs
This commit is contained in:
@@ -55,6 +55,7 @@ public enum VarPlayer
|
|||||||
NMZ_REWARD_POINTS(1060),
|
NMZ_REWARD_POINTS(1060),
|
||||||
|
|
||||||
ATTACKING_PLAYER(1075),
|
ATTACKING_PLAYER(1075),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* -1 : Poison immune
|
* -1 : Poison immune
|
||||||
* Normal poison damage is ceil( this / 5.0f )
|
* Normal poison damage is ceil( this / 5.0f )
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package net.runelite.client.plugins.poison;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
class ActorPoisonInfo
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Plain and simple, the last poison damage this actor received
|
||||||
|
*/
|
||||||
|
private int lastDamage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What the poison varp for this actor would be. -1 if unknown
|
||||||
|
* This should always be known if the actor is venomed
|
||||||
|
*/
|
||||||
|
private int accurateDamage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many ticks after the tickcount is divisible by 30 the poison will hit.
|
||||||
|
*
|
||||||
|
* For instance, if something gets hit by poison on tick 607 this will be 7, as 607 % 30 == 7.
|
||||||
|
* With this info you know all the times to expect a poison hit
|
||||||
|
*/
|
||||||
|
private int cycle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is to be able to remove the overlay if someone doesn't get hit
|
||||||
|
*/
|
||||||
|
private int lastDamageTick;
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package net.runelite.client.plugins.poison;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import lombok.Setter;
|
||||||
|
import net.runelite.api.Actor;
|
||||||
|
import net.runelite.api.Client;
|
||||||
|
import net.runelite.api.Constants;
|
||||||
|
import net.runelite.api.Perspective;
|
||||||
|
import net.runelite.api.Point;
|
||||||
|
import net.runelite.api.SpriteID;
|
||||||
|
import net.runelite.api.coords.LocalPoint;
|
||||||
|
import static net.runelite.client.plugins.poison.PoisonPlugin.POISON_TICK_TICKS;
|
||||||
|
import static net.runelite.client.plugins.poison.PoisonPlugin.VENOM_THRESHOLD;
|
||||||
|
import static net.runelite.client.plugins.poison.PoisonPlugin.nextDamage;
|
||||||
|
import net.runelite.client.ui.overlay.Overlay;
|
||||||
|
import net.runelite.client.ui.overlay.OverlayLayer;
|
||||||
|
import net.runelite.client.ui.overlay.OverlayPosition;
|
||||||
|
import net.runelite.client.ui.overlay.OverlayUtil;
|
||||||
|
|
||||||
|
public class PoisonActorOverlay extends Overlay
|
||||||
|
{
|
||||||
|
private final PoisonPlugin plugin;
|
||||||
|
private final Client client;
|
||||||
|
private int fontSize;
|
||||||
|
private Font font;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
PoisonActorOverlay(PoisonPlugin plugin, Client client)
|
||||||
|
{
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.client = client;
|
||||||
|
setPosition(OverlayPosition.DYNAMIC);
|
||||||
|
setLayer(OverlayLayer.ABOVE_SCENE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dimension render(Graphics2D g)
|
||||||
|
{
|
||||||
|
final Map<Actor, ActorPoisonInfo> actors = plugin.getPoisonedActors();
|
||||||
|
|
||||||
|
if (actors.isEmpty())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int tickCount = client.getTickCount();
|
||||||
|
final int modTickCount = tickCount % 30;
|
||||||
|
|
||||||
|
if (font == null)
|
||||||
|
{
|
||||||
|
font = g.getFont().deriveFont(fontSize * 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
g.setFont(font);
|
||||||
|
|
||||||
|
for (Map.Entry<Actor, ActorPoisonInfo> entry : actors.entrySet())
|
||||||
|
{
|
||||||
|
Actor a = entry.getKey();
|
||||||
|
|
||||||
|
if (a == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActorPoisonInfo i = entry.getValue();
|
||||||
|
|
||||||
|
int accurateDamage = i.getAccurateDamage();
|
||||||
|
int damage;
|
||||||
|
|
||||||
|
if (accurateDamage != -1)
|
||||||
|
{
|
||||||
|
damage = nextDamage(accurateDamage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
damage = i.getLastDamage();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean venomed = accurateDamage >= VENOM_THRESHOLD;
|
||||||
|
|
||||||
|
String timeLeft = getTimeLeft(modTickCount, i.getCycle());
|
||||||
|
|
||||||
|
renderOverlayFor(g, a, damage, timeLeft, venomed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTimeLeft(int tickCount, int cycle)
|
||||||
|
{
|
||||||
|
if (tickCount > cycle)
|
||||||
|
{
|
||||||
|
cycle += POISON_TICK_TICKS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int timeLeftMillis = (cycle - tickCount) * Constants.GAME_TICK_LENGTH;
|
||||||
|
|
||||||
|
return String.valueOf(timeLeftMillis / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderOverlayFor(Graphics2D g, Actor actor, int damage, String timeLeft, boolean venomed)
|
||||||
|
{
|
||||||
|
BufferedImage splat = plugin.getSplat(venomed ? SpriteID.HITSPLAT_DARK_GREEN_VENOM : SpriteID.HITSPLAT_GREEN_POISON, damage);
|
||||||
|
|
||||||
|
LocalPoint localLocation = actor.getLocalLocation();
|
||||||
|
if (localLocation == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point overlayLocation = Perspective.getCanvasImageLocation(client, localLocation, splat, 0);
|
||||||
|
|
||||||
|
if (overlayLocation == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int textOffset = splat.getHeight() - (splat.getHeight() - fontSize) / 2;
|
||||||
|
|
||||||
|
Point textLocation = new Point(overlayLocation.getX() + splat.getWidth() + 3, overlayLocation.getY() + textOffset);
|
||||||
|
|
||||||
|
g.drawImage(splat, overlayLocation.getX(), overlayLocation.getY(), null);
|
||||||
|
OverlayUtil.renderTextLocation(g, textLocation, timeLeft, Color.WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFontSize(int size)
|
||||||
|
{
|
||||||
|
if (font != null)
|
||||||
|
{
|
||||||
|
fontSize = size;
|
||||||
|
font = font.deriveFont(fontSize * 2.0f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fontSize = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,4 +52,37 @@ public interface PoisonConfig extends Config
|
|||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "showPlayers",
|
||||||
|
name = "Show for players",
|
||||||
|
description = "Show poison timers for other players",
|
||||||
|
position = 1
|
||||||
|
)
|
||||||
|
default boolean showForPlayers()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "showNpcs",
|
||||||
|
name = "Show for NPCs",
|
||||||
|
description = "Show poison timers for NPCs",
|
||||||
|
position = 2
|
||||||
|
)
|
||||||
|
default boolean showForNpcs()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "fontsize",
|
||||||
|
name = "Font size",
|
||||||
|
description = "The size the time left text for other players/npc's will be",
|
||||||
|
position = 3
|
||||||
|
)
|
||||||
|
default int fontSize()
|
||||||
|
{
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,13 +34,23 @@ import java.text.MessageFormat;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import net.runelite.api.Actor;
|
||||||
import net.runelite.api.Client;
|
import net.runelite.api.Client;
|
||||||
import net.runelite.api.GameState;
|
import net.runelite.api.GameState;
|
||||||
|
import net.runelite.api.Hitsplat;
|
||||||
|
import net.runelite.api.NPC;
|
||||||
|
import net.runelite.api.Player;
|
||||||
import net.runelite.api.SpriteID;
|
import net.runelite.api.SpriteID;
|
||||||
import net.runelite.api.VarPlayer;
|
import net.runelite.api.VarPlayer;
|
||||||
import net.runelite.api.events.ConfigChanged;
|
import net.runelite.api.events.ConfigChanged;
|
||||||
|
import net.runelite.api.events.GameTick;
|
||||||
|
import net.runelite.api.events.HitsplatApplied;
|
||||||
|
import net.runelite.api.events.NpcDespawned;
|
||||||
|
import net.runelite.api.events.PlayerDespawned;
|
||||||
import net.runelite.api.events.VarbitChanged;
|
import net.runelite.api.events.VarbitChanged;
|
||||||
import net.runelite.client.callback.ClientThread;
|
import net.runelite.client.callback.ClientThread;
|
||||||
import net.runelite.client.config.ConfigManager;
|
import net.runelite.client.config.ConfigManager;
|
||||||
@@ -62,8 +72,10 @@ import net.runelite.client.util.ImageUtil;
|
|||||||
public class PoisonPlugin extends Plugin
|
public class PoisonPlugin extends Plugin
|
||||||
{
|
{
|
||||||
private static final int POISON_TICK_MILLIS = 18000;
|
private static final int POISON_TICK_MILLIS = 18000;
|
||||||
private static final int VENOM_THRESHOLD = 1000000;
|
static final int VENOM_THRESHOLD = 1000000;
|
||||||
|
private static final int VENOM_UTILITY = 999997;
|
||||||
private static final int VENOM_MAXIMUM_DAMAGE = 20;
|
private static final int VENOM_MAXIMUM_DAMAGE = 20;
|
||||||
|
static final int POISON_TICK_TICKS = 30;
|
||||||
|
|
||||||
private static final BufferedImage HEART_DISEASE;
|
private static final BufferedImage HEART_DISEASE;
|
||||||
private static final BufferedImage HEART_POISON;
|
private static final BufferedImage HEART_POISON;
|
||||||
@@ -97,8 +109,12 @@ public class PoisonPlugin extends Plugin
|
|||||||
@Inject
|
@Inject
|
||||||
private PoisonConfig config;
|
private PoisonConfig config;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private PoisonActorOverlay actorOverlay;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private int lastDamage;
|
private int lastDamage;
|
||||||
|
|
||||||
private boolean envenomed;
|
private boolean envenomed;
|
||||||
private PoisonInfobox infobox;
|
private PoisonInfobox infobox;
|
||||||
private Instant nextPoisonTick;
|
private Instant nextPoisonTick;
|
||||||
@@ -107,6 +123,9 @@ public class PoisonPlugin extends Plugin
|
|||||||
private BufferedImage heart;
|
private BufferedImage heart;
|
||||||
private int nextTickCount;
|
private int nextTickCount;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private Map<Actor, ActorPoisonInfo> poisonedActors = new HashMap<>();
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
PoisonConfig getConfig(ConfigManager configManager)
|
PoisonConfig getConfig(ConfigManager configManager)
|
||||||
{
|
{
|
||||||
@@ -116,8 +135,14 @@ public class PoisonPlugin extends Plugin
|
|||||||
@Override
|
@Override
|
||||||
protected void startUp() throws Exception
|
protected void startUp() throws Exception
|
||||||
{
|
{
|
||||||
|
actorOverlay.setFontSize(config.fontSize());
|
||||||
overlayManager.add(poisonOverlay);
|
overlayManager.add(poisonOverlay);
|
||||||
|
|
||||||
|
if (config.showForNpcs() || config.showForPlayers())
|
||||||
|
{
|
||||||
|
overlayManager.add(actorOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
if (client.getGameState() == GameState.LOGGED_IN)
|
if (client.getGameState() == GameState.LOGGED_IN)
|
||||||
{
|
{
|
||||||
clientThread.invoke(this::checkHealthIcon);
|
clientThread.invoke(this::checkHealthIcon);
|
||||||
@@ -140,6 +165,7 @@ public class PoisonPlugin extends Plugin
|
|||||||
nextPoisonTick = null;
|
nextPoisonTick = null;
|
||||||
lastValue = 0;
|
lastValue = 0;
|
||||||
lastDiseaseValue = 0;
|
lastDiseaseValue = 0;
|
||||||
|
overlayManager.remove(actorOverlay);
|
||||||
|
|
||||||
clientThread.invoke(this::resetHealthIcon);
|
clientThread.invoke(this::resetHealthIcon);
|
||||||
}
|
}
|
||||||
@@ -193,6 +219,92 @@ public class PoisonPlugin extends Plugin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
private void onHitsplatApplied(HitsplatApplied event)
|
||||||
|
{
|
||||||
|
Hitsplat.HitsplatType type = event.getHitsplat().getHitsplatType();
|
||||||
|
|
||||||
|
if (type != Hitsplat.HitsplatType.POISON && type != Hitsplat.HitsplatType.VENOM)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Actor actor = event.getActor();
|
||||||
|
|
||||||
|
if (actor == client.getLocalPlayer() ||
|
||||||
|
actor instanceof NPC && !config.showForNpcs() ||
|
||||||
|
actor instanceof Player && !config.showForPlayers())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tickCount = client.getTickCount();
|
||||||
|
int damage = event.getHitsplat().getAmount();
|
||||||
|
|
||||||
|
ActorPoisonInfo info = poisonedActors.get(actor);
|
||||||
|
|
||||||
|
if (info == null)
|
||||||
|
{
|
||||||
|
info = new ActorPoisonInfo();
|
||||||
|
info.setAccurateDamage(-1);
|
||||||
|
info.setLastDamage(damage);
|
||||||
|
|
||||||
|
poisonedActors.put(actor, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.getAccurateDamage() != -1)
|
||||||
|
{
|
||||||
|
int accurateDamage = info.getAccurateDamage();
|
||||||
|
accurateDamage -= 1;
|
||||||
|
|
||||||
|
if (accurateDamage == 0)
|
||||||
|
{
|
||||||
|
poisonedActors.remove(actor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
info.setAccurateDamage(accurateDamage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == Hitsplat.HitsplatType.VENOM)
|
||||||
|
{
|
||||||
|
info.setAccurateDamage(damage / 2 + VENOM_UTILITY + 1);
|
||||||
|
}
|
||||||
|
else if (info.getLastDamage() != damage)
|
||||||
|
{
|
||||||
|
// The damage changed so we know the accurate value!
|
||||||
|
// This may of course not be 100% accurate
|
||||||
|
// (if someone gets repoisoned for instance)
|
||||||
|
info.setAccurateDamage(damage * 5 - 1);
|
||||||
|
|
||||||
|
info.setLastDamage(damage);
|
||||||
|
}
|
||||||
|
|
||||||
|
info.setCycle(tickCount % POISON_TICK_TICKS);
|
||||||
|
info.setLastDamageTick(tickCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
private void onGameTick(GameTick event)
|
||||||
|
{
|
||||||
|
int tickCount = client.getTickCount();
|
||||||
|
|
||||||
|
// Remove the actor if the last damage tick was over 35 ticks ago.
|
||||||
|
poisonedActors.values().removeIf(info -> info.getLastDamageTick() + POISON_TICK_TICKS + 5 < tickCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
private void onNpcDespawned(NpcDespawned event)
|
||||||
|
{
|
||||||
|
poisonedActors.remove(event.getNpc());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
private void onPlayerDespawned(PlayerDespawned event)
|
||||||
|
{
|
||||||
|
poisonedActors.remove(event.getPlayer());
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onConfigChanged(ConfigChanged event)
|
public void onConfigChanged(ConfigChanged event)
|
||||||
{
|
{
|
||||||
@@ -215,9 +327,38 @@ public class PoisonPlugin extends Plugin
|
|||||||
{
|
{
|
||||||
clientThread.invoke(this::resetHealthIcon);
|
clientThread.invoke(this::resetHealthIcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.getKey().startsWith("show"))
|
||||||
|
{
|
||||||
|
overlayManager.remove(actorOverlay);
|
||||||
|
|
||||||
|
if (!config.showForPlayers() && !config.showForNpcs())
|
||||||
|
{
|
||||||
|
poisonedActors.clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!config.showForNpcs())
|
||||||
|
{
|
||||||
|
poisonedActors.entrySet().removeIf(a -> a instanceof NPC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.showForPlayers())
|
||||||
|
{
|
||||||
|
poisonedActors.entrySet().removeIf(a -> a instanceof Player);
|
||||||
|
}
|
||||||
|
|
||||||
|
overlayManager.add(actorOverlay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.getKey().equals("fontsize"))
|
||||||
|
{
|
||||||
|
actorOverlay.setFontSize(config.fontSize());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int nextDamage(int poisonValue)
|
static int nextDamage(int poisonValue)
|
||||||
{
|
{
|
||||||
int damage;
|
int damage;
|
||||||
|
|
||||||
@@ -225,7 +366,7 @@ public class PoisonPlugin extends Plugin
|
|||||||
{
|
{
|
||||||
//Venom Damage starts at 6, and increments in twos;
|
//Venom Damage starts at 6, and increments in twos;
|
||||||
//The VarPlayer increments in values of 1, however.
|
//The VarPlayer increments in values of 1, however.
|
||||||
poisonValue -= VENOM_THRESHOLD - 3;
|
poisonValue -= VENOM_UTILITY;
|
||||||
damage = poisonValue * 2;
|
damage = poisonValue * 2;
|
||||||
//Venom Damage caps at 20, but the VarPlayer keeps increasing
|
//Venom Damage caps at 20, but the VarPlayer keeps increasing
|
||||||
if (damage > VENOM_MAXIMUM_DAMAGE)
|
if (damage > VENOM_MAXIMUM_DAMAGE)
|
||||||
@@ -241,7 +382,7 @@ public class PoisonPlugin extends Plugin
|
|||||||
return damage;
|
return damage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BufferedImage getSplat(int id, int damage)
|
BufferedImage getSplat(int id, int damage)
|
||||||
{
|
{
|
||||||
//Get a copy of the hitsplat to get a clean one each time
|
//Get a copy of the hitsplat to get a clean one each time
|
||||||
final BufferedImage rawSplat = spriteManager.getSprite(id, 0);
|
final BufferedImage rawSplat = spriteManager.getSprite(id, 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user