diff --git a/src/CHANGELOG.tsx b/src/CHANGELOG.tsx index 90755160c9a..dc9b1b9fe0c 100644 --- a/src/CHANGELOG.tsx +++ b/src/CHANGELOG.tsx @@ -37,6 +37,7 @@ import SpellLink from 'interface/SpellLink'; export default [ change(date(2024, 9, 9), 'Fix character search showing UNKNOWN instead of THE WAR WITHIN.', ToppleTheNun), change(date(2024, 9, 8), "Add Nerub'ar Palace bosses and season 1 M+ dungeons.", ToppleTheNun), + change(date(2024, 9, 6), "Add missing supportID property to DamageEvent interface", Vollmer), change(date(2024, 9, 5), 'Update Rogue spells for Classic Cataclysm', jazminite), change(date(2024, 9, 4), 'Updates to Active Time calculation to avoid cases where overcount could occur.', Sref), change(date(2024, 9, 3), "Fixed a bug where the Death Recap wouldn't detect some defensive buffs", Sref), diff --git a/src/analysis/retail/evoker/augmentation/CHANGELOG.tsx b/src/analysis/retail/evoker/augmentation/CHANGELOG.tsx index bff7963bae5..7d226368a89 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, 9, 6), <>Implement module, Vollmer), change(date(2024, 9, 6), <>Update MajorDefensive module for and , Vollmer), change(date(2024, 9, 6), <>Implement module, Vollmer), change(date(2024, 9, 6), <>Implement module, Vollmer), diff --git a/src/analysis/retail/evoker/augmentation/CombatLogParser.ts b/src/analysis/retail/evoker/augmentation/CombatLogParser.ts index 9fc79c70997..744328f50f0 100644 --- a/src/analysis/retail/evoker/augmentation/CombatLogParser.ts +++ b/src/analysis/retail/evoker/augmentation/CombatLogParser.ts @@ -63,6 +63,7 @@ import { ExtendedBattle, DivertedPower, UnrelentingSiege, + Wingleader, Slipstream, } from 'analysis/retail/evoker/shared'; @@ -131,6 +132,7 @@ class CombatLogParser extends MainCombatLogParser { extendedBattle: ExtendedBattle, divertedPower: DivertedPower, unrelentingSiege: UnrelentingSiege, + wingLeader: Wingleader, slipstream: Slipstream, // Features diff --git a/src/analysis/retail/evoker/devastation/CHANGELOG.tsx b/src/analysis/retail/evoker/devastation/CHANGELOG.tsx index 09892112ed1..e61718b33fa 100644 --- a/src/analysis/retail/evoker/devastation/CHANGELOG.tsx +++ b/src/analysis/retail/evoker/devastation/CHANGELOG.tsx @@ -5,6 +5,7 @@ import { TALENTS_EVOKER } from 'common/TALENTS/evoker'; import SPELLS from 'common/SPELLS/evoker'; export default [ + change(date(2024, 9, 6), <>Implement module, Vollmer), change(date(2024, 9, 6), <>Update MajorDefensive module for and , Vollmer), change(date(2024, 9, 6), <>Implement module, Vollmer), change(date(2024, 9, 6), <>Implement module, Vollmer), diff --git a/src/analysis/retail/evoker/devastation/CombatLogParser.ts b/src/analysis/retail/evoker/devastation/CombatLogParser.ts index bf55447c298..dc996c289c5 100644 --- a/src/analysis/retail/evoker/devastation/CombatLogParser.ts +++ b/src/analysis/retail/evoker/devastation/CombatLogParser.ts @@ -65,6 +65,7 @@ import { ExtendedBattle, DivertedPower, UnrelentingSiege, + Wingleader, Slipstream, } from 'analysis/retail/evoker/shared'; import ExpandedLungs from '../shared/modules/talents/hero/flameshaper/ExpandedLungs'; @@ -141,6 +142,7 @@ class CombatLogParser extends MainCombatLogParser { extendedBattle: ExtendedBattle, divertedPower: DivertedPower, unrelentingSiege: UnrelentingSiege, + wingLeader: Wingleader, slipstream: Slipstream, // core abilities diff --git a/src/analysis/retail/evoker/shared/constants.ts b/src/analysis/retail/evoker/shared/constants.ts index 9d8423d3b63..83281d8f3f5 100644 --- a/src/analysis/retail/evoker/shared/constants.ts +++ b/src/analysis/retail/evoker/shared/constants.ts @@ -84,3 +84,6 @@ export const HARDENED_SCALES_MITIGATION = 0.1; export const MASS_DISINTEGRATE_MULTIPLIER_PER_MISSING_TARGET = 0.15; export const MASS_ERUPTION_MULTIPLIER_PER_MISSING_TARGET = MASS_DISINTEGRATE_MULTIPLIER_PER_MISSING_TARGET; + +export const WINGLEADER_CDR_PER_HIT_MS = 1_000; +export const WINGLEADER_MAX_HITS = 3; diff --git a/src/analysis/retail/evoker/shared/index.ts b/src/analysis/retail/evoker/shared/index.ts index 0d5565a8f96..ce4d11aadbd 100644 --- a/src/analysis/retail/evoker/shared/index.ts +++ b/src/analysis/retail/evoker/shared/index.ts @@ -26,5 +26,6 @@ export { default as MightOfTheBlackDragonflight } from './modules/talents/hero/s export { default as ExtendedBattle } from './modules/talents/hero/scalecommander/ExtendedBattle'; export { default as DivertedPower } from './modules/talents/hero/scalecommander/DivertedPower'; export { default as UnrelentingSiege } from './modules/talents/hero/scalecommander/UnrelentingSiege'; +export { default as Wingleader } from './modules/talents/hero/scalecommander/Wingleader'; export { default as Slipstream } from './modules/talents/hero/scalecommander/Slipstream'; export * from './constants'; diff --git a/src/analysis/retail/evoker/shared/modules/talents/hero/scalecommander/Wingleader.tsx b/src/analysis/retail/evoker/shared/modules/talents/hero/scalecommander/Wingleader.tsx new file mode 100644 index 00000000000..3a1f7a23b0f --- /dev/null +++ b/src/analysis/retail/evoker/shared/modules/talents/hero/scalecommander/Wingleader.tsx @@ -0,0 +1,131 @@ +import Analyzer, { Options, SELECTED_PLAYER } from 'parser/core/Analyzer'; +import SPELLS from 'common/SPELLS/evoker'; +import TALENTS from 'common/TALENTS/evoker'; +import Events, { DamageEvent } from 'parser/core/Events'; +import { + WINGLEADER_CDR_PER_HIT_MS, + WINGLEADER_MAX_HITS, +} from 'analysis/retail/evoker/shared/constants'; +import SpellUsable from 'parser/shared/modules/SpellUsable'; +import SPECS from 'game/SPECS'; +import STATISTIC_ORDER from 'parser/ui/STATISTIC_ORDER'; +import Statistic from 'parser/ui/Statistic'; +import STATISTIC_CATEGORY from 'parser/ui/STATISTIC_CATEGORY'; +import SpellLink from 'interface/SpellLink'; +import DonutChart from 'parser/ui/DonutChart'; +import Spell from 'common/SPELLS/Spell'; + +const BUFFER = 50; + +/** Bombardments reduce the cooldown of Deep Breath by 1 sec for each target struck, + * up to 3 sec. */ +class Wingleader extends Analyzer { + static dependencies = { + spellUsable: SpellUsable, + }; + protected spellUsable!: SpellUsable; + + damageRecord: Record< + number, + { + initialTimestamp: number; + hits: number; + } + > = {}; + + effectiveCDR = 0; + wastedCDR = 0; + breathSpell: Spell; + + constructor(options: Options) { + super(options); + this.active = this.selectedCombatant.hasTalent(TALENTS.WINGLEADER_TALENT); + + this.breathSpell = + this.owner.selectedCombatant.specId === SPECS.DEVASTATION_EVOKER.id + ? SPELLS.DEEP_BREATH_SCALECOMMANDER + : SPELLS.BREATH_OF_EONS_SCALECOMMANDER; + + this.addEventListener( + Events.damage.by(SELECTED_PLAYER).spell(SPELLS.BOMBARDMENTS_DAMAGE), + this.onDamage, + ); + } + + onDamage(event: DamageEvent) { + /** When you proc Bombas yourself, you will get both the + * natty damage event and the supportID damage event. + * We only want to track one of them. + * We track the natty event since sometimes the supportID event doesn't + * show up */ + if (event.supportID === this.owner.selectedCombatant.id) { + return; + } + + const sourceId = event.supportID ?? this.owner.selectedCombatant.id; + + if (!this.damageRecord[sourceId]) { + this.damageRecord[sourceId] = { + initialTimestamp: event.timestamp, + hits: 0, + }; + } + + const record = this.damageRecord[sourceId]; + + if (event.timestamp - record.initialTimestamp > BUFFER) { + record.initialTimestamp = event.timestamp; + record.hits = 0; + } + + if (record.hits === WINGLEADER_MAX_HITS) { + return; + } + + record.hits += 1; + const effectiveCDR = this.spellUsable.reduceCooldown( + this.breathSpell.id, + WINGLEADER_CDR_PER_HIT_MS, + ); + const wastedCDR = WINGLEADER_CDR_PER_HIT_MS - effectiveCDR; + + this.effectiveCDR += effectiveCDR / 1000; + this.wastedCDR += wastedCDR / 1000; + } + + statistic() { + const donutItems = [ + { + color: 'rgb(123,188,93)', + label: 'Effective CDR', + valueTooltip: this.effectiveCDR.toFixed(2) + 's effective CDR', + value: this.effectiveCDR, + }, + { + color: 'rgb(216,59,59)', + label: 'Wasted CDR', + valueTooltip: + this.wastedCDR.toFixed(2) + `s CDR wasted whilst ${this.breathSpell.name} was ready`, + value: this.wastedCDR, + }, + ]; + + return ( + +
+ + CDR efficiency: + +
+
+ ); + } +} + +export default Wingleader; diff --git a/src/parser/core/Events.ts b/src/parser/core/Events.ts index d3254bd0fe8..5d17512e8d5 100644 --- a/src/parser/core/Events.ts +++ b/src/parser/core/Events.ts @@ -606,6 +606,7 @@ export interface DamageEvent extends Event { overkill?: number; blocked?: number; // does this exist? subtractsFromSupportedActor?: boolean; + supportID?: number; } export interface BuffEvent extends Event {