Add poison tracking for other players/npcs
This commit is contained in:
@@ -55,6 +55,7 @@ public enum VarPlayer
|
||||
NMZ_REWARD_POINTS(1060),
|
||||
|
||||
ATTACKING_PLAYER(1075),
|
||||
|
||||
/**
|
||||
* -1 : Poison immune
|
||||
* 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;
|
||||
}
|
||||
|
||||
@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.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import lombok.Getter;
|
||||
import net.runelite.api.Actor;
|
||||
import net.runelite.api.Client;
|
||||
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.VarPlayer;
|
||||
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.client.callback.ClientThread;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
@@ -62,8 +72,10 @@ import net.runelite.client.util.ImageUtil;
|
||||
public class PoisonPlugin extends Plugin
|
||||
{
|
||||
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;
|
||||
static final int POISON_TICK_TICKS = 30;
|
||||
|
||||
private static final BufferedImage HEART_DISEASE;
|
||||
private static final BufferedImage HEART_POISON;
|
||||
@@ -97,8 +109,12 @@ public class PoisonPlugin extends Plugin
|
||||
@Inject
|
||||
private PoisonConfig config;
|
||||
|
||||
@Inject
|
||||
private PoisonActorOverlay actorOverlay;
|
||||
|
||||
@Getter
|
||||
private int lastDamage;
|
||||
|
||||
private boolean envenomed;
|
||||
private PoisonInfobox infobox;
|
||||
private Instant nextPoisonTick;
|
||||
@@ -107,6 +123,9 @@ public class PoisonPlugin extends Plugin
|
||||
private BufferedImage heart;
|
||||
private int nextTickCount;
|
||||
|
||||
@Getter
|
||||
private Map<Actor, ActorPoisonInfo> poisonedActors = new HashMap<>();
|
||||
|
||||
@Provides
|
||||
PoisonConfig getConfig(ConfigManager configManager)
|
||||
{
|
||||
@@ -116,8 +135,14 @@ public class PoisonPlugin extends Plugin
|
||||
@Override
|
||||
protected void startUp() throws Exception
|
||||
{
|
||||
actorOverlay.setFontSize(config.fontSize());
|
||||
overlayManager.add(poisonOverlay);
|
||||
|
||||
if (config.showForNpcs() || config.showForPlayers())
|
||||
{
|
||||
overlayManager.add(actorOverlay);
|
||||
}
|
||||
|
||||
if (client.getGameState() == GameState.LOGGED_IN)
|
||||
{
|
||||
clientThread.invoke(this::checkHealthIcon);
|
||||
@@ -140,6 +165,7 @@ public class PoisonPlugin extends Plugin
|
||||
nextPoisonTick = null;
|
||||
lastValue = 0;
|
||||
lastDiseaseValue = 0;
|
||||
overlayManager.remove(actorOverlay);
|
||||
|
||||
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
|
||||
public void onConfigChanged(ConfigChanged event)
|
||||
{
|
||||
@@ -215,9 +327,38 @@ public class PoisonPlugin extends Plugin
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -225,7 +366,7 @@ public class PoisonPlugin extends Plugin
|
||||
{
|
||||
//Venom Damage starts at 6, and increments in twos;
|
||||
//The VarPlayer increments in values of 1, however.
|
||||
poisonValue -= VENOM_THRESHOLD - 3;
|
||||
poisonValue -= VENOM_UTILITY;
|
||||
damage = poisonValue * 2;
|
||||
//Venom Damage caps at 20, but the VarPlayer keeps increasing
|
||||
if (damage > VENOM_MAXIMUM_DAMAGE)
|
||||
@@ -241,7 +382,7 @@ public class PoisonPlugin extends Plugin
|
||||
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
|
||||
final BufferedImage rawSplat = spriteManager.getSprite(id, 0);
|
||||
|
||||
Reference in New Issue
Block a user