Add poison tracking for other players/npcs

This commit is contained in:
Lucas
2019-06-14 02:15:32 +02:00
parent 2071815822
commit bdc070ef0c
5 changed files with 354 additions and 4 deletions

View File

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

View File

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

View File

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

View File

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

View File

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