From 89a5051274d177d52908a169518956a9e62f18d4 Mon Sep 17 00:00:00 2001 From: Vollmer Date: Sun, 31 Dec 2023 21:01:26 +0100 Subject: [PATCH 1/9] Add resource initialization and add support for non rounded resources --- .../resourcetracker/ResourceTracker.ts | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/src/parser/shared/modules/resources/resourcetracker/ResourceTracker.ts b/src/parser/shared/modules/resources/resourcetracker/ResourceTracker.ts index 96468ec48c6..aff5ee8f36d 100644 --- a/src/parser/shared/modules/resources/resourcetracker/ResourceTracker.ts +++ b/src/parser/shared/modules/resources/resourcetracker/ResourceTracker.ts @@ -132,6 +132,8 @@ export default class ResourceTracker extends Analyzer { /** The maximum amount of the resource. * This is only used as a starting value - it will be updated from event's classResources field. */ maxResource!: number; + /** The amount of resources you start the fight with. */ + initialResources = 0; /** Resource's base regeneration rate, in units per second. This is the value before haste. * Leave as 0 for non-regenerating resources. @@ -157,6 +159,15 @@ export default class ResourceTracker extends Analyzer { * timestamp as a valid gain. */ allowMultipleGainsInSameTimestamp = false; + /** Instead of calculating resource as rounded numbers, use decimal precision for granularity. + * This is needed for specs that have resource generation that isn't provided in whole numbers + * eg. Evoker's Essence */ + useGranularity = false; + /** amount of decimals to use for granularity */ + granularity = 2; + /** If true, will adjust the resource amount to account for any mismatch between the previous + * update's current and the new current. */ + adjustResourceMismatch = false; // END override values /** Data object for the whole fight - updated during analysis */ @@ -405,11 +416,43 @@ export default class ResourceTracker extends Analyzer { prevUpdate && timestamp <= prevUpdate.timestamp + MULTI_UPDATE_BUFFER_MS; const beforeAmount = - reportedBeforeAmount !== undefined && !withinMultiUpdateBuffer + reportedBeforeAmount !== undefined && !withinMultiUpdateBuffer && !this.useGranularity ? reportedBeforeAmount : calculatedBeforeAmount; const current = Math.max(Math.min(max, beforeAmount + change), 0); // current is the after amount + /** There may be a discrepancy between the previous update's current value and + * the new current value due to the time elapsed since the last resource generation calculation. + * This discrepancy can cause issues when plotting the resource graph. + * + * For instance, if prevUpdate.current = 100, + * and beforeAmount = 110 with a change of -105, + * + * Ideally, the plot should show: 100 -> 110 -> 5 + * However, it displays: 100 -> 5, despite reporting a change of -105. + * + * To address this, we generate a new update using the beforeAmount as the current value. + */ + if ( + this.adjustResourceMismatch && + prevUpdate && + prevUpdate.current < beforeAmount && + type === 'spend' && + change < 0 + ) { + this._logAndPushUpdate({ + type: 'gain', + timestamp: timestamp - (timestamp - prevUpdate.timestamp) / 2, + change: beforeAmount - prevUpdate.current, + current: beforeAmount, + max, + rate: this.currentRegenRate, + rateWaste: 0, + changeWaste: 0, + atCap: false, + }); + } + // if our resource regenerates and the beforeAmount was capped, // then we were wasting resources due to natural regeneration let rateWaste = 0; @@ -520,7 +563,7 @@ export default class ResourceTracker extends Analyzer { const lastUpdate = this.resourceUpdates.at(-1); if (!lastUpdate) { // there have been no updates so far, return a default - return 0; // TODO make some resources default to max? + return this.initialResources; } if (lastUpdate.rate === 0) { // resource doesn't naturally regenerate, so return the last seen val @@ -528,7 +571,10 @@ export default class ResourceTracker extends Analyzer { } // resource naturally regenerates, estimate current based on last seen val const timePassedSeconds = (this.owner.currentTimestamp - lastUpdate.timestamp) / 1000; - const naturalGain = Math.round(timePassedSeconds * lastUpdate.rate); // whole number amount of resources pls + const naturalGain = this.useGranularity + ? parseFloat((timePassedSeconds * lastUpdate.rate).toFixed(this.granularity)) + : Math.round(timePassedSeconds * lastUpdate.rate); // whole number amount of resources pls + return Math.min(lastUpdate.max, lastUpdate.current + naturalGain); } From 2a87b3e63b09a34431f71a4850e3ea9e5d75cc4f Mon Sep 17 00:00:00 2001 From: Vollmer Date: Sun, 31 Dec 2023 21:17:55 +0100 Subject: [PATCH 2/9] Add SpellEssenceCost module --- .../retail/evoker/devastation/constants.tsx | 1 + src/analysis/retail/evoker/shared/index.ts | 1 + .../modules/core/essence/SpellEssenceCost.ts | 35 +++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 src/analysis/retail/evoker/shared/modules/core/essence/SpellEssenceCost.ts diff --git a/src/analysis/retail/evoker/devastation/constants.tsx b/src/analysis/retail/evoker/devastation/constants.tsx index dd82e6d7707..9927c1831f8 100644 --- a/src/analysis/retail/evoker/devastation/constants.tsx +++ b/src/analysis/retail/evoker/devastation/constants.tsx @@ -29,3 +29,4 @@ export const CAUSALITY_DISINTEGRATE_CDR_MS = 500; export const CAUSALITY_PYRE_CDR_MS = 400; export const DEVA_T31_2PC_MULTIPLER = 0.05; +export const DENSE_ENERGY_ESSENCE_REDUCTION = 1; diff --git a/src/analysis/retail/evoker/shared/index.ts b/src/analysis/retail/evoker/shared/index.ts index b578dbab964..9f341cffb1a 100644 --- a/src/analysis/retail/evoker/shared/index.ts +++ b/src/analysis/retail/evoker/shared/index.ts @@ -8,4 +8,5 @@ export { getWastedEssenceBurst, } from './modules/normalizers/LeapingFlamesNormalizer'; export { default as LeapingFlames } from './modules/talents/LeapingFlames'; +export { default as SpellEssenceCost } from './modules/core/essence/SpellEssenceCost'; export * from './constants'; diff --git a/src/analysis/retail/evoker/shared/modules/core/essence/SpellEssenceCost.ts b/src/analysis/retail/evoker/shared/modules/core/essence/SpellEssenceCost.ts new file mode 100644 index 00000000000..5791d535558 --- /dev/null +++ b/src/analysis/retail/evoker/shared/modules/core/essence/SpellEssenceCost.ts @@ -0,0 +1,35 @@ +import { Options } from 'parser/core/EventSubscriber'; +import SpellResourceCost from 'parser/shared/modules/SpellResourceCost'; +import TALENTS from 'common/TALENTS/evoker'; +import { CastEvent } from 'parser/core/Events'; +import RESOURCE_TYPES from 'game/RESOURCE_TYPES'; +import { DENSE_ENERGY_ESSENCE_REDUCTION } from 'analysis/retail/evoker/devastation/constants'; +import { VOLCANISM_ESSENCE_REDUCTION } from 'analysis/retail/evoker/augmentation/constants'; + +class SpellEssenceCost extends SpellResourceCost { + static resourceType = RESOURCE_TYPES.ESSENCE; + + hasDenseEnergy: boolean; + hasVolcanism: boolean; + + constructor(options: Options) { + super(options); + this.hasDenseEnergy = this.selectedCombatant.hasTalent(TALENTS.DENSE_ENERGY_TALENT); + this.hasVolcanism = this.selectedCombatant.hasTalent(TALENTS.VOLCANISM_TALENT); + } + + getResourceCost(event: CastEvent) { + let cost = super.getResourceCost(event); + + if (this.hasDenseEnergy && event.ability.guid === TALENTS.PYRE_TALENT.id) { + cost = Math.max(0, cost - DENSE_ENERGY_ESSENCE_REDUCTION); + } + if (this.hasVolcanism && event.ability.guid === TALENTS.ERUPTION_TALENT.id) { + cost = Math.max(0, cost - VOLCANISM_ESSENCE_REDUCTION); + } + + return cost; + } +} + +export default SpellEssenceCost; From f9c80d3e4c12a385beff8bce769fe510a912c44b Mon Sep 17 00:00:00 2001 From: Vollmer Date: Sun, 31 Dec 2023 21:30:06 +0100 Subject: [PATCH 3/9] Update EssenceTracker module to show proper values, moved to shared --- .../evoker/devastation/CombatLogParser.ts | 11 +- .../retail/evoker/devastation/constants.tsx | 2 + .../evoker/preservation/CombatLogParser.ts | 9 +- .../modules/features/EssenceTracker.ts | 79 ------------ src/analysis/retail/evoker/shared/index.ts | 1 + .../modules/core/essence/EssenceTracker.ts | 112 ++++++++++++++++++ 6 files changed, 130 insertions(+), 84 deletions(-) delete mode 100644 src/analysis/retail/evoker/preservation/modules/features/EssenceTracker.ts create mode 100644 src/analysis/retail/evoker/shared/modules/core/essence/EssenceTracker.ts diff --git a/src/analysis/retail/evoker/devastation/CombatLogParser.ts b/src/analysis/retail/evoker/devastation/CombatLogParser.ts index f9cdb880b65..bbfad1f3ed6 100644 --- a/src/analysis/retail/evoker/devastation/CombatLogParser.ts +++ b/src/analysis/retail/evoker/devastation/CombatLogParser.ts @@ -6,7 +6,6 @@ import ShatteringStar from './modules/abilities/ShatteringStar'; import Buffs from './modules/Buffs'; import Guide from './Guide'; import AplCheck from './modules/AplCheck'; -import EssenceTracker from '../preservation/modules/features/EssenceTracker'; import EssenceGraph from './modules/guide/EssenceGraph/EssenceGraph'; import Disintegrate from './modules/abilities/Disintegrate'; import EssenceBurst from './modules/abilities/EssenceBurst'; @@ -33,13 +32,20 @@ import Iridescence from './modules/talents/Iridescence'; import T31DevaTier from './modules/dragonflight/tier/T31DevaTier'; // Shared -import { LeapingFlamesNormalizer, LeapingFlames } from 'analysis/retail/evoker/shared'; +import { + LeapingFlamesNormalizer, + LeapingFlames, + SpellEssenceCost, + EssenceTracker, +} from 'analysis/retail/evoker/shared'; class CombatLogParser extends MainCombatLogParser { static specModules = { // Shared leapingFlamesNormalizer: LeapingFlamesNormalizer, leapingFlames: LeapingFlames, + spellEssenceCost: SpellEssenceCost, + essenceTracker: EssenceTracker, // Core abilities: Abilities, @@ -50,7 +56,6 @@ class CombatLogParser extends MainCombatLogParser { essenceBurstNormalizer: EssenceBurstNormalizer, // features - essenceTracker: EssenceTracker, essenceGraph: EssenceGraph, apls: AplCheck, cooldownThroughputTracker: CooldownThroughputTracker, diff --git a/src/analysis/retail/evoker/devastation/constants.tsx b/src/analysis/retail/evoker/devastation/constants.tsx index 9927c1831f8..3be125fe9e0 100644 --- a/src/analysis/retail/evoker/devastation/constants.tsx +++ b/src/analysis/retail/evoker/devastation/constants.tsx @@ -29,4 +29,6 @@ export const CAUSALITY_DISINTEGRATE_CDR_MS = 500; export const CAUSALITY_PYRE_CDR_MS = 400; export const DEVA_T31_2PC_MULTIPLER = 0.05; + +export const POWER_SWELL_REGEN_FACTOR = 1; export const DENSE_ENERGY_ESSENCE_REDUCTION = 1; diff --git a/src/analysis/retail/evoker/preservation/CombatLogParser.ts b/src/analysis/retail/evoker/preservation/CombatLogParser.ts index 02ae3b0bfc9..e0005d4ddc9 100644 --- a/src/analysis/retail/evoker/preservation/CombatLogParser.ts +++ b/src/analysis/retail/evoker/preservation/CombatLogParser.ts @@ -17,7 +17,6 @@ import HotRemovalNormalizer from './normalizers/HotRemovalNormalizer'; import Checklist from 'analysis/retail/evoker/preservation/modules/features/Checklist/Module'; import EssenceDetails from './modules/features/EssenceDetails'; -import EssenceTracker from './modules/features/EssenceTracker'; import GracePeriod from './modules/talents/GracePeriod'; import Reversion from './modules/talents/Reversion'; import CallOfYsera from './modules/talents/CallOfYsera'; @@ -48,7 +47,12 @@ import RegenerativeMagic from '../shared/modules/talents/RegenerativeMagic'; import AncientFlame from './modules/talents/AncientFlame'; import T31PrevokerSet from './modules/dragonflight/tier/T31TierSet'; import EchoTypeBreakdown from './modules/talents/EchoTypeBreakdown'; -import { LeapingFlamesNormalizer, LeapingFlames } from '../shared'; +import { + LeapingFlamesNormalizer, + LeapingFlames, + SpellEssenceCost, + EssenceTracker, +} from '../shared'; class CombatLogParser extends CoreCombatLogParser { static specModules = { @@ -68,6 +72,7 @@ class CombatLogParser extends CoreCombatLogParser { //resources essenceTracker: EssenceTracker, essenceDetails: EssenceDetails, + spellEssenceCost: SpellEssenceCost, manaTracker: ManaTracker, //features diff --git a/src/analysis/retail/evoker/preservation/modules/features/EssenceTracker.ts b/src/analysis/retail/evoker/preservation/modules/features/EssenceTracker.ts deleted file mode 100644 index 43c5e353f09..00000000000 --- a/src/analysis/retail/evoker/preservation/modules/features/EssenceTracker.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - EMPATH_REGEN_FACTOR, - FLOW_STATE_FACTOR, -} from 'analysis/retail/evoker/preservation/constants'; -import { TALENTS_EVOKER } from 'common/TALENTS'; -import RESOURCE_TYPES from 'game/RESOURCE_TYPES'; -import { Options, SELECTED_PLAYER } from 'parser/core/Analyzer'; -import Events, { ApplyBuffEvent, CastEvent, RemoveBuffEvent } from 'parser/core/Events'; -import ResourceTracker from 'parser/shared/modules/resources/resourcetracker/ResourceTracker'; -import { - BASE_ESSENCE_REGEN, - BASE_MAX_ESSENCE, - INNATE_MAGIC_REGEN, -} from 'analysis/retail/evoker/shared/constants'; - -class EssenceTracker extends ResourceTracker { - static dependencies = { - ...ResourceTracker.dependencies, - }; - - constructor(options: Options) { - super(options); - this.resource = RESOURCE_TYPES.ESSENCE; - this.maxResource = - BASE_MAX_ESSENCE + - (this.selectedCombatant.hasTalent(TALENTS_EVOKER.FONT_OF_MAGIC_PRESERVATION_TALENT) ? 1 : 0); - this.baseRegenRate = - BASE_ESSENCE_REGEN * - (1 + - INNATE_MAGIC_REGEN * - this.selectedCombatant.getTalentRank(TALENTS_EVOKER.INNATE_MAGIC_TALENT)); - this.addEventListener( - Events.applybuff - .by(SELECTED_PLAYER) - .spell([TALENTS_EVOKER.EMPATH_TALENT, TALENTS_EVOKER.FLOW_STATE_TALENT]), - this.increaseEssenceRegen, - ); - this.addEventListener( - Events.removebuff - .by(SELECTED_PLAYER) - .spell([TALENTS_EVOKER.EMPATH_TALENT, TALENTS_EVOKER.FLOW_STATE_TALENT]), - this.decreaseEssenceRegen, - ); - } - - increaseEssenceRegen(event: ApplyBuffEvent) { - const spellId = event.ability.guid; - //the triggerRateChange function dynamically updates baseRegenRate - let newRate = this.currentRegenRate; - if (spellId === TALENTS_EVOKER.EMPATH_TALENT.id) { - newRate *= 1 + EMPATH_REGEN_FACTOR; - } else if (spellId === TALENTS_EVOKER.FLOW_STATE_TALENT.id) { - newRate *= 1 + FLOW_STATE_FACTOR; - } - this.triggerRateChange(newRate); - } - - decreaseEssenceRegen(event: RemoveBuffEvent) { - const spellId = event.ability.guid; - let newRate = this.currentRegenRate; - if (spellId === TALENTS_EVOKER.EMPATH_TALENT.id) { - newRate /= 1 + EMPATH_REGEN_FACTOR; - } else if (spellId === TALENTS_EVOKER.FLOW_STATE_TALENT.id) { - newRate /= 1 + FLOW_STATE_FACTOR; - } - this.triggerRateChange(newRate); - } - - getAdjustedCost(event: CastEvent) { - const cost = this.getResource(event)?.cost; - if (!cost) { - this._applySpender(event, 0); - return 0; - } - return cost; - } -} - -export default EssenceTracker; diff --git a/src/analysis/retail/evoker/shared/index.ts b/src/analysis/retail/evoker/shared/index.ts index 9f341cffb1a..bfd893fd873 100644 --- a/src/analysis/retail/evoker/shared/index.ts +++ b/src/analysis/retail/evoker/shared/index.ts @@ -9,4 +9,5 @@ export { } from './modules/normalizers/LeapingFlamesNormalizer'; export { default as LeapingFlames } from './modules/talents/LeapingFlames'; export { default as SpellEssenceCost } from './modules/core/essence/SpellEssenceCost'; +export { default as EssenceTracker } from './modules/core/essence/EssenceTracker'; export * from './constants'; diff --git a/src/analysis/retail/evoker/shared/modules/core/essence/EssenceTracker.ts b/src/analysis/retail/evoker/shared/modules/core/essence/EssenceTracker.ts new file mode 100644 index 00000000000..1b5c2b745e7 --- /dev/null +++ b/src/analysis/retail/evoker/shared/modules/core/essence/EssenceTracker.ts @@ -0,0 +1,112 @@ +import { + EMPATH_REGEN_FACTOR, + FLOW_STATE_FACTOR, +} from 'analysis/retail/evoker/preservation/constants'; +import { TALENTS_EVOKER } from 'common/TALENTS'; +import SPELLS from 'common/SPELLS/evoker'; +import RESOURCE_TYPES from 'game/RESOURCE_TYPES'; +import { Options, SELECTED_PLAYER } from 'parser/core/Analyzer'; +import Events, { ApplyBuffEvent, CastEvent, RemoveBuffEvent } from 'parser/core/Events'; +import ResourceTracker from 'parser/shared/modules/resources/resourcetracker/ResourceTracker'; +import { + BASE_ESSENCE_REGEN, + BASE_MAX_ESSENCE, + INNATE_MAGIC_REGEN, +} from 'analysis/retail/evoker/shared/constants'; +import { POWER_SWELL_REGEN_FACTOR } from 'analysis/retail/evoker/devastation/constants'; +import SpellEssenceCost from './SpellEssenceCost'; + +const REGEN_BUFFS = { + [TALENTS_EVOKER.EMPATH_TALENT.id]: { + spell: TALENTS_EVOKER.EMPATH_TALENT, + regenFactor: EMPATH_REGEN_FACTOR, + }, + [TALENTS_EVOKER.FLOW_STATE_TALENT.id]: { + spell: TALENTS_EVOKER.FLOW_STATE_TALENT, + regenFactor: FLOW_STATE_FACTOR, + }, + [SPELLS.POWER_SWELL_BUFF.id]: { + spell: SPELLS.POWER_SWELL_BUFF, + regenFactor: POWER_SWELL_REGEN_FACTOR, + }, +}; + +class EssenceTracker extends ResourceTracker { + static dependencies = { + ...ResourceTracker.dependencies, + spellEssenceCost: SpellEssenceCost, + }; + protected spellEssenceCost!: SpellEssenceCost; + + constructor(options: Options) { + super(options); + this.resource = RESOURCE_TYPES.ESSENCE; + this.maxResource = + BASE_MAX_ESSENCE + + (this.selectedCombatant.hasTalent(TALENTS_EVOKER.POWER_NEXUS_TALENT) ? 1 : 0); + this.initialResources = this.maxResource; + + this.baseRegenRate = + BASE_ESSENCE_REGEN * + (1 + + INNATE_MAGIC_REGEN * + this.selectedCombatant.getTalentRank(TALENTS_EVOKER.INNATE_MAGIC_TALENT)); + + this.allowMultipleGainsInSameTimestamp = true; + this.useGranularity = true; + this.adjustResourceMismatch = true; + + const regenSpells = Object.entries(REGEN_BUFFS).map(([, regenBuff]) => regenBuff.spell); + + this.addEventListener( + Events.applybuff.by(SELECTED_PLAYER).spell(regenSpells), + this.increaseEssenceRegen, + ); + this.addEventListener( + Events.removebuff.by(SELECTED_PLAYER).spell(regenSpells), + this.decreaseEssenceRegen, + ); + } + + increaseEssenceRegen(event: ApplyBuffEvent) { + const regenRate = REGEN_BUFFS[event.ability.guid].regenFactor; + const newRate = (this.baseRegenRate *= 1 + regenRate); + + this.triggerRateChange(newRate); + } + + decreaseEssenceRegen(event: RemoveBuffEvent) { + const regenRate = REGEN_BUFFS[event.ability.guid].regenFactor; + const newRate = (this.baseRegenRate /= 1 + regenRate); + + this.triggerRateChange(newRate); + } + + getAdjustedCost(event: CastEvent) { + const cost = this.spellEssenceCost.getResourceCost(event); + + if (!cost) { + this._applySpender(event, 0); + return 0; + } + + return cost; + } + + onCast(event: CastEvent) { + /* Early return as to not count Prescience casts before the fight starts + * that Augmentation might have when setting up T31 4pc buff. */ + if ( + event.timestamp < this.owner.fight.start_time && + event.ability.guid === TALENTS_EVOKER.PRESCIENCE_TALENT.id + ) { + return; + } + const cost = this.getAdjustedCost(event); + if (cost) { + this._applySpender(event, cost, this.getResource(event)); + } + } +} + +export default EssenceTracker; From 115b114c844944345e69ad6ceaa9de56666ac0d8 Mon Sep 17 00:00:00 2001 From: Vollmer Date: Sun, 31 Dec 2023 21:32:29 +0100 Subject: [PATCH 4/9] Make EssenceGraph shared --- src/analysis/retail/evoker/devastation/CombatLogParser.ts | 4 ++-- src/analysis/retail/evoker/shared/index.ts | 1 + .../modules/core/essence/EssenceGraph.ts} | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) rename src/analysis/retail/evoker/{devastation/modules/guide/EssenceGraph/EssenceGraph.tsx => shared/modules/core/essence/EssenceGraph.ts} (79%) diff --git a/src/analysis/retail/evoker/devastation/CombatLogParser.ts b/src/analysis/retail/evoker/devastation/CombatLogParser.ts index bbfad1f3ed6..b1f441a43bc 100644 --- a/src/analysis/retail/evoker/devastation/CombatLogParser.ts +++ b/src/analysis/retail/evoker/devastation/CombatLogParser.ts @@ -6,7 +6,6 @@ import ShatteringStar from './modules/abilities/ShatteringStar'; import Buffs from './modules/Buffs'; import Guide from './Guide'; import AplCheck from './modules/AplCheck'; -import EssenceGraph from './modules/guide/EssenceGraph/EssenceGraph'; import Disintegrate from './modules/abilities/Disintegrate'; import EssenceBurst from './modules/abilities/EssenceBurst'; import Burnout from './modules/abilities/Burnout'; @@ -37,6 +36,7 @@ import { LeapingFlames, SpellEssenceCost, EssenceTracker, + EssenceGraph, } from 'analysis/retail/evoker/shared'; class CombatLogParser extends MainCombatLogParser { @@ -46,6 +46,7 @@ class CombatLogParser extends MainCombatLogParser { leapingFlames: LeapingFlames, spellEssenceCost: SpellEssenceCost, essenceTracker: EssenceTracker, + essenceGraph: EssenceGraph, // Core abilities: Abilities, @@ -56,7 +57,6 @@ class CombatLogParser extends MainCombatLogParser { essenceBurstNormalizer: EssenceBurstNormalizer, // features - essenceGraph: EssenceGraph, apls: AplCheck, cooldownThroughputTracker: CooldownThroughputTracker, diff --git a/src/analysis/retail/evoker/shared/index.ts b/src/analysis/retail/evoker/shared/index.ts index bfd893fd873..e5c723ec367 100644 --- a/src/analysis/retail/evoker/shared/index.ts +++ b/src/analysis/retail/evoker/shared/index.ts @@ -10,4 +10,5 @@ export { export { default as LeapingFlames } from './modules/talents/LeapingFlames'; export { default as SpellEssenceCost } from './modules/core/essence/SpellEssenceCost'; export { default as EssenceTracker } from './modules/core/essence/EssenceTracker'; +export { default as EssenceGraph } from './modules/core/essence/EssenceGraph'; export * from './constants'; diff --git a/src/analysis/retail/evoker/devastation/modules/guide/EssenceGraph/EssenceGraph.tsx b/src/analysis/retail/evoker/shared/modules/core/essence/EssenceGraph.ts similarity index 79% rename from src/analysis/retail/evoker/devastation/modules/guide/EssenceGraph/EssenceGraph.tsx rename to src/analysis/retail/evoker/shared/modules/core/essence/EssenceGraph.ts index c4131361486..738e6cf6bf6 100644 --- a/src/analysis/retail/evoker/devastation/modules/guide/EssenceGraph/EssenceGraph.tsx +++ b/src/analysis/retail/evoker/shared/modules/core/essence/EssenceGraph.ts @@ -1,4 +1,4 @@ -import EssenceTracker from 'analysis/retail/evoker/preservation/modules/features/EssenceTracker'; +import EssenceTracker from 'analysis/retail/evoker/shared/modules/core/essence/EssenceTracker'; import ResourceGraph from 'parser/shared/modules/ResourceGraph'; class EssenceGraph extends ResourceGraph { From 1ee77dd0c42708e0e1e39d173d4873ceaa828714 Mon Sep 17 00:00:00 2001 From: Vollmer Date: Sun, 31 Dec 2023 21:33:22 +0100 Subject: [PATCH 5/9] Refactor EssenceGraphSection for Devastation --- .../retail/evoker/devastation/Guide.tsx | 4 +- .../EssenceGraph/EssenceGraphSection.tsx | 17 ----- .../modules/guide/EssenceGraphSection.tsx | 73 +++++++++++++++++++ .../modules/guide/PerformancePercentage.tsx | 45 ++++++++++++ 4 files changed, 120 insertions(+), 19 deletions(-) delete mode 100644 src/analysis/retail/evoker/devastation/modules/guide/EssenceGraph/EssenceGraphSection.tsx create mode 100644 src/analysis/retail/evoker/devastation/modules/guide/EssenceGraphSection.tsx create mode 100644 src/analysis/retail/evoker/devastation/modules/guide/PerformancePercentage.tsx diff --git a/src/analysis/retail/evoker/devastation/Guide.tsx b/src/analysis/retail/evoker/devastation/Guide.tsx index c8611aee995..e1fc92895cb 100644 --- a/src/analysis/retail/evoker/devastation/Guide.tsx +++ b/src/analysis/retail/evoker/devastation/Guide.tsx @@ -4,7 +4,7 @@ import PreparationSection from 'interface/guide/components/Preparation/Preparati import { CooldownSection } from './modules/guide/Cooldown'; import { CoreRotation } from './modules/guide/CoreRotation'; import { DamageEfficiency } from './modules/guide/DamageEfficiencySection'; -import { EssenceGraphSection } from './modules/guide/EssenceGraph/EssenceGraphSection'; +import { EssenceGraphSection } from './modules/guide/EssenceGraphSection'; import { DragonRageSection } from './modules/guide/DragonRageSection'; import { IntroSection } from './modules/guide/IntroSection'; @@ -12,10 +12,10 @@ export default function Guide({ modules, events, info }: GuideProps + - diff --git a/src/analysis/retail/evoker/devastation/modules/guide/EssenceGraph/EssenceGraphSection.tsx b/src/analysis/retail/evoker/devastation/modules/guide/EssenceGraph/EssenceGraphSection.tsx deleted file mode 100644 index 2d8eb508195..00000000000 --- a/src/analysis/retail/evoker/devastation/modules/guide/EssenceGraph/EssenceGraphSection.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import CombatLogParser from '../../../CombatLogParser'; -import { GuideProps, Section } from 'interface/guide'; -import { formatPercentage } from 'common/format'; - -export function EssenceGraphSection({ modules, events, info }: GuideProps) { - return ( -
-

- This documents your Essence level over time. You shouldn't have long windows of overcapped - essence except in some unique cases in Dragon Rage. You were capped on Essence for{' '} - {formatPercentage(modules.essenceTracker.percentAtCap, 1)}% of the - encounter. -

- {modules.essenceGraph.plot} -
- ); -} diff --git a/src/analysis/retail/evoker/devastation/modules/guide/EssenceGraphSection.tsx b/src/analysis/retail/evoker/devastation/modules/guide/EssenceGraphSection.tsx new file mode 100644 index 00000000000..5a90058224e --- /dev/null +++ b/src/analysis/retail/evoker/devastation/modules/guide/EssenceGraphSection.tsx @@ -0,0 +1,73 @@ +import CombatLogParser from '../../CombatLogParser'; +import { GuideProps, Section } from 'interface/guide'; +import { QualitativePerformance } from 'parser/ui/QualitativePerformance'; +import PerformancePercentage from './PerformancePercentage'; +import { ResourceLink, SpellLink, Tooltip } from 'interface'; +import RESOURCE_TYPES from 'game/RESOURCE_TYPES'; +import { TIERS } from 'game/TIERS'; +import { InformationIcon } from 'interface/icons'; +import SPELLS from 'common/SPELLS/evoker'; + +export function EssenceGraphSection({ modules, events, info }: GuideProps) { + const hasT31 = info.combatant.has4PieceByTier(TIERS.T31); + const percentAtCap = modules.essenceTracker.percentAtCap; + const essenceWasted = modules.essenceTracker.wasted; + + const perfectTimeAtEssenceCap = 0.1 + (hasT31 ? 0.05 : 0); + const goodTimeAtEssenceCap = 0.15 + (hasT31 ? 0.05 : 0); + const okTimeAtEssenceCap = 0.2 + (hasT31 ? 0.05 : 0); + + const percentAtCapPerformance = + percentAtCap <= perfectTimeAtEssenceCap + ? QualitativePerformance.Perfect + : percentAtCap <= goodTimeAtEssenceCap + ? QualitativePerformance.Good + : percentAtCap <= okTimeAtEssenceCap + ? QualitativePerformance.Ok + : QualitativePerformance.Fail; + + return ( +
+

+ Your primary resource is . You should avoid + overcapping - lost{' '} + generation is lost DPS. Sometimes it will be + impossible to avoid overcapping - due to + handling mechanics, high rolling procs + or during intermission phases. +

+

+ The chart below shows your over the course + of the encounter. You wasted{' '} + {' '} + of your . + {hasT31 && ( + <> + {' '} + + Since you have T31 4pc an extra 5% grace period is added, as it is expected that + more will go to waste, due to the + extra amount of generated. + + } + > +

+ +
+ + + )} +

+ {modules.essenceGraph.plot} +
+ ); +} diff --git a/src/analysis/retail/evoker/devastation/modules/guide/PerformancePercentage.tsx b/src/analysis/retail/evoker/devastation/modules/guide/PerformancePercentage.tsx new file mode 100644 index 00000000000..f0447413e6c --- /dev/null +++ b/src/analysis/retail/evoker/devastation/modules/guide/PerformancePercentage.tsx @@ -0,0 +1,45 @@ +import { PerformanceMark } from 'interface/guide'; +import { QualitativePerformance } from 'parser/ui/QualitativePerformance'; +import { formatNumber, formatPercentage } from 'common/format'; +import PerformanceStrongWithTooltip from 'interface/PerformanceStrongWithTooltip'; + +interface Props { + performance: QualitativePerformance; + perfectPercentage: number; + goodPercentage: number; + okPercentage: number; + percentage: number; + flatAmount: number; +} +const PerformancePercentage = ({ + performance, + perfectPercentage, + goodPercentage, + okPercentage, + percentage, + flatAmount, +}: Props) => { + const perfectSign = perfectPercentage > 0 ? '<=' : '='; + + return ( + + Perfect usage {perfectSign}{' '} + {formatPercentage(perfectPercentage, 0)}% +
+ Good usage <={' '} + {formatPercentage(goodPercentage, 0)}% +
+ OK usage <={' '} + {formatPercentage(okPercentage, 0)}% + + } + > + {formatNumber(flatAmount)} ({formatPercentage(percentage)}%) +
+ ); +}; + +export default PerformancePercentage; From ca378c6615afa0d6e9dd265d0c37ea80b6812526 Mon Sep 17 00:00:00 2001 From: Vollmer Date: Sun, 31 Dec 2023 21:34:05 +0100 Subject: [PATCH 6/9] Implement EssenceGraphSection for Augmentation --- .../evoker/augmentation/CombatLogParser.ts | 11 +++- .../modules/guide/CoreRotation.tsx | 54 ++++++++++++++++++- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/analysis/retail/evoker/augmentation/CombatLogParser.ts b/src/analysis/retail/evoker/augmentation/CombatLogParser.ts index 49582bb1561..0d41e3e1807 100644 --- a/src/analysis/retail/evoker/augmentation/CombatLogParser.ts +++ b/src/analysis/retail/evoker/augmentation/CombatLogParser.ts @@ -35,13 +35,22 @@ import EbonMightNormalizer from './modules/normalizers/EbonMightNormalizer'; import T31Augmentation4P from './modules/dragonflight/T31Augmentation4P'; //Shared -import { LeapingFlamesNormalizer, LeapingFlames } from 'analysis/retail/evoker/shared'; +import { + LeapingFlamesNormalizer, + LeapingFlames, + SpellEssenceCost, + EssenceTracker, + EssenceGraph, +} from 'analysis/retail/evoker/shared'; class CombatLogParser extends MainCombatLogParser { static specModules = { // Shared leapingFlamesNormalizer: LeapingFlamesNormalizer, leapingFlames: LeapingFlames, + spellEssenceCost: SpellEssenceCost, + essenceTracker: EssenceTracker, + essenceGraph: EssenceGraph, // Normalizers castLinkNormalizer: CastLinkNormalizer, diff --git a/src/analysis/retail/evoker/augmentation/modules/guide/CoreRotation.tsx b/src/analysis/retail/evoker/augmentation/modules/guide/CoreRotation.tsx index 53cb7479e92..3dd3ed5217c 100644 --- a/src/analysis/retail/evoker/augmentation/modules/guide/CoreRotation.tsx +++ b/src/analysis/retail/evoker/augmentation/modules/guide/CoreRotation.tsx @@ -2,13 +2,16 @@ import { GuideProps, Section, SubSection } from 'interface/guide'; import { TALENTS_EVOKER } from 'common/TALENTS'; import SPELLS from 'common/SPELLS'; import CombatLogParser from '../../CombatLogParser'; -import { SpellLink } from 'interface'; +import { ResourceLink, SpellLink } from 'interface'; import HideExplanationsToggle from 'interface/guide/components/HideExplanationsToggle'; import HideGoodCastsToggle from 'interface/guide/components/HideGoodCastsToggle'; import { RoundedPanel } from 'interface/guide/components/GuideDivs'; import ExplanationRow from 'interface/guide/components/ExplanationRow'; import Explanation from 'interface/guide/components/Explanation'; import CooldownUsage from 'parser/core/MajorCooldowns/CooldownUsage'; +import { QualitativePerformance } from 'parser/ui/QualitativePerformance'; +import RESOURCE_TYPES from 'game/RESOURCE_TYPES'; +import PerformancePercentage from 'analysis/retail/evoker/devastation/modules/guide/PerformancePercentage'; export function CoreRotationSection({ modules, events, info }: GuideProps) { return ( @@ -34,6 +37,8 @@ export function CoreRotationSection({ modules, events, info }: GuideProps + + This graph shows how many Ebon Mights and Presciences you had active over the course of the encounter, with rule lines showing when you used Breath of Eon. Use this to ensure that you @@ -56,7 +61,7 @@ export function CoreRotationSection({ modules, events, info }: GuideProps) { +function BlisteringScalesSection({ modules }: GuideProps) { return ( @@ -83,3 +88,48 @@ function BlisteringScalesSection({ modules, events, info }: GuideProps ); } + +function EssenceGraphSection({ modules, events, info }: GuideProps) { + const percentAtCap = modules.essenceTracker.percentAtCap; + const essenceWasted = modules.essenceTracker.wasted; + + const perfectTimeAtEssenceCap = 0.1; + const goodTimeAtEssenceCap = 0.15; + const okTimeAtEssenceCap = 0.2; + + const percentAtCapPerformance = + percentAtCap <= perfectTimeAtEssenceCap + ? QualitativePerformance.Perfect + : percentAtCap <= goodTimeAtEssenceCap + ? QualitativePerformance.Good + : percentAtCap <= okTimeAtEssenceCap + ? QualitativePerformance.Ok + : QualitativePerformance.Fail; + + return ( + +

