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

Merged
merged 12 commits into from
Feb 6, 2025
Next Next commit
feat(kc): include pb time in metadata for normal kills
iProdigy committed Jan 30, 2025
commit 44dcb396df506e2ee6d0c65ff884f2a54cf9c097
25 changes: 18 additions & 7 deletions src/main/java/dinkplugin/notifiers/KillCountNotifier.java
Original file line number Diff line number Diff line change
@@ -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;
@@ -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";

@@ -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));
}
}
}
@@ -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())
);
}
@@ -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();
}
@@ -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
@@ -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
38 changes: 19 additions & 19 deletions src/test/java/dinkplugin/notifiers/KillCountNotifierTest.java
Original file line number Diff line number Diff line change
@@ -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();
@@ -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()
@@ -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();
@@ -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();
@@ -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();
@@ -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()
@@ -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();
@@ -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()
@@ -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()
@@ -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()
@@ -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()
@@ -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()
@@ -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()
@@ -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()
@@ -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()
@@ -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()
@@ -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()
@@ -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();
@@ -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();