diff --git a/triggers/triggers-dt/src/main/java/gg/xp/xivsupport/triggers/ultimate/FRU.java b/triggers/triggers-dt/src/main/java/gg/xp/xivsupport/triggers/ultimate/FRU.java index a266f6ffbc99..05633aa49e7f 100644 --- a/triggers/triggers-dt/src/main/java/gg/xp/xivsupport/triggers/ultimate/FRU.java +++ b/triggers/triggers-dt/src/main/java/gg/xp/xivsupport/triggers/ultimate/FRU.java @@ -777,9 +777,11 @@ private static Predicate initDurBetween(int secondsMin, int seconds .extendedDescription("The long fire/stack pop calls happen about 25 seconds in."); private final ModifiableCallout relLongStackPop = ModifiableCallout.durationBasedCall("Relativity: Long Stack Popping", "Stack").autoIcon(); - private final ModifiableCallout relMedFireBaitLookOut = new ModifiableCallout<>("Relativity: Medium Fire Final Baits", "Bait Light, Look Outside").statusIcon(0x998) + private final ModifiableCallout relMedFireBaitLookOut = new ModifiableCallout<>("Relativity: Medium Fire Final Baits", "Bait Light then Look Outside") .extendedDescription("Final Traffic Light Bait, with Medium Fire"); - private final ModifiableCallout relFinalCenterLookOut = new ModifiableCallout<>("Relativity: Final Mechanics", "Look Outside").statusIcon(0x998) + private final ModifiableCallout relMedFireBaitMoveInLookOut = ModifiableCallout.durationBasedCall("Relativity: Medium Fire Final Mechanic", "Move In, Look Outside").statusIcon(0x998) + .extendedDescription("Final Traffic Light Bait, with Medium Fire, after Baiting Light"); + private final ModifiableCallout relFinalCenterLookOut = ModifiableCallout.durationBasedCall("Relativity: Final Mechanics", "Look Outside").statusIcon(0x998) .extendedDescription("Final Traffic Light Bait, not Medium Fire"); @AutoFeed @@ -938,9 +940,15 @@ private static Predicate initDurBetween(int secondsMin, int seconds s.waitMs(5_000); // T=31 + var rewindBuff = s.findOrWaitForBuff(buffs, ba -> ba.buffIdMatches(0x994)); + medFireC.findAny(isPlayer).ifPresentOrElse( - e -> s.updateCall(relMedFireBaitLookOut), - () -> s.updateCall(relFinalCenterLookOut) + e -> { + s.updateCall(relMedFireBaitLookOut); + s.waitEvent(AbilityUsedEvent.class, aue -> aue.abilityIdMatches(0x9D2B)); + s.updateCall(relMedFireBaitMoveInLookOut, rewindBuff); + }, + () -> s.updateCall(relFinalCenterLookOut, rewindBuff) ); }); diff --git a/triggers/triggers-dt/src/test/java/gg/xp/xivsupport/triggers/ultimate/FRUTest.java b/triggers/triggers-dt/src/test/java/gg/xp/xivsupport/triggers/ultimate/FRUTest.java index b6c7bed8e289..8b47ab04f0e8 100644 --- a/triggers/triggers-dt/src/test/java/gg/xp/xivsupport/triggers/ultimate/FRUTest.java +++ b/triggers/triggers-dt/src/test/java/gg/xp/xivsupport/triggers/ultimate/FRUTest.java @@ -93,7 +93,7 @@ protected List getExpectedCalls() { call(473149, "Stack", "Stack (4.6)"), call(478175, "Stand Middle", "Stand Middle"), call(483195, "Move Out", "Move Out (4.5)"), - call(488217, "Look Outside", "Look Outside"), + call(488217, "Look Outside", "Look Outside (8.5)"), call(500670, "Stack", "Stack (2.7)"), call(507080, "Raidwide", "Raidwide (4.7)"), call(515215, "Tankbuster on Chachajire Titijire", "Tankbuster on Chachajire Titijire (4.7)"), diff --git a/xivsupport/src/main/java/gg/xp/xivsupport/callouts/ModifiableCallout.java b/xivsupport/src/main/java/gg/xp/xivsupport/callouts/ModifiableCallout.java index 41955d268379..6e84ee15ecc9 100644 --- a/xivsupport/src/main/java/gg/xp/xivsupport/callouts/ModifiableCallout.java +++ b/xivsupport/src/main/java/gg/xp/xivsupport/callouts/ModifiableCallout.java @@ -8,6 +8,7 @@ import gg.xp.xivsupport.events.actlines.events.HasStatusEffect; import gg.xp.xivsupport.gui.tables.renderers.IconTextRenderer; import gg.xp.xivsupport.gui.tables.renderers.RenderUtils; +import org.apache.commons.lang3.mutable.MutableBoolean; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,19 +83,23 @@ public ModifiableCallout(String descriptionAndText) { * @param expiry A condition for expiring the callout (removes it from the on-screen display) */ public ModifiableCallout(String description, String tts, String text, Predicate expiry) { + this(description, tts, text, (call, event) -> expiry.test(event)); + } + + private ModifiableCallout(String description, String tts, String text, CombinedExpiryCondition expiry) { this.description = description; this.defaultTtsText = tts; this.defaultVisualText = text; - this.expiry = x -> expiry.test(x.getEvent()); + this.expiry = expiry.combined(); } -// // last argument is dumb but works -// public ModifiableCallout(String description, String tts, String text, Predicate> expiry, int ignoredUnused) { -// this.description = description; -// this.defaultTtsText = tts; -// this.defaultVisualText = text; -// this.expiry = expiry; -// } + private interface CombinedExpiryCondition { + boolean isExpired(RawModifiedCallout callout, X event); + + default Predicate> combined() { + return c -> isExpired(c, c.getEvent()); + } + } /** * A callout with the same TTS and on-screen text, and a custom expiry time. @@ -131,7 +136,7 @@ public static Predicate expiresIn(Duration dur) { return be.getEffectiveTimeSince().compareTo(dur) > 0; } else { - log.warn("Hit expiresIn false branch - this should never happen!"); + log.error("Hit expiresIn false branch - this should never happen!"); return defaultExpiryAt.isBefore(Instant.now()); } }; @@ -341,19 +346,14 @@ public RawModifiedCallout getModified(Map rawArguments) { * that the buff will. * * @param desc The description. - * @param text The base text. For the visual text, the duration will be appended in parenthesis. + * @param text The base text. For the visual text, the duration will be appended in parentheses. * e.g. "Water on You" will become "Water on You" (123.4) will be appended, and the timer will count * down. * @return the ModifiableCallout */ public static ModifiableCallout durationBasedCall(String desc, String text) { - Predicate expiry = hd -> { - if (hd == null) { - log.error("durationBasedCall: event was null! No time basis! Expiring callout prematurely."); - return true; - } - return hd.getEstimatedTimeSinceExpiry().compareTo(defaultLingerTime) > 0; - }; + // TODO: this warns once per call per program execution, rather than once per call invocation + CombinedExpiryCondition expiry = combinedExpiry(defaultLingerTime); return new ModifiableCallout<>(desc, text, text + " ({event.estimatedRemainingDuration})", expiry); } @@ -363,15 +363,23 @@ public static ModifiableCallout durationBasedCall(Str public static ModifiableCallout durationBasedCallWithOffset(String desc, String text, Duration offset) { Duration combinedLingerTime = defaultLingerTime.plus(offset); - Predicate expiry = hd -> { + CombinedExpiryCondition expiry = combinedExpiry(combinedLingerTime); + long millis = offset.toMillis(); + return new ModifiableCallout<>(desc, text, text + " ({event.remainingDurationPlus(java.time.Duration.ofMillis(" + millis + "))})", expiry); + } + + private static CombinedExpiryCondition combinedExpiry(Duration combinedLingerTime) { + var alreadyWarned = new MutableBoolean(); + return (call, hd) -> { if (hd == null) { - log.error("durationBasedCall: event was null! No time basis! Expiring callout prematurely."); - return true; + if (alreadyWarned.isFalse()) { + log.error("durationBasedCall: event was null! No time basis! Falling back to fixed duration."); + alreadyWarned.setTrue(); + } + return call.getEffectiveTimeSince().compareTo(defaultLingerTime) > 0; } return hd.getEstimatedTimeSinceExpiry().compareTo(combinedLingerTime) > 0; }; - long millis = offset.toMillis(); - return new ModifiableCallout<>(desc, text, text + " ({event.remainingDurationPlus(java.time.Duration.ofMillis(" + millis + "))})", expiry); }