Repository: pajlads/DinkPlugin
Pull request #227 (2023-05-04 19:46:35)

feat: add setting to notify on safe deaths
For folks who want death notifications in safe areas (requested by a user)



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 72a0641..83a4130 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
 ## Unreleased
 
+- Minor: Add setting to allow death notifications during safe activities. (#227)
 - Minor: Allow player name in Discord notifications to link to CollectionLog.net profile. (#224)
 
 ## 1.4.1
diff --git a/src/main/java/dinkplugin/DinkPluginConfig.java b/src/main/java/dinkplugin/DinkPluginConfig.java
index b4e954c..1629ce3 100644
--- a/src/main/java/dinkplugin/DinkPluginConfig.java
+++ b/src/main/java/dinkplugin/DinkPluginConfig.java
@@ -698,11 +698,24 @@ public interface DinkPluginConfig extends Config {
         return false;
     }
 
+    @ConfigItem(
+        keyName = "deathIgnoreSafe",
+        name = "Ignore Safe Deaths",
+        description = "Whether deaths in safe areas should be ignored.<br/>" +
+            "Note: Inferno and TzHaar Fight Cave deaths are still sent with this setting enabled",
+        position = 43,
+        section = deathSection
+    )
+    default boolean deathIgnoreSafe() {
+        return true;
+    }
+
     @ConfigItem(
         keyName = "deathMinValue",
         name = "Min Lost Value",
-        description = "The minimum value of the lost items for a notification to be sent",
-        position = 43,
+        description = "The minimum value of the lost items for a notification to be sent.<br/>" +
+            "This setting does not apply for safe deaths",
+        position = 44,
         section = deathSection
     )
     default int deathMinValue() {
@@ -715,7 +728,7 @@ public interface DinkPluginConfig extends Config {
         description = "The message to be sent through the webhook.<br/>" +
             "Use %USERNAME% to insert your username<br/>" +
             "Use %VALUELOST% to insert the GE value of the stuff you lost",
-        position = 44,
+        position = 45,
         section = deathSection
     )
     default String deathNotifyMessage() {
@@ -726,7 +739,7 @@ public interface DinkPluginConfig extends Config {
         keyName = "deathNotifPvpEnabled",
         name = "Distinguish PvP deaths",
         description = "Should the plugin use a different message for dying in PvP?",
-        position = 45,
+        position = 46,
         section = deathSection
     )
     default boolean deathNotifPvpEnabled() {
@@ -740,7 +753,7 @@ public interface DinkPluginConfig extends Config {
             "Use %PKER% to insert the killer<br/>" +
             "Use %USERNAME% to insert your username<br/>" +
             "Use %VALUELOST% to insert the GE value of the stuff you lost",
-        position = 46,
+        position = 47,
         section = deathSection
     )
     default String deathNotifPvpMessage() {
diff --git a/src/main/java/dinkplugin/notifiers/DeathNotifier.java b/src/main/java/dinkplugin/notifiers/DeathNotifier.java
index 9d205eb..f2f9ca6 100644
--- a/src/main/java/dinkplugin/notifiers/DeathNotifier.java
+++ b/src/main/java/dinkplugin/notifiers/DeathNotifier.java
@@ -93,7 +93,7 @@ public class DeathNotifier extends BaseNotifier {
 
     @Override
     public boolean isEnabled() {
-        return config.notifyDeath() && super.isEnabled() && isDangerous(client);
+        return config.notifyDeath() && super.isEnabled();
     }
 
     @Override
@@ -118,11 +118,20 @@ public class DeathNotifier extends BaseNotifier {
     }
 
     private void handleNotify() {
+        boolean dangerous = isDangerous(client);
+        if (!dangerous && config.deathIgnoreSafe())
+            return;
+
         Collection<Item> items = ItemUtils.getItems(client);
         List<Pair<Item, Long>> itemsByPrice = getPricedItems(itemManager, items);
 
-        int keepCount = getKeepCount();
-        Pair<List<Pair<Item, Long>>, List<Pair<Item, Long>>> split = splitItemsByKept(itemsByPrice, keepCount);
+        Pair<List<Pair<Item, Long>>, List<Pair<Item, Long>>> split;
+        if (dangerous) {
+            int keepCount = getKeepCount();
+            split = splitItemsByKept(itemsByPrice, keepCount);
+        } else {
+            split = Pair.of(itemsByPrice, Collections.emptyList());
+        }
         List<Pair<Item, Long>> keptItems = split.getLeft();
         List<Pair<Item, Long>> lostItems = split.getRight();
 
@@ -132,7 +141,7 @@ public class DeathNotifier extends BaseNotifier {
             .orElse(0L);
 
         int valueThreshold = config.deathMinValue();
-        if (losePrice < valueThreshold) {
+        if (dangerous && losePrice < valueThreshold) {
             log.debug("Skipping death notification; total value of lost items {} is below minimum lost value {}", losePrice, valueThreshold);
             return;
         }


Test patch:
diff --git a/src/test/java/dinkplugin/notifiers/DeathNotifierTest.java b/src/test/java/dinkplugin/notifiers/DeathNotifierTest.java
index c620793..7c9cbb1 100644
--- a/src/test/java/dinkplugin/notifiers/DeathNotifierTest.java
+++ b/src/test/java/dinkplugin/notifiers/DeathNotifierTest.java
@@ -6,6 +6,7 @@ import dinkplugin.message.NotificationBody;
 import dinkplugin.message.NotificationType;
 import dinkplugin.notifiers.data.DeathNotificationData;
 import dinkplugin.notifiers.data.SerializedItemStack;
+import dinkplugin.util.ItemUtils;
 import net.runelite.api.InventoryID;
 import net.runelite.api.Item;
 import net.runelite.api.ItemContainer;
@@ -27,7 +28,9 @@ import org.mockito.InjectMocks;
 
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -59,6 +62,7 @@ class DeathNotifierTest extends MockedNotifierTest {
         when(config.deathNotifPvpEnabled()).thenReturn(true);
         when(config.deathSendImage()).thenReturn(false);
         when(config.deathEmbedKeptItems()).thenReturn(true);
+        when(config.deathIgnoreSafe()).thenReturn(true);
         when(config.deathNotifyMessage()).thenReturn("%USERNAME% has died, losing %VALUELOST% gp");
         when(config.deathNotifPvpMessage()).thenReturn("%USERNAME% has been PKed by %PKER% for %VALUELOST% gp");
 
@@ -262,6 +266,87 @@ class DeathNotifierTest extends MockedNotifierTest {
         );
     }
 
+    @Test
+    void testNotifyValue() {
+        // prepare mocks
+        when(config.deathMinValue()).thenReturn(TUNA_PRICE - 1);
+        when(config.deathEmbedKeptItems()).thenReturn(false);
+        when(client.isPrayerActive(Prayer.PROTECT_ITEM)).thenReturn(true);
+        Item[] items = {
+            new Item(ItemID.RUBY, 1),
+            new Item(ItemID.TUNA, 1),
+            new Item(ItemID.COAL, 1),
+            new Item(ItemID.SHARK, 1),
+            new Item(ItemID.OPAL, 1),
+        };
+        ItemContainer inv = mock(ItemContainer.class);
+        when(client.getItemContainer(InventoryID.INVENTORY)).thenReturn(inv);
+        when(inv.getItems()).thenReturn(items);
+
+        // fire event
+        plugin.onActorDeath(new ActorDeath(localPlayer));
+
+        // verify notification
+        List<SerializedItemStack> kept = Arrays.asList(
+            ItemUtils.stackFromItem(itemManager, items[0]),
+            ItemUtils.stackFromItem(itemManager, items[3]),
+            ItemUtils.stackFromItem(itemManager, items[4]),
+            ItemUtils.stackFromItem(itemManager, items[2])
+        );
+        List<SerializedItemStack> lost = Collections.singletonList(
+            ItemUtils.stackFromItem(itemManager, items[1])
+        );
+        verify(messageHandler).createMessage(
+            PRIMARY_WEBHOOK_URL,
+            false,
+            NotificationBody.builder()
+                .text(String.format("%s has died, losing %d gp", PLAYER_NAME, TUNA_PRICE))
+                .extra(new DeathNotificationData(TUNA_PRICE, false, null, null, null, kept, lost))
+                .type(NotificationType.DEATH)
+                .build()
+        );
+    }
+
+    @Test
+    void testNotifySafe() {
+        // update config mock
+        when(config.deathIgnoreSafe()).thenReturn(false);
+        when(config.deathEmbedKeptItems()).thenReturn(false);
+        when(config.deathMinValue()).thenReturn(TUNA_PRICE + 1);
+        when(client.isPrayerActive(Prayer.PROTECT_ITEM)).thenReturn(true);
+        Item[] items = {
+            new Item(ItemID.RUBY, 1),
+            new Item(ItemID.TUNA, 1),
+            new Item(ItemID.COAL, 1),
+            new Item(ItemID.SHARK, 1),
+            new Item(ItemID.OPAL, 1),
+        };
+        ItemContainer inv = mock(ItemContainer.class);
+        when(client.getItemContainer(InventoryID.INVENTORY)).thenReturn(inv);
+        when(inv.getItems()).thenReturn(items);
+
+        // mock castle wars
+        when(localPlayer.getWorldLocation()).thenReturn(new WorldPoint(2400, 3100, 0));
+
+        // fire event
+        plugin.onActorDeath(new ActorDeath(localPlayer));
+
+        // verify notification
+        List<SerializedItemStack> kept = Arrays.stream(items)
+            .map(i -> ItemUtils.stackFromItem(itemManager, i))
+            .sorted(Comparator.comparingLong(SerializedItemStack::getTotalPrice).reversed())
+            .collect(Collectors.toList());
+        verify(messageHandler).createMessage(
+            PRIMARY_WEBHOOK_URL,
+            false,
+            NotificationBody.builder()
+                .text(String.format("%s has died, losing %d gp", PLAYER_NAME, 0))
+                .extra(new DeathNotificationData(0L, false, null, null, null, kept, Collections.emptyList()))
+                .type(NotificationType.DEATH)
+                .build()
+        );
+    }
+
     @Test
     void testIgnore() {
         // prepare mock