Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add raid party members to loot metadata #478

Merged
merged 9 commits into from
Aug 5, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Unreleased

- Dev: Add raid party members to loot notification metadata. (#478)

## 1.10.1

- Minor: Fire notifications for XP milestones beyond level 99. (#462)
Expand Down
11 changes: 8 additions & 3 deletions docs/json-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,19 +227,24 @@ JSON for Loot Notifications:
"name": "Some item"
}
],
"source": "Giant rat",
"category": "NPC",
"source": "Tombs of Amascut",
"party": ["%USERNAME%", "another RSN", "yet another RSN"],
"category": "EVENT",
"killCount": 60,
"rarestProbability": 0.001
},
"type": "LOOT"
}
```

`killCount` is only specified for NPC loot with the base RuneLite Loot Tracker plugin enabled.
The possible values for `extra.category` correspond to the [`LootRecordType`](https://github.com/runelite/api.runelite.net/blob/master/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecordType.java) enum.

`killCount` is only specified for NPC/EVENT loot with the base RuneLite Loot Tracker plugin enabled.

The items are valued at GE prices (when possible) if the user has not disabled the `Use actively traded price` base RuneLite setting. Otherwise, the store price of the item is used.

The `extra.party` field is only populated for raids loot (i.e., COX, TOA, TOB).

### Slayer

JSON for Slayer Notifications:
Expand Down
18 changes: 17 additions & 1 deletion src/main/java/dinkplugin/notifiers/LootNotifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import net.runelite.client.util.QuantityFormatter;
import net.runelite.http.api.loottracker.LootRecordType;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.inject.Inject;
Expand Down Expand Up @@ -215,6 +216,7 @@ private void handleNotify(Collection<ItemStack> items, String dropper, LootRecor
SerializedItemStack keyItem = rarest != null ? rarest.getKey() : max;
Double rarity = rarest != null ? rarest.getValue() : null;
boolean screenshot = config.lootSendImage() && totalStackValue >= config.lootImageMinValue();
Collection<String> party = type == LootRecordType.EVENT ? getParty(dropper) : null;
Evaluable source = type == LootRecordType.PLAYER
? Replacements.ofLink(dropper, config.playerLookupService().getPlayerUrl(dropper))
: Replacements.ofWiki(dropper);
Expand All @@ -230,7 +232,7 @@ private void handleNotify(Collection<ItemStack> items, String dropper, LootRecor
NotificationBody.builder()
.text(notifyMessage)
.embeds(embeds)
.extra(new LootNotificationData(serializedItems, dropper, type, kc, rarity))
.extra(new LootNotificationData(serializedItems, dropper, type, kc, rarity, party))
.type(NotificationType.LOOT)
.thumbnailUrl(ItemUtils.getItemImageUrl(keyItem.getId()))
.build()
Expand Down Expand Up @@ -266,6 +268,20 @@ private boolean sufficientlyRare(@Nullable Map.Entry<SerializedItemStack, Double
return DoubleMath.fuzzyCompare(rarest.getValue(), rarityThreshold, MathUtils.EPSILON) <= 0;
}

@Nullable
private Collection<String> getParty(@NotNull String source) {
switch (source) {
case "Chambers of Xeric":
return Utils.getXericChambersParty(client);
case "Tombs of Amascut":
return Utils.getAmascutTombsParty(client);
case "Theatre of Blood":
return Utils.getBloodTheatreParty(client);
default:
return null;
}
}

private static boolean matches(Collection<Pattern> regexps, String input) {
for (Pattern regex : regexps) {
if (regex.matcher(input).find())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Value
Expand All @@ -25,9 +26,12 @@ public class LootNotificationData extends NotificationData {
@Nullable
Double rarestProbability;

@Nullable
Collection<String> party;

@Override
public List<Field> getFields() {
List<Field> fields = new ArrayList<>(3);
List<Field> fields = new ArrayList<>(4);
if (killCount != null) {
fields.add(
new Field(
Expand All @@ -45,6 +49,9 @@ public List<Field> getFields() {
if (rarestProbability != null) {
fields.add(new Field("Item Rarity", Field.formatProbability(rarestProbability)));
}
if (party != null && !party.isEmpty()) {
fields.add(new Field("Party Size", Field.formatBlock("", String.valueOf(party.size()))));
}
return fields;
}
}
46 changes: 46 additions & 0 deletions src/main/java/dinkplugin/util/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import net.runelite.api.Client;
import net.runelite.api.Varbits;
import net.runelite.api.annotations.Component;
import net.runelite.api.annotations.VarCStr;
import net.runelite.api.widgets.InterfaceID;
import net.runelite.api.widgets.Widget;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.util.ColorUtil;
Expand Down Expand Up @@ -41,7 +43,11 @@
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.regex.Pattern;
Expand All @@ -58,6 +64,10 @@ public class Utils {

private final char ELLIPSIS = '\u2026'; // '…'

@SuppressWarnings("MagicConstant")
private final @VarCStr int TOA_MEMBER_NAME = 1099, TOB_MEMBER_NAME = 330;
private final int TOA_PARTY_MAX_SIZE = 8, TOB_PARTY_MAX_SIZE = 5;

/**
* Custom padding for applying SHA-256 to a long.
* SHA-256 adds '0' padding bits such that L+1+K+64 mod 512 == 0.
Expand Down Expand Up @@ -193,6 +203,42 @@ public String getChatBadge(@NotNull AccountType type, boolean seasonal) {
}
}

public Collection<String> getXericChambersParty(@NotNull Client client) {
Widget widget = client.getWidget(InterfaceID.RAIDING_PARTY, 10);
if (widget == null) return Collections.emptyList();

Widget[] children = widget.getChildren();
if (children == null) return Collections.emptyList();

List<String> names = new ArrayList<>(children.length);
for (Widget child : children) {
String text = child.getText();
if (text.startsWith("<col=ffffff>")) {
names.add(sanitize(text));
}
}
return names;
}

public Collection<String> getAmascutTombsParty(@NotNull Client client) {
return getVarcStrings(client, TOA_MEMBER_NAME, TOA_PARTY_MAX_SIZE);
}

public Collection<String> getBloodTheatreParty(@NotNull Client client) {
return getVarcStrings(client, TOB_MEMBER_NAME, TOB_PARTY_MAX_SIZE);
}

private List<String> getVarcStrings(@NotNull Client client, @VarCStr final int initialVarcId, final int maxSize) {
List<String> strings = new ArrayList<>(maxSize);
for (int i = 0; i < maxSize; i++) {
// noinspection MagicConstant
String name = client.getVarcStrValue(initialVarcId + i);
if (name == null || name.isEmpty()) continue;
strings.add(name.replace('\u00A0', ' '));
}
return strings;
}

public static boolean hideWidget(boolean shouldHide, Client client, @Component int info) {
if (!shouldHide)
return false;
Expand Down
30 changes: 15 additions & 15 deletions src/test/java/dinkplugin/notifiers/LootNotifierTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ void testNotifyNpc() {
.replacement("{{source}}", Replacements.ofWiki(name))
.build()
)
.extra(new LootNotificationData(Collections.singletonList(new SerializedItemStack(ItemID.RUBY, 1, RUBY_PRICE, "Ruby")), name, LootRecordType.NPC, kc + 1, null))
.extra(new LootNotificationData(Collections.singletonList(new SerializedItemStack(ItemID.RUBY, 1, RUBY_PRICE, "Ruby")), name, LootRecordType.NPC, kc + 1, null, null))
.type(NotificationType.LOOT)
.build()
);
Expand Down Expand Up @@ -141,7 +141,7 @@ void testNotifyNpcRarity() {
.replacement("{{source}}", Replacements.ofWiki(name))
.build()
)
.extra(new LootNotificationData(List.of(new SerializedItemStack(ItemID.LARRANS_KEY, 1, LARRAN_PRICE, "Larran's key")), name, LootRecordType.NPC, 1, rarity))
.extra(new LootNotificationData(List.of(new SerializedItemStack(ItemID.LARRANS_KEY, 1, LARRAN_PRICE, "Larran's key")), name, LootRecordType.NPC, 1, rarity, null))
.type(NotificationType.LOOT)
.thumbnailUrl(ItemUtils.getItemImageUrl(ItemID.LARRANS_KEY))
.build()
Expand Down Expand Up @@ -214,7 +214,7 @@ void testNotifyAllowlist() {
.replacement("{{source}}", Replacements.ofLink(LOOTED_NAME, config.playerLookupService().getPlayerUrl(LOOTED_NAME)))
.build()
)
.extra(new LootNotificationData(Collections.singletonList(new SerializedItemStack(ItemID.TUNA, 1, TUNA_PRICE, "Tuna")), LOOTED_NAME, LootRecordType.PLAYER, 1, null))
.extra(new LootNotificationData(Collections.singletonList(new SerializedItemStack(ItemID.TUNA, 1, TUNA_PRICE, "Tuna")), LOOTED_NAME, LootRecordType.PLAYER, 1, null, null))
.type(NotificationType.LOOT)
.build()
);
Expand Down Expand Up @@ -243,7 +243,7 @@ void testNotifyAllowlistWildcard() {
.replacement("{{source}}", Replacements.ofLink(LOOTED_NAME, config.playerLookupService().getPlayerUrl(LOOTED_NAME)))
.build()
)
.extra(new LootNotificationData(Collections.singletonList(new SerializedItemStack(ItemID.TUNA, 1, TUNA_PRICE, "Tuna")), LOOTED_NAME, LootRecordType.PLAYER, 1, null))
.extra(new LootNotificationData(Collections.singletonList(new SerializedItemStack(ItemID.TUNA, 1, TUNA_PRICE, "Tuna")), LOOTED_NAME, LootRecordType.PLAYER, 1, null, null))
.type(NotificationType.LOOT)
.build()
);
Expand Down Expand Up @@ -292,7 +292,7 @@ void testNotifyWhisperer() {
.replacement("{{source}}", Replacements.ofWiki(name))
.build()
)
.extra(new LootNotificationData(Collections.singletonList(new SerializedItemStack(ItemID.RUBY, 1, RUBY_PRICE, "Ruby")), name, LootRecordType.NPC, 1, null))
.extra(new LootNotificationData(Collections.singletonList(new SerializedItemStack(ItemID.RUBY, 1, RUBY_PRICE, "Ruby")), name, LootRecordType.NPC, 1, null, null))
.type(NotificationType.LOOT)
.build()
);
Expand Down Expand Up @@ -347,7 +347,7 @@ void testNotifyPickpocket() {
.replacement("{{source}}", Replacements.ofWiki(LOOTED_NAME))
.build()
)
.extra(new LootNotificationData(Collections.singletonList(new SerializedItemStack(ItemID.RUBY, 1, RUBY_PRICE, "Ruby")), LOOTED_NAME, LootRecordType.PICKPOCKET, 1, null))
.extra(new LootNotificationData(Collections.singletonList(new SerializedItemStack(ItemID.RUBY, 1, RUBY_PRICE, "Ruby")), LOOTED_NAME, LootRecordType.PICKPOCKET, 1, null, null))
.type(NotificationType.LOOT)
.build()
);
Expand Down Expand Up @@ -385,7 +385,7 @@ void testNotifyClue() {
.replacement("{{source}}", Replacements.ofWiki(source))
.build()
)
.extra(new LootNotificationData(Collections.singletonList(new SerializedItemStack(ItemID.RUBY, 1, RUBY_PRICE, "Ruby")), source, LootRecordType.EVENT, 42, null))
.extra(new LootNotificationData(Collections.singletonList(new SerializedItemStack(ItemID.RUBY, 1, RUBY_PRICE, "Ruby")), source, LootRecordType.EVENT, 42, null, null))
.type(NotificationType.LOOT)
.build()
);
Expand Down Expand Up @@ -428,7 +428,7 @@ void testNotifyPlayer() {
.replacement("{{source}}", Replacements.ofLink(LOOTED_NAME, config.playerLookupService().getPlayerUrl(LOOTED_NAME)))
.build()
)
.extra(new LootNotificationData(Arrays.asList(new SerializedItemStack(ItemID.RUBY, 1, RUBY_PRICE, "Ruby"), new SerializedItemStack(ItemID.TUNA, 1, TUNA_PRICE, "Tuna")), LOOTED_NAME, LootRecordType.PLAYER, 1, null))
.extra(new LootNotificationData(Arrays.asList(new SerializedItemStack(ItemID.RUBY, 1, RUBY_PRICE, "Ruby"), new SerializedItemStack(ItemID.TUNA, 1, TUNA_PRICE, "Tuna")), LOOTED_NAME, LootRecordType.PLAYER, 1, null, null))
.type(NotificationType.LOOT)
.build()
);
Expand Down Expand Up @@ -459,7 +459,7 @@ void testNotifyPlayerForwarded() {
.replacement("{{source}}", Replacements.ofLink(LOOTED_NAME, config.playerLookupService().getPlayerUrl(LOOTED_NAME)))
.build()
)
.extra(new LootNotificationData(Arrays.asList(new SerializedItemStack(ItemID.RUBY, 1, RUBY_PRICE, "Ruby"), new SerializedItemStack(ItemID.TUNA, 1, TUNA_PRICE, "Tuna")), LOOTED_NAME, LootRecordType.PLAYER, 1, null))
.extra(new LootNotificationData(Arrays.asList(new SerializedItemStack(ItemID.RUBY, 1, RUBY_PRICE, "Ruby"), new SerializedItemStack(ItemID.TUNA, 1, TUNA_PRICE, "Tuna")), LOOTED_NAME, LootRecordType.PLAYER, 1, null, null))
.type(NotificationType.LOOT)
.build()
);
Expand Down Expand Up @@ -489,7 +489,7 @@ void testNotifyPlayerForwardBlank() {
.replacement("{{source}}", Replacements.ofLink(LOOTED_NAME, config.playerLookupService().getPlayerUrl(LOOTED_NAME)))
.build()
)
.extra(new LootNotificationData(Arrays.asList(new SerializedItemStack(ItemID.RUBY, 1, RUBY_PRICE, "Ruby"), new SerializedItemStack(ItemID.TUNA, 1, TUNA_PRICE, "Tuna")), LOOTED_NAME, LootRecordType.PLAYER, 1, null))
.extra(new LootNotificationData(Arrays.asList(new SerializedItemStack(ItemID.RUBY, 1, RUBY_PRICE, "Ruby"), new SerializedItemStack(ItemID.TUNA, 1, TUNA_PRICE, "Tuna")), LOOTED_NAME, LootRecordType.PLAYER, 1, null, null))
.type(NotificationType.LOOT)
.build()
);
Expand Down Expand Up @@ -521,7 +521,7 @@ void testNotifyPkChest() {
.replacement("{{source}}", Replacements.ofWiki(source))
.build()
)
.extra(new LootNotificationData(List.of(new SerializedItemStack(ItemID.OPAL, 1, OPAL_PRICE, "Opal"), new SerializedItemStack(ItemID.TUNA, 2, TUNA_PRICE, "Tuna")), source, LootRecordType.EVENT, 1, null))
.extra(new LootNotificationData(List.of(new SerializedItemStack(ItemID.OPAL, 1, OPAL_PRICE, "Opal"), new SerializedItemStack(ItemID.TUNA, 2, TUNA_PRICE, "Tuna")), source, LootRecordType.EVENT, 1, null, null))
.type(NotificationType.LOOT)
.thumbnailUrl(ItemUtils.getItemImageUrl(ItemID.TUNA))
.build()
Expand Down Expand Up @@ -590,7 +590,7 @@ void testNotifyMultiple() {
.replacement("{{source}}", Replacements.ofWiki(LOOTED_NAME))
.build()
)
.extra(new LootNotificationData(Arrays.asList(new SerializedItemStack(ItemID.RUBY, 1, RUBY_PRICE, "Ruby"), new SerializedItemStack(ItemID.OPAL, 1, OPAL_PRICE, "Opal"), new SerializedItemStack(ItemID.TUNA, 1, TUNA_PRICE, "Tuna")), LOOTED_NAME, LootRecordType.EVENT, 1, null))
.extra(new LootNotificationData(Arrays.asList(new SerializedItemStack(ItemID.RUBY, 1, RUBY_PRICE, "Ruby"), new SerializedItemStack(ItemID.OPAL, 1, OPAL_PRICE, "Opal"), new SerializedItemStack(ItemID.TUNA, 1, TUNA_PRICE, "Tuna")), LOOTED_NAME, LootRecordType.EVENT, 1, null, null))
.type(NotificationType.LOOT)
.build()
);
Expand Down Expand Up @@ -627,7 +627,7 @@ void testNotifyRepeated() {
.replacement("{{source}}", Replacements.ofWiki(LOOTED_NAME))
.build()
)
.extra(new LootNotificationData(Collections.singletonList(new SerializedItemStack(ItemID.TUNA, 5, TUNA_PRICE, "Tuna")), LOOTED_NAME, LootRecordType.EVENT, 1, null))
.extra(new LootNotificationData(Collections.singletonList(new SerializedItemStack(ItemID.TUNA, 5, TUNA_PRICE, "Tuna")), LOOTED_NAME, LootRecordType.EVENT, 1, null, null))
.type(NotificationType.LOOT)
.build()
);
Expand Down Expand Up @@ -679,7 +679,7 @@ void testNotifyGauntlet() {
.replacement("{{source}}", Replacements.ofWiki(source))
.build()
)
.extra(new LootNotificationData(List.of(new SerializedItemStack(ItemID.RUBY, quantity, RUBY_PRICE, "Ruby")), source, LootRecordType.EVENT, kc, null))
.extra(new LootNotificationData(List.of(new SerializedItemStack(ItemID.RUBY, quantity, RUBY_PRICE, "Ruby")), source, LootRecordType.EVENT, kc, null, null))
.type(NotificationType.LOOT)
.build()
);
Expand Down Expand Up @@ -711,7 +711,7 @@ void testNotifyCorruptedGauntlet() {
.replacement("{{source}}", Replacements.ofWiki(realSource))
.build()
)
.extra(new LootNotificationData(List.of(new SerializedItemStack(ItemID.RUBY, quantity, RUBY_PRICE, "Ruby")), realSource, LootRecordType.EVENT, kc, null))
.extra(new LootNotificationData(List.of(new SerializedItemStack(ItemID.RUBY, quantity, RUBY_PRICE, "Ruby")), realSource, LootRecordType.EVENT, kc, null, null))
.type(NotificationType.LOOT)
.build()
);
Expand Down
Loading