Merge pull request #1818 from open-osrs/spawn
* Add notification on NPC spawn Co-authored-by: William Maga <magaw@mymacewan.ca> Co-authored-by: Owain van Brakel <owain.vanbrakel@gmail.com>
This commit is contained in:
@@ -129,4 +129,24 @@ public interface NpcIndicatorsConfig extends Config
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
position = 7,
|
||||||
|
keyName = "notifyOnRespawn",
|
||||||
|
name = "Notify on Respawn",
|
||||||
|
description = "Enable notification on respawn")
|
||||||
|
default boolean getNotifyOnRespawn()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
position = 8,
|
||||||
|
keyName = "notifyOnRespawnDelay",
|
||||||
|
name = "Notification Delay",
|
||||||
|
description = "Notify when NPC is x ms from respawning")
|
||||||
|
default int getNotifyOnRespawnDelay()
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,9 @@ import com.google.common.annotations.VisibleForTesting;
|
|||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.inject.Provides;
|
import com.google.inject.Provides;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.text.NumberFormat;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -38,6 +41,7 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.Locale;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
@@ -61,6 +65,7 @@ import net.runelite.api.events.MenuOptionClicked;
|
|||||||
import net.runelite.api.events.NpcDefinitionChanged;
|
import net.runelite.api.events.NpcDefinitionChanged;
|
||||||
import net.runelite.api.events.NpcDespawned;
|
import net.runelite.api.events.NpcDespawned;
|
||||||
import net.runelite.api.events.NpcSpawned;
|
import net.runelite.api.events.NpcSpawned;
|
||||||
|
import net.runelite.client.Notifier;
|
||||||
import net.runelite.api.util.Text;
|
import net.runelite.api.util.Text;
|
||||||
import net.runelite.client.callback.ClientThread;
|
import net.runelite.client.callback.ClientThread;
|
||||||
import net.runelite.client.config.ConfigManager;
|
import net.runelite.client.config.ConfigManager;
|
||||||
@@ -86,6 +91,16 @@ public class NpcIndicatorsPlugin extends Plugin
|
|||||||
{
|
{
|
||||||
private static final int MAX_ACTOR_VIEW_RANGE = 15;
|
private static final int MAX_ACTOR_VIEW_RANGE = 15;
|
||||||
|
|
||||||
|
// Estimated time of a game tick in seconds
|
||||||
|
private static final double ESTIMATED_TICK_LENGTH = 0.6;
|
||||||
|
|
||||||
|
private static final NumberFormat TIME_LEFT_FORMATTER = DecimalFormat.getInstance(Locale.getDefault());
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
((DecimalFormat)TIME_LEFT_FORMATTER).applyPattern("#0.0");
|
||||||
|
}
|
||||||
|
|
||||||
// Option added to NPC menu
|
// Option added to NPC menu
|
||||||
private static final String TAG = "Tag";
|
private static final String TAG = "Tag";
|
||||||
private static final String UNTAG = "Un-tag";
|
private static final String UNTAG = "Un-tag";
|
||||||
@@ -125,12 +140,21 @@ public class NpcIndicatorsPlugin extends Plugin
|
|||||||
@Setter(AccessLevel.PACKAGE)
|
@Setter(AccessLevel.PACKAGE)
|
||||||
private boolean hotKeyPressed = false;
|
private boolean hotKeyPressed = false;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Notifier notifier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NPCs to highlight
|
* NPCs to highlight
|
||||||
*/
|
*/
|
||||||
@Getter(AccessLevel.PACKAGE)
|
@Getter(AccessLevel.PACKAGE)
|
||||||
private final Set<NPC> highlightedNpcs = new HashSet<>();
|
private final Set<NPC> highlightedNpcs = new HashSet<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NPCs to notify when close to spawning
|
||||||
|
*/
|
||||||
|
@Getter(AccessLevel.PACKAGE)
|
||||||
|
private final Set<MemorizedNpc> pendingNotificationNpcs = new HashSet<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dead NPCs that should be displayed with a respawn indicator if the config is on.
|
* Dead NPCs that should be displayed with a respawn indicator if the config is on.
|
||||||
*/
|
*/
|
||||||
@@ -206,6 +230,10 @@ public class NpcIndicatorsPlugin extends Plugin
|
|||||||
private boolean highlightMenuNames;
|
private boolean highlightMenuNames;
|
||||||
@Getter(AccessLevel.PACKAGE)
|
@Getter(AccessLevel.PACKAGE)
|
||||||
private boolean showRespawnTimer;
|
private boolean showRespawnTimer;
|
||||||
|
@Getter(AccessLevel.PACKAGE)
|
||||||
|
private boolean getNotifyOnRespawn;
|
||||||
|
@Getter(AccessLevel.PACKAGE)
|
||||||
|
private int getNotifyOnRespawnDelay;
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
NpcIndicatorsConfig provideConfig(ConfigManager configManager)
|
NpcIndicatorsConfig provideConfig(ConfigManager configManager)
|
||||||
@@ -235,6 +263,7 @@ public class NpcIndicatorsPlugin extends Plugin
|
|||||||
overlayManager.remove(npcSceneOverlay);
|
overlayManager.remove(npcSceneOverlay);
|
||||||
overlayManager.remove(npcMinimapOverlay);
|
overlayManager.remove(npcMinimapOverlay);
|
||||||
deadNpcsToDisplay.clear();
|
deadNpcsToDisplay.clear();
|
||||||
|
pendingNotificationNpcs.clear();
|
||||||
memorizedNpcs.clear();
|
memorizedNpcs.clear();
|
||||||
spawnedNpcsThisTick.clear();
|
spawnedNpcsThisTick.clear();
|
||||||
despawnedNpcsThisTick.clear();
|
despawnedNpcsThisTick.clear();
|
||||||
@@ -252,6 +281,7 @@ public class NpcIndicatorsPlugin extends Plugin
|
|||||||
{
|
{
|
||||||
highlightedNpcs.clear();
|
highlightedNpcs.clear();
|
||||||
deadNpcsToDisplay.clear();
|
deadNpcsToDisplay.clear();
|
||||||
|
pendingNotificationNpcs.clear();
|
||||||
memorizedNpcs.forEach((id, npc) -> npc.setDiedOnTick(-1));
|
memorizedNpcs.forEach((id, npc) -> npc.setDiedOnTick(-1));
|
||||||
lastPlayerLocation = null;
|
lastPlayerLocation = null;
|
||||||
skipNextSpawnCheck = true;
|
skipNextSpawnCheck = true;
|
||||||
@@ -388,6 +418,12 @@ public class NpcIndicatorsPlugin extends Plugin
|
|||||||
if (memorizedNpcs.containsKey(npc.getIndex()))
|
if (memorizedNpcs.containsKey(npc.getIndex()))
|
||||||
{
|
{
|
||||||
despawnedNpcsThisTick.add(npc);
|
despawnedNpcsThisTick.add(npc);
|
||||||
|
MemorizedNpc mn = memorizedNpcs.get(npc.getIndex());
|
||||||
|
|
||||||
|
if (!mn.getPossibleRespawnLocations().isEmpty())
|
||||||
|
{
|
||||||
|
pendingNotificationNpcs.add(mn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
highlightedNpcs.remove(npc);
|
highlightedNpcs.remove(npc);
|
||||||
@@ -409,6 +445,7 @@ public class NpcIndicatorsPlugin extends Plugin
|
|||||||
{
|
{
|
||||||
removeOldHighlightedRespawns();
|
removeOldHighlightedRespawns();
|
||||||
validateSpawnedNpcs();
|
validateSpawnedNpcs();
|
||||||
|
checkNotifyNpcs();
|
||||||
lastTickUpdate = Instant.now();
|
lastTickUpdate = Instant.now();
|
||||||
lastPlayerLocation = client.getLocalPlayer().getWorldLocation();
|
lastPlayerLocation = client.getLocalPlayer().getWorldLocation();
|
||||||
}
|
}
|
||||||
@@ -486,6 +523,16 @@ public class NpcIndicatorsPlugin extends Plugin
|
|||||||
highlightedNpcs.remove(npc);
|
highlightedNpcs.remove(npc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double getTimeLeftForNpc(MemorizedNpc npc)
|
||||||
|
{
|
||||||
|
final Instant now = Instant.now();
|
||||||
|
final double baseTick = NpcIndicatorsPlugin.ESTIMATED_TICK_LENGTH * (
|
||||||
|
npc.getDiedOnTick() + npc.getRespawnTime() - client.getTickCount()
|
||||||
|
);
|
||||||
|
final double sinceLast = (now.toEpochMilli() - lastTickUpdate.toEpochMilli()) / 1000.0;
|
||||||
|
return Math.max(0.0, baseTick - sinceLast);
|
||||||
|
}
|
||||||
|
|
||||||
private void memorizeNpc(NPC npc)
|
private void memorizeNpc(NPC npc)
|
||||||
{
|
{
|
||||||
final int npcIndex = npc.getIndex();
|
final int npcIndex = npc.getIndex();
|
||||||
@@ -561,6 +608,33 @@ public class NpcIndicatorsPlugin extends Plugin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String formatTime(double time)
|
||||||
|
{
|
||||||
|
return TIME_LEFT_FORMATTER.format(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkNotifyNpcs()
|
||||||
|
{
|
||||||
|
if (!this.getNotifyOnRespawn)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final double notifyDelay = ((double)this.getNotifyOnRespawnDelay) / 1000;
|
||||||
|
final String notifyDelayStr = notifyDelay > 0
|
||||||
|
? " is less than " + formatTime(notifyDelay) + " seconds from respawn"
|
||||||
|
: " respawned.";
|
||||||
|
|
||||||
|
for (MemorizedNpc npc : pendingNotificationNpcs)
|
||||||
|
{
|
||||||
|
if (getTimeLeftForNpc(npc) <= notifyDelay)
|
||||||
|
{
|
||||||
|
pendingNotificationNpcs.remove(npc);
|
||||||
|
notifier.notify(npc.getNpcNames() + notifyDelayStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void validateSpawnedNpcs()
|
private void validateSpawnedNpcs()
|
||||||
{
|
{
|
||||||
if (skipNextSpawnCheck)
|
if (skipNextSpawnCheck)
|
||||||
@@ -569,6 +643,7 @@ public class NpcIndicatorsPlugin extends Plugin
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
for (NPC npc : despawnedNpcsThisTick)
|
for (NPC npc : despawnedNpcsThisTick)
|
||||||
{
|
{
|
||||||
if (!teleportGraphicsObjectSpawnedThisTick.isEmpty() && teleportGraphicsObjectSpawnedThisTick.contains(npc.getWorldLocation()))
|
if (!teleportGraphicsObjectSpawnedThisTick.isEmpty() && teleportGraphicsObjectSpawnedThisTick.contains(npc.getWorldLocation()))
|
||||||
@@ -657,5 +732,7 @@ public class NpcIndicatorsPlugin extends Plugin
|
|||||||
this.drawMinimapNames = config.drawMinimapNames();
|
this.drawMinimapNames = config.drawMinimapNames();
|
||||||
this.highlightMenuNames = config.highlightMenuNames();
|
this.highlightMenuNames = config.highlightMenuNames();
|
||||||
this.showRespawnTimer = config.showRespawnTimer();
|
this.showRespawnTimer = config.showRespawnTimer();
|
||||||
|
this.getNotifyOnRespawn = config.getNotifyOnRespawn();
|
||||||
|
this.getNotifyOnRespawnDelay = config.getNotifyOnRespawnDelay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,15 +31,10 @@ import java.awt.Dimension;
|
|||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.Polygon;
|
import java.awt.Polygon;
|
||||||
import java.awt.Shape;
|
import java.awt.Shape;
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.text.NumberFormat;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import net.runelite.api.Client;
|
import net.runelite.api.Client;
|
||||||
import net.runelite.api.Constants;
|
|
||||||
import net.runelite.api.NPC;
|
import net.runelite.api.NPC;
|
||||||
import net.runelite.api.NPCDefinition;
|
import net.runelite.api.NPCDefinition;
|
||||||
import net.runelite.api.Perspective;
|
import net.runelite.api.Perspective;
|
||||||
@@ -63,12 +58,7 @@ public class NpcSceneOverlay extends Overlay
|
|||||||
// a dark background
|
// a dark background
|
||||||
private static final Color TEXT_COLOR = Color.WHITE;
|
private static final Color TEXT_COLOR = Color.WHITE;
|
||||||
|
|
||||||
private static final NumberFormat TIME_LEFT_FORMATTER = DecimalFormat.getInstance(Locale.US);
|
|
||||||
|
|
||||||
static
|
|
||||||
{
|
|
||||||
((DecimalFormat) TIME_LEFT_FORMATTER).applyPattern("#0.0");
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Client client;
|
private final Client client;
|
||||||
private final NpcIndicatorsPlugin plugin;
|
private final NpcIndicatorsPlugin plugin;
|
||||||
@@ -128,11 +118,8 @@ public class NpcSceneOverlay extends Overlay
|
|||||||
OverlayUtil.renderPolygon(graphics, poly, color);
|
OverlayUtil.renderPolygon(graphics, poly, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Instant now = Instant.now();
|
|
||||||
final double baseTick = ((npc.getDiedOnTick() + npc.getRespawnTime()) - client.getTickCount()) * (Constants.GAME_TICK_LENGTH / 1000.0);
|
final String timeLeftStr = plugin.formatTime(plugin.getTimeLeftForNpc(npc));
|
||||||
final double sinceLast = (now.toEpochMilli() - plugin.getLastTickUpdate().toEpochMilli()) / 1000.0;
|
|
||||||
final double timeLeft = Math.max(0.0, baseTick - sinceLast);
|
|
||||||
final String timeLeftStr = TIME_LEFT_FORMATTER.format(timeLeft);
|
|
||||||
|
|
||||||
final int textWidth = graphics.getFontMetrics().stringWidth(timeLeftStr);
|
final int textWidth = graphics.getFontMetrics().stringWidth(timeLeftStr);
|
||||||
final int textHeight = graphics.getFontMetrics().getAscent();
|
final int textHeight = graphics.getFontMetrics().getAscent();
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ import javax.inject.Inject;
|
|||||||
import net.runelite.api.Client;
|
import net.runelite.api.Client;
|
||||||
import net.runelite.client.config.OpenOSRSConfig;
|
import net.runelite.client.config.OpenOSRSConfig;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import net.runelite.client.Notifier;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -59,6 +61,10 @@ public class NpcIndicatorsPluginTest
|
|||||||
@Bind
|
@Bind
|
||||||
private OpenOSRSConfig openOSRSConfig;
|
private OpenOSRSConfig openOSRSConfig;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
@Bind
|
||||||
|
private Notifier notifier;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private NpcIndicatorsPlugin npcIndicatorsPlugin;
|
private NpcIndicatorsPlugin npcIndicatorsPlugin;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user