+ Your primary resource is . You should avoid + overcapping - lost{' '} + generation is lost DPS. Sometimes it will be + impossible to avoid overcapping - due to + handling mechanics, high rolling procs + or during intermission phases. +

+

+ The chart below shows your over the course + of the encounter. You wasted{' '} + {' '} + of your . +

+ {modules.essenceGraph.plot} +
+ ); +} From 84ea9cc6e2380a77468ba6534767bb6cdec05b66 Mon Sep 17 00:00:00 2001 From: Vollmer Date: Sun, 31 Dec 2023 21:35:50 +0100 Subject: [PATCH 7/9] Refactor EssenceDetails.tsx: Import EssenceTracker from shared module --- .../evoker/preservation/modules/features/EssenceDetails.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analysis/retail/evoker/preservation/modules/features/EssenceDetails.tsx b/src/analysis/retail/evoker/preservation/modules/features/EssenceDetails.tsx index 84eb28b58ef..153cdd7f784 100644 --- a/src/analysis/retail/evoker/preservation/modules/features/EssenceDetails.tsx +++ b/src/analysis/retail/evoker/preservation/modules/features/EssenceDetails.tsx @@ -6,10 +6,10 @@ import STATISTIC_ORDER from 'parser/ui/STATISTIC_ORDER'; import ResourceBreakdown from 'parser/shared/modules/resources/resourcetracker/ResourceBreakdown'; import { Panel } from 'interface'; -import EssenceTracker from './EssenceTracker'; import { defineMessage } from '@lingui/macro'; import { ThresholdStyle, When } from 'parser/core/ParseResults'; import { TALENTS_EVOKER } from 'common/TALENTS'; +import { EssenceTracker } from 'analysis/retail/evoker/shared'; class EssenceDetails extends Analyzer { static dependencies = { From 6a914693a514d68b8696dbc8768ccb39f4d6e700 Mon Sep 17 00:00:00 2001 From: Vollmer Date: Sun, 31 Dec 2023 22:00:33 +0100 Subject: [PATCH 8/9] changelogs --- src/CHANGELOG.tsx | 1 + src/analysis/retail/evoker/augmentation/CHANGELOG.tsx | 4 +++- src/analysis/retail/evoker/devastation/CHANGELOG.tsx | 4 +++- src/analysis/retail/evoker/preservation/CHANGELOG.tsx | 6 ++++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/CHANGELOG.tsx b/src/CHANGELOG.tsx index 152ca0363d5..8ce2e432c7f 100644 --- a/src/CHANGELOG.tsx +++ b/src/CHANGELOG.tsx @@ -32,6 +32,7 @@ import SpellLink from 'interface/SpellLink'; // prettier-ignore export default [ + change(date(2023, 12, 31), <>Add resource initialization and granularity support for ResourceTracker module., Vollmer), change(date(2023, 12, 30), 'Fix errors caused by ESLint update.', ToppleTheNun), change(date(2023, 12, 28), 'Improve internal handling of phases', emallson), change(date(2023, 12, 21), <>Mark as a max rank enchant., ToppleTheNun), diff --git a/src/analysis/retail/evoker/augmentation/CHANGELOG.tsx b/src/analysis/retail/evoker/augmentation/CHANGELOG.tsx index 7a2321840ac..508aaf3ee09 100644 --- a/src/analysis/retail/evoker/augmentation/CHANGELOG.tsx +++ b/src/analysis/retail/evoker/augmentation/CHANGELOG.tsx @@ -1,10 +1,12 @@ import { change, date } from 'common/changelog'; import { Pants, Vollmer } from 'CONTRIBUTORS'; -import { SpellLink } from 'interface'; +import { ResourceLink, SpellLink } from 'interface'; import TALENTS from 'common/TALENTS/evoker'; import SPELLS from 'common/SPELLS/evoker'; +import RESOURCE_TYPES from 'game/RESOURCE_TYPES'; export default [ + change(date(2023, 12, 31), <>Implemented Graph., Vollmer), change(date(2023, 12, 22), <>Update blacklist for Helpers to increase accuracy., Vollmer), change(date(2023, 12, 13), <>Added Boss filter button for Buff Helper, and improved loading speed., Vollmer), change(date(2023, 12, 7), <>Update blacklist for Helpers to increase accuracy., Vollmer), diff --git a/src/analysis/retail/evoker/devastation/CHANGELOG.tsx b/src/analysis/retail/evoker/devastation/CHANGELOG.tsx index f8e999135b1..deb60f47ea2 100644 --- a/src/analysis/retail/evoker/devastation/CHANGELOG.tsx +++ b/src/analysis/retail/evoker/devastation/CHANGELOG.tsx @@ -1,10 +1,12 @@ import { change, date } from 'common/changelog'; import { ToppleTheNun, Tyndi, Vireve, Vollmer } from 'CONTRIBUTORS'; -import { SpellLink } from 'interface'; +import { ResourceLink, SpellLink } from 'interface'; import TALENTS from 'common/TALENTS/evoker'; import SPELLS from 'common/SPELLS/evoker'; +import RESOURCE_TYPES from 'game/RESOURCE_TYPES'; export default [ + change(date(2023, 12, 31), <>Improved tracking of for a more precise representation on the Graph., Vollmer), change(date(2023, 12, 6), <>Update APL Check., Vollmer), change(date(2023, 11, 30), <>Update guide section., Vollmer), change(date(2023, 11, 12), <>Properly track dropped ticks when only 1 tick is remaining for graph., Vollmer), diff --git a/src/analysis/retail/evoker/preservation/CHANGELOG.tsx b/src/analysis/retail/evoker/preservation/CHANGELOG.tsx index 67613f45a4d..0d86b8c4d90 100644 --- a/src/analysis/retail/evoker/preservation/CHANGELOG.tsx +++ b/src/analysis/retail/evoker/preservation/CHANGELOG.tsx @@ -1,10 +1,12 @@ import { change, date } from 'common/changelog'; import SPELLS from 'common/SPELLS'; import { TALENTS_EVOKER } from 'common/TALENTS'; -import { ToppleTheNun, Trevor, Tyndi, Vohrr } from 'CONTRIBUTORS'; -import { SpellLink } from 'interface'; +import { ToppleTheNun, Trevor, Tyndi, Vohrr, Vollmer } from 'CONTRIBUTORS'; +import RESOURCE_TYPES from 'game/RESOURCE_TYPES'; +import { ResourceLink, SpellLink } from 'interface'; export default [ + change(date(2023, 12, 31), <>Improved tracking of for a more precise representation of overcapped ., Vollmer), change(date(2023, 11, 25), <>Fixed bug in module, Trevor), change(date(2023, 11, 24), <>Add chart to T31 tier set 4PC module, Trevor), change(date(2023, 11, 11), <>Add graphic to guide, Trevor), From d53fc36a963f0f28aae944e71a563ea2805e893a Mon Sep 17 00:00:00 2001 From: Vollmer Date: Mon, 8 Jan 2024 00:16:02 +0100 Subject: [PATCH 9/9] Add comment explaining beforeAmount evaluation --- .../shared/modules/resources/resourcetracker/ResourceTracker.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/parser/shared/modules/resources/resourcetracker/ResourceTracker.ts b/src/parser/shared/modules/resources/resourcetracker/ResourceTracker.ts index aff5ee8f36d..1810c51de45 100644 --- a/src/parser/shared/modules/resources/resourcetracker/ResourceTracker.ts +++ b/src/parser/shared/modules/resources/resourcetracker/ResourceTracker.ts @@ -415,6 +415,8 @@ export default class ResourceTracker extends Analyzer { !this.allowMultipleGainsInSameTimestamp && prevUpdate && timestamp <= prevUpdate.timestamp + MULTI_UPDATE_BUFFER_MS; + /** If 'useGranularity' is true we use 'calculatedBeforeAmount', since 'reportedBeforeAmount' will always return a rounded + * amount, whilst 'calculatedBeforeAmount' will return the amount with the decimal precision specified by 'granularity'. */ const beforeAmount = reportedBeforeAmount !== undefined && !withinMultiUpdateBuffer && !this.useGranularity ? reportedBeforeAmount