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(kc): include pb time in metadata for normal kills #648

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## Unreleased

- Minor: Add `personalBest` time to killcount notification metadata. (#648)
- Minor: Add `%SENDER%` template variable for chat notifier. (#644)
- Bugfix: Ignore misconfigured loot value-rarity intersection setting if rarity threshold is not configured. (#647)

Expand Down
7 changes: 6 additions & 1 deletion docs/json-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,13 +334,18 @@ JSON for Kill Count Notifications:
"gameMessage": "Your completed Chambers of Xeric count is: 69.",
"time": "PT46M34S",
"isPersonalBest": true,
"personalBest": null,
"party": ["%USERNAME%", "another RSN", "yet another RSN"]
},
"type": "KILL_COUNT"
}
```

When an associated duration is not found, `extra.time` and `extra.isPersonalBest` are not populated.
Both `extra.time` and `extra.personalBest` are reported in [ISO-8601 duration format](https://en.wikipedia.org/wiki/ISO_8601#Durations).

When an associated duration is not found, `extra.time`, `extra.isPersonalBest`, and `extra.personalBest` are not populated.
It is possible for both `extra.time` and `extra.isPersonalBest` to be populated while `extra.personalBest` is absent.
Also, `extra.personalBest` is never populated if `isPersonalBest` is true.

Note: when `boss` is `Penance Queen`, `count` refers to the high level gamble count, rather than kill count.

Expand Down
25 changes: 18 additions & 7 deletions src/main/java/dinkplugin/notifiers/KillCountNotifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import dinkplugin.util.KillCountService;
import dinkplugin.util.TimeUtils;
import dinkplugin.util.Utils;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.Varbits;
Expand Down Expand Up @@ -40,7 +41,7 @@ public class KillCountNotifier extends BaseNotifier {

private static final Pattern PRIMARY_REGEX = Pattern.compile("Your (?<key>.+)\\s(?<type>kill|chest|completion|harvest)\\s?count is: ?(?<value>[\\d,]+)\\b", Pattern.CASE_INSENSITIVE);
private static final Pattern SECONDARY_REGEX = Pattern.compile("Your (?:completed|subdued) (?<key>.+) count is: (?<value>[\\d,]+)\\b");
private static final Pattern TIME_REGEX = Pattern.compile("(?:Duration|time|Subdued in):? (?<time>[\\d:]+(.\\d+)?)\\.?", Pattern.CASE_INSENSITIVE);
private static final Pattern TIME_REGEX = Pattern.compile("(?:Duration|time|Subdued in):? (?<time>[\\d:]+(?:.\\d+)?)\\.?(?: Personal best: (?<pbtime>[\\d:+]+(?:.\\d+)?))?", Pattern.CASE_INSENSITIVE);

private static final String BA_BOSS_NAME = "Penance Queen";

Expand Down Expand Up @@ -92,7 +93,7 @@ public void onWidget(WidgetLoaded event) {
// https://oldschool.runescape.wiki/w/Barbarian_Assault/Rewards#Earning_Honour_points
if (widget != null && widget.getText().contains("80 ") && widget.getText().contains("5 ")) {
int gambleCount = client.getVarbitValue(Varbits.BA_GC);
this.data.set(new BossNotificationData(BA_BOSS_NAME, gambleCount, "The Queen is dead!", null, null, null));
this.data.set(new BossNotificationData(BA_BOSS_NAME, gambleCount, "The Queen is dead!", null, null, null, null));
}
}
}
Expand Down Expand Up @@ -174,6 +175,7 @@ private void updateData(BossNotificationData updated) {
defaultIfNull(updated.getGameMessage(), old.getGameMessage()),
updated.getTime() == null || (tob && old.getTime() != null) ? old.getTime() : updated.getTime(),
updated.isPersonalBest() == null || (tob && old.isPersonalBest() != null) ? old.isPersonalBest() : updated.isPersonalBest(),
defaultIfNull(updated.getPersonalBest(), old.getPersonalBest()),
defaultIfNull(updated.getParty(), old.getParty())
);
}
Expand All @@ -184,21 +186,23 @@ private static Optional<BossNotificationData> parse(Client client, String messag
if (message.startsWith("Preparation")) return Optional.empty();
Optional<Pair<String, Integer>> boss = parseBoss(message);
if (boss.isPresent())
return boss.map(pair -> new BossNotificationData(pair.getLeft(), pair.getRight(), message, null, null, Utils.getBossParty(client, pair.getLeft())));
return boss.map(pair -> new BossNotificationData(pair.getLeft(), pair.getRight(), message, null, null, null, Utils.getBossParty(client, pair.getLeft())));

// TOB reports final wave duration before challenge time in the same message; skip to the part we care about
int tobIndex = message.startsWith("Wave") ? message.indexOf(KillCountService.TOB) : -1;
String msg = tobIndex < 0 ? message : message.substring(tobIndex);

return parseTime(msg).map(t -> new BossNotificationData(tobIndex < 0 ? null : KillCountService.TOB, null, null, t.getLeft(), t.getRight(), null));
return parseTime(msg).map(t -> new BossNotificationData(tobIndex < 0 ? null : KillCountService.TOB, null, null, t.getTime(), t.isPb(), t.getPb(), null));
}

private static Optional<Pair<Duration, Boolean>> parseTime(String message) {
private static Optional<ParsedTime> parseTime(String message) {
Matcher matcher = TIME_REGEX.matcher(message);
if (matcher.find()) {
Duration duration = TimeUtils.parseTime(matcher.group("time"));
boolean pb = message.toLowerCase().contains("(new personal best)");
return Optional.of(Pair.of(duration, pb));
boolean isPb = message.toLowerCase().contains("(new personal best)");
String pbTime = matcher.group("pbtime");
Duration pb = pbTime != null ? TimeUtils.parseTime(pbTime) : null;
return Optional.of(new ParsedTime(duration, isPb, pb));
}
return Optional.empty();
}
Expand Down Expand Up @@ -271,4 +275,11 @@ private static String parseSecondary(String boss) {

return null;
}

@Value
private static class ParsedTime {
Duration time;
boolean isPb;
@Nullable Duration pb;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public class BossNotificationData extends NotificationData {
@Accessors(fluent = true)
Boolean isPersonalBest;
@Nullable
@JsonAdapter(DurationAdapter.class)
Duration personalBest;
@Nullable
Collection<String> party;

@Override
Expand Down
38 changes: 19 additions & 19 deletions src/test/java/dinkplugin/notifiers/KillCountNotifierTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ void testNotifyInterval() {
// check notification
NotificationBody<BossNotificationData> body = NotificationBody.<BossNotificationData>builder()
.text(buildTemplate("King Black Dragon", 420))
.extra(new BossNotificationData("King Black Dragon", 420, gameMessage, null, null, null))
.extra(new BossNotificationData("King Black Dragon", 420, gameMessage, null, null, null, null))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build();
Expand Down Expand Up @@ -100,7 +100,7 @@ void testNotifyInitial() {
true,
NotificationBody.builder()
.text(buildTemplate("King Black Dragon", 1))
.extra(new BossNotificationData("King Black Dragon", 1, gameMessage, null, null, null))
.extra(new BossNotificationData("King Black Dragon", 1, gameMessage, null, null, null, null))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build()
Expand Down Expand Up @@ -152,7 +152,7 @@ void testNotifyPb() {
// check notification
NotificationBody<BossNotificationData> body = NotificationBody.<BossNotificationData>builder()
.text(buildPbTemplate("Zulrah", "00:56.50", 12))
.extra(new BossNotificationData("Zulrah", 12, gameMessage, Duration.ofSeconds(56).plusMillis(500), true, null))
.extra(new BossNotificationData("Zulrah", 12, gameMessage, Duration.ofSeconds(56).plusMillis(500), true, null, null))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build();
Expand Down Expand Up @@ -181,7 +181,7 @@ void testNotifyPbDelayed() {
// check notification
NotificationBody<BossNotificationData> body = NotificationBody.<BossNotificationData>builder()
.text(buildPbTemplate("Grotesque Guardians", "01:54.00", 79))
.extra(new BossNotificationData("Grotesque Guardians", 79, gameMessage, Duration.ofMinutes(1).plusSeconds(54), true, null))
.extra(new BossNotificationData("Grotesque Guardians", 79, gameMessage, Duration.ofMinutes(1).plusSeconds(54), true, null, null))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build();
Expand Down Expand Up @@ -210,7 +210,7 @@ void testNotifyExtremeDelayMissingPb() {
// check notification
NotificationBody<BossNotificationData> body = NotificationBody.<BossNotificationData>builder()
.text(buildTemplate("Grotesque Guardians", 80))
.extra(new BossNotificationData("Grotesque Guardians", 80, gameMessage, null, null, null))
.extra(new BossNotificationData("Grotesque Guardians", 80, gameMessage, null, null, null, null))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build();
Expand Down Expand Up @@ -241,7 +241,7 @@ void testNotifyPbLong() {
true,
NotificationBody.builder()
.text(buildPbTemplate("Zulrah", "01:00:56.50", 1))
.extra(new BossNotificationData("Zulrah", 1, gameMessage, Duration.ofHours(1).plusSeconds(56).plusMillis(500), true, null))
.extra(new BossNotificationData("Zulrah", 1, gameMessage, Duration.ofHours(1).plusSeconds(56).plusMillis(500), true, null, null))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build()
Expand All @@ -263,7 +263,7 @@ void testNotifyPbImprecise() {
// check notification
NotificationBody<BossNotificationData> body = NotificationBody.<BossNotificationData>builder()
.text(buildPbTemplate("Zulrah", "00:56", 13))
.extra(new BossNotificationData("Zulrah", 13, gameMessage, Duration.ofSeconds(56), true, null))
.extra(new BossNotificationData("Zulrah", 13, gameMessage, Duration.ofSeconds(56), true, null, null))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build();
Expand Down Expand Up @@ -294,7 +294,7 @@ void testNotifyChambersPb() {
true,
NotificationBody.builder()
.text(buildPbTemplate("Chambers of Xeric", "36:04.20", 125))
.extra(new BossNotificationData("Chambers of Xeric", 125, gameMessage, Duration.ofMinutes(36).plusSeconds(4).plusMillis(200), true, Collections.emptyList()))
.extra(new BossNotificationData("Chambers of Xeric", 125, gameMessage, Duration.ofMinutes(36).plusSeconds(4).plusMillis(200), true, null, Collections.emptyList()))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build()
Expand All @@ -319,7 +319,7 @@ void testNotifyChambersInterval() {
true,
NotificationBody.builder()
.text(buildTemplate("Chambers of Xeric", 150))
.extra(new BossNotificationData("Chambers of Xeric", 150, gameMessage, Duration.ofMinutes(46).plusSeconds(31).plusMillis(800), false, Collections.emptyList()))
.extra(new BossNotificationData("Chambers of Xeric", 150, gameMessage, Duration.ofMinutes(46).plusSeconds(31).plusMillis(800), false, Duration.ofMinutes(40).plusSeconds(24).plusMillis(600), Collections.emptyList()))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build()
Expand All @@ -345,7 +345,7 @@ void testNotifyTobPb() {
true,
NotificationBody.builder()
.text(buildPbTemplate("Theatre of Blood", "21:33.60", 1))
.extra(new BossNotificationData("Theatre of Blood", 1, gameMessage, Duration.ofMinutes(21).plusSeconds(33).plusMillis(600), true, Collections.emptyList()))
.extra(new BossNotificationData("Theatre of Blood", 1, gameMessage, Duration.ofMinutes(21).plusSeconds(33).plusMillis(600), true, null, Collections.emptyList()))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build()
Expand All @@ -370,7 +370,7 @@ void testNotifyTobInterval() {
true,
NotificationBody.builder()
.text(buildTemplate("Theatre of Blood", 5))
.extra(new BossNotificationData("Theatre of Blood", 5, gameMessage, Duration.ofMinutes(19).plusSeconds(26).plusMillis(400), false, Collections.emptyList()))
.extra(new BossNotificationData("Theatre of Blood", 5, gameMessage, Duration.ofMinutes(19).plusSeconds(26).plusMillis(400), false, Duration.ofMinutes(19).plusSeconds(24), Collections.emptyList()))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build()
Expand All @@ -395,7 +395,7 @@ void testNotifyGauntletPb() {
true,
NotificationBody.builder()
.text(buildPbTemplate("Crystalline Hunllef", "10:25.00", 10))
.extra(new BossNotificationData("Crystalline Hunllef", 10, gameMessage, Duration.ofMinutes(10).plusSeconds(25), true, null))
.extra(new BossNotificationData("Crystalline Hunllef", 10, gameMessage, Duration.ofMinutes(10).plusSeconds(25), true, null, null))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build()
Expand All @@ -419,7 +419,7 @@ void testNotifyTemporossPb() {
true,
NotificationBody.builder()
.text(buildPbTemplate("Tempoross", "06:30.00", 69))
.extra(new BossNotificationData("Tempoross", 69, gameMessage, Duration.ofMinutes(6).plusSeconds(30), true, null))
.extra(new BossNotificationData("Tempoross", 69, gameMessage, Duration.ofMinutes(6).plusSeconds(30), true, null, null))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build()
Expand Down Expand Up @@ -449,7 +449,7 @@ void testNotifyTombsPb() {
true,
NotificationBody.builder()
.text(buildPbTemplate("Tombs of Amascut: Expert Mode", "25:00.00", 8))
.extra(new BossNotificationData("Tombs of Amascut: Expert Mode", 8, gameMessage, Duration.ofMinutes(25), true, party))
.extra(new BossNotificationData("Tombs of Amascut: Expert Mode", 8, gameMessage, Duration.ofMinutes(25), true, null, party))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build()
Expand All @@ -473,7 +473,7 @@ void testNotifyNoPb() {
true,
NotificationBody.builder()
.text(buildTemplate("Zulrah", 12))
.extra(new BossNotificationData("Zulrah", 12, gameMessage, Duration.ofSeconds(59).plusMillis(300), false, null))
.extra(new BossNotificationData("Zulrah", 12, gameMessage, Duration.ofSeconds(59).plusMillis(300), false, Duration.ofSeconds(56).plusMillis(500), null))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build()
Expand All @@ -496,7 +496,7 @@ void testNotifyPerilousMoons() {
true,
NotificationBody.builder()
.text(buildTemplate("Lunar Chest", 30))
.extra(new BossNotificationData("Lunar Chest", 30, gameMessage, null, null, null))
.extra(new BossNotificationData("Lunar Chest", 30, gameMessage, null, null, null, null))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build()
Expand Down Expand Up @@ -532,7 +532,7 @@ void testNotifyBarbarianAssault() {
.replacement("{{boss}}", Replacements.ofWiki(boss))
.build()
)
.extra(new BossNotificationData(boss, count, "The Queen is dead!", null, null, null))
.extra(new BossNotificationData(boss, count, "The Queen is dead!", null, null, null, null))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build()
Expand Down Expand Up @@ -646,7 +646,7 @@ void testPrimaryWithComma() {
// check notification
NotificationBody<BossNotificationData> body = NotificationBody.<BossNotificationData>builder()
.text(buildTemplate("King Black Dragon", 1337))
.extra(new BossNotificationData("King Black Dragon", 1337, gameMessage, null, null, null))
.extra(new BossNotificationData("King Black Dragon", 1337, gameMessage, null, null, null, null))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build();
Expand Down Expand Up @@ -674,7 +674,7 @@ void testSecondaryWithComma() {
// check notification
NotificationBody<BossNotificationData> body = NotificationBody.<BossNotificationData>builder()
.text(buildTemplate("Tempoross", 1337))
.extra(new BossNotificationData("Tempoross", 1337, gameMessage, Duration.ofMinutes(6).plusSeconds(13), false, null))
.extra(new BossNotificationData("Tempoross", 1337, gameMessage, Duration.ofMinutes(6).plusSeconds(13), false, Duration.ofMinutes(5).plusSeconds(57), null))
.playerName(PLAYER_NAME)
.type(NotificationType.KILL_COUNT)
.build();
Expand Down