diff --git a/src/analysis/retail/evoker/augmentation/CHANGELOG.tsx b/src/analysis/retail/evoker/augmentation/CHANGELOG.tsx
index bbebfeafeb2..c01aab72087 100644
--- a/src/analysis/retail/evoker/augmentation/CHANGELOG.tsx
+++ b/src/analysis/retail/evoker/augmentation/CHANGELOG.tsx
@@ -4,6 +4,7 @@ import { SpellLink } from 'interface';
import TALENTS from 'common/TALENTS/evoker';
export default [
+ change(date(2024, 10, 4), <>Fix some edgecases for module>, Vollmer),
change(date(2024, 9, 16), "Update Buff Helper note gen for Frame Glows WA", Vollmer),
change(date(2024, 9, 10), "Update Ability Filters for Helper Modules for TWW S1", Vollmer),
change(date(2024, 9, 6), <>Implement module>, Vollmer),
diff --git a/src/analysis/retail/evoker/augmentation/modules/breahtofeons/BreathOfEonsRotational.tsx b/src/analysis/retail/evoker/augmentation/modules/breahtofeons/BreathOfEonsRotational.tsx
index 31ba3d76987..b8dd12d66d0 100644
--- a/src/analysis/retail/evoker/augmentation/modules/breahtofeons/BreathOfEonsRotational.tsx
+++ b/src/analysis/retail/evoker/augmentation/modules/breahtofeons/BreathOfEonsRotational.tsx
@@ -8,6 +8,7 @@ import Events, {
EmpowerEndEvent,
EventType,
GetRelatedEvents,
+ HasRelatedEvent,
RemoveBuffEvent,
RemoveDebuffEvent,
UpdateSpellUsableEvent,
@@ -15,6 +16,7 @@ import Events, {
} from 'parser/core/Events';
import {
BREATH_EBON_APPLY_LINK,
+ BREATH_OF_EONS_DEBUFF_LINK,
EBON_MIGHT_BUFF_LINKS,
ebonIsFromBreath,
getBreathOfEonsBuffEvents,
@@ -22,7 +24,7 @@ import {
getBreathOfEonsDebuffApplyEvents,
} from '../normalizers/CastLinkNormalizer';
import SpellUsable from 'parser/shared/modules/SpellUsable';
-import Spell from 'common/SPELLS/dragonflight/potions';
+import Potions from 'common/SPELLS/thewarwithin/potions';
import BreathOfEonsSection from './BreathOfEonsSection';
import spells from 'common/SPELLS/dragonflight/trinkets';
import trinkets from 'common/ITEMS/dragonflight/trinkets';
@@ -43,6 +45,7 @@ type BreathWindowPerformance = {
temporalWoundsCounter: SpellTracker[];
ebonMightDroppedDuringBreath: boolean;
ebonMightDroppedDuration: number;
+ ebonMightDrops: number[];
ebonMightProblems: SpellTracker[];
possibleTrinkets: number;
trinketUsed: number;
@@ -136,7 +139,7 @@ class BreathOfEonsRotational extends Analyzer {
spells.MIRROR_OF_FRACTURED_TOMORROWS,
];
- trackedPotions = [Spell.ELEMENTAL_POTION_OF_ULTIMATE_POWER, Spell.ELEMENTAL_POTION_OF_POWER];
+ trackedPotions = [Potions.TEMPERED_POTION];
foundTrinket = this.selectedCombatant.hasTrinket(trinkets.IRIDEUS_FRAGMENT.id)
? spells.IRIDEUS_FRAGMENT.id
@@ -259,7 +262,7 @@ class BreathOfEonsRotational extends Analyzer {
}
});
- let potionReady = this.spellUsable.isAvailable(Spell.ELEMENTAL_POTION_OF_POWER.id) ? 1 : 0;
+ let potionReady = this.spellUsable.isAvailable(Potions.TEMPERED_POTION.id) ? 1 : 0;
let potionUsed = 0;
// Check if Potion was used pre-breath
this.trackedPotions.forEach((potion) => {
@@ -298,6 +301,7 @@ class BreathOfEonsRotational extends Analyzer {
temporalWoundsCounter: [],
ebonMightDroppedDuringBreath: false,
ebonMightDroppedDuration: 0,
+ ebonMightDrops: [],
possibleTrinkets: trinketReady,
trinketUsed: trinketUsed,
possiblePotions: potionReady,
@@ -374,8 +378,14 @@ class BreathOfEonsRotational extends Analyzer {
event.timestamp !== this.latestEbonMightEvent.timestamp &&
this.latestEbonMightDrop
) {
- const droppedDuration = event.timestamp - this.latestEbonMightDrop.timestamp;
- this.currentPerformanceBreathWindow.ebonMightDroppedDuration += droppedDuration;
+ const perfWindow = this.currentPerformanceBreathWindow;
+ const droppedUptime = perfWindow.ebonMightDrops.reduce(
+ (acc, timestamp) => acc + (event.timestamp - timestamp) / perfWindow.buffedPlayers.size,
+ 0,
+ );
+
+ perfWindow.ebonMightDrops = [];
+ perfWindow.ebonMightDroppedDuration += droppedUptime;
}
this.latestEbonMightEvent = event;
this.ebonMightCount.push({ timestamp: event.timestamp, count: this.ebonMightCounter });
@@ -396,6 +406,7 @@ class BreathOfEonsRotational extends Analyzer {
this.latestEbonMightDrop = event;
const prevProblem = perfWindow.ebonMightProblems[perfWindow.ebonMightProblems.length - 1];
+ perfWindow.ebonMightDrops.push(event.timestamp);
const ebonMightProblem = {
timestamp: event.timestamp,
@@ -443,7 +454,9 @@ class BreathOfEonsRotational extends Analyzer {
this.currentBreathWindow.start = event.timestamp;
}
- this.activeDebuffs = this.activeDebuffs + 1;
+ if (HasRelatedEvent(event, BREATH_OF_EONS_DEBUFF_LINK)) {
+ this.activeDebuffs = this.activeDebuffs + 1;
+ }
this.currentPerformanceBreathWindow.temporalWoundsCounter.push({
timestamp: event.timestamp,
@@ -487,6 +500,19 @@ class BreathOfEonsRotational extends Analyzer {
perfWindow.successfulHits += 1;
}
+ /** In 10.2 blizzard introduced *sparkles* delayed EM buffs *sparkles*
+ * so now we need to double check whether or not we actually found our buffed players */
+ if (this.currentPerformanceBreathWindow.buffedPlayers.size === 0) {
+ const currentBuffedTargets: Map = new Map();
+ const players = Object.values(this.combatants.players);
+ players.forEach((player) => {
+ if (player.hasBuff(SPELLS.EBON_MIGHT_BUFF_EXTERNAL.id)) {
+ currentBuffedTargets.set(player.name, player);
+ }
+ });
+ this.currentPerformanceBreathWindow.buffedPlayers = currentBuffedTargets;
+ }
+
if (
this.activeDebuffs === 0 &&
!BREATH_OF_EONS_SPELLS.some((spell) => this.selectedCombatant.hasBuff(spell.id))
@@ -494,11 +520,14 @@ class BreathOfEonsRotational extends Analyzer {
this.breathWindowActive = false;
const ebonMightDroppedDuringBreath = perfWindow.ebonMightDroppedDuringBreath;
- const ebonMightDroppedDuration = perfWindow.ebonMightDroppedDuration;
- if (ebonMightDroppedDuringBreath && ebonMightDroppedDuration === 0) {
- const droppedUptime = event.timestamp - this.latestEbonMightDrop.timestamp;
- perfWindow.ebonMightDroppedDuration = droppedUptime;
+ if (ebonMightDroppedDuringBreath && perfWindow.ebonMightDrops.length) {
+ const droppedUptime = perfWindow.ebonMightDrops.reduce(
+ (acc, timestamp) => acc + (event.timestamp - timestamp) / perfWindow.buffedPlayers.size,
+ 0,
+ );
+
+ perfWindow.ebonMightDroppedDuration += droppedUptime;
}
breathWindow.breathPerformance = perfWindow;
@@ -520,19 +549,6 @@ class BreathOfEonsRotational extends Analyzer {
perfWindow.potentialLostDamage =
potentialDamagePerTarget * perfWindow.earlyDeaths * PRIO_MULTIPLIER;
}
-
- /** In 10.2 blizzard introduced *sparkles* delayed EM buffs *sparkles*
- * so now we need to double check whether or not we actually found our buffed players */
- if (this.currentPerformanceBreathWindow.buffedPlayers.size === 0) {
- const currentBuffedTargets: Map = new Map();
- const players = Object.values(this.combatants.players);
- players.forEach((player) => {
- if (player.hasBuff(SPELLS.EBON_MIGHT_BUFF_EXTERNAL.id)) {
- currentBuffedTargets.set(player.name, player);
- }
- });
- this.currentPerformanceBreathWindow.buffedPlayers = currentBuffedTargets;
- }
}
private finalize() {
diff --git a/src/analysis/retail/evoker/augmentation/modules/normalizers/CastLinkNormalizer.ts b/src/analysis/retail/evoker/augmentation/modules/normalizers/CastLinkNormalizer.ts
index 92d25cdd16e..b0b4868a54c 100644
--- a/src/analysis/retail/evoker/augmentation/modules/normalizers/CastLinkNormalizer.ts
+++ b/src/analysis/retail/evoker/augmentation/modules/normalizers/CastLinkNormalizer.ts
@@ -38,6 +38,7 @@ export const EBON_MIGHT_APPLY_REMOVE_LINK = 'ebonMightApplyRemoveLink';
export const BREATH_OF_EONS_CAST_DEBUFF_APPLY_LINK = 'breathOfEonsCastDebuffApplyLink';
export const BREATH_OF_EONS_CAST_BUFF_LINK = 'breathOfEonsCastBuffLink';
export const BREATH_OF_EONS_DAMAGE_LINK = 'breathOfEonsDamageLink';
+export const BREATH_OF_EONS_DEBUFF_LINK = 'breathOfEonsDebuffLink';
const ERUPTION_CAST_DAM_LINK = 'eruptionCastDamLink';
const ERUPTION_CHITIN_LINK = 'eruptionChitinLink';
@@ -57,6 +58,7 @@ const BREATH_EBON_BUFFER = 250;
const EBON_MIGHT_BUFFER = 150;
const BREATH_OF_EONS_DEBUFF_APPLY_BUFFER = 8000;
const BREATH_OF_EONS_BUFF_BUFFER = 8000;
+const BREATH_OF_EONS_DEBUFF_BUFFER = 14000;
const BREATH_OF_EONS_DAMAGE_BUFFER = 100;
const PUPIL_OF_ALEXSTRASZA_BUFFER = 1000;
const UPHEAVAL_DAMAGE_BUFFER = 800;
@@ -150,6 +152,37 @@ const EVENT_LINKS: EventLink[] = [
anyTarget: false,
forwardBufferMs: BREATH_OF_EONS_DAMAGE_BUFFER,
},
+ /**
+ * So this is *slightly* cursed, but basically fixes an issue with mobs sharing HP eg. Silken Court.
+ * Essentially will just apply links backwards if no link is already present.
+ * example log:
+ * https://www.warcraftlogs.com/reports/zCvY2PKpHtd6MqAW#fight=4&type=auras&pins=0%24Separate%24%23244F4B%24damage%240%240.0.0.Any%24184027896.0.0.Evoker%24true%240.0.0.Any%24false%24409632&hostility=1&spells=debuffs&target=217&ability=409560&view=events&start=4819003&end=4837682
+ */
+ {
+ linkRelation: BREATH_OF_EONS_DAMAGE_LINK,
+ reverseLinkRelation: BREATH_OF_EONS_DAMAGE_LINK,
+ linkingEventId: SPELLS.BREATH_OF_EONS_DAMAGE.id,
+ linkingEventType: EventType.Damage,
+ referencedEventId: SPELLS.TEMPORAL_WOUND_DEBUFF.id,
+ referencedEventType: EventType.RemoveDebuff,
+ anyTarget: true,
+ maximumLinks: 1,
+ backwardBufferMs: BREATH_OF_EONS_DAMAGE_BUFFER,
+ additionalCondition(linkingEvent, _referencedEvent) {
+ return !HasRelatedEvent(linkingEvent, BREATH_OF_EONS_DAMAGE_LINK);
+ },
+ },
+ {
+ linkRelation: BREATH_OF_EONS_DEBUFF_LINK,
+ reverseLinkRelation: BREATH_OF_EONS_DEBUFF_LINK,
+ linkingEventId: SPELLS.TEMPORAL_WOUND_DEBUFF.id,
+ linkingEventType: EventType.ApplyDebuff,
+ referencedEventId: SPELLS.TEMPORAL_WOUND_DEBUFF.id,
+ referencedEventType: EventType.RemoveDebuff,
+ anyTarget: false,
+ maximumLinks: 1,
+ forwardBufferMs: BREATH_OF_EONS_DEBUFF_BUFFER,
+ },
{
linkRelation: PUPIL_OF_ALEXSTRASZA_LINK,
reverseLinkRelation: PUPIL_OF_ALEXSTRASZA_LINK,