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,