From 67b44e96c6ca032bf8c8a98c717bc3b41f5ddf8c Mon Sep 17 00:00:00 2001
From: Putro <29204244+Pewtro@users.noreply.github.com>
Date: Thu, 15 Feb 2024 19:46:56 +0100
Subject: [PATCH] [Hunter] Workaround for Volcoross having weird events

Volcoross is notoriously bad at having events that make no sense, e.g. https://www.warcraftlogs.com/reports/qpJ4tV8aYZ6CRGrL#fight=10&type=auras&source=45&ability=272790&view=events where Apply Buff events are going out without the buff expiring.
This adds a new flag to the buff tracker labelled as experimental because it might have other adverse side effects and uses that to try and work around the weird events
---
 .../talents/BlisteringScalesStackTracker.tsx  | 10 +++++++
 .../retail/hunter/beastmastery/CHANGELOG.tsx  |  1 +
 .../rotation/FrenzyBuffStackTracker.tsx       |  9 +++++++
 .../modules/talents/BarbedShot.tsx            | 10 ++++---
 src/parser/shared/modules/BuffStackTracker.ts | 27 +++++++++++++++++++
 5 files changed, 54 insertions(+), 3 deletions(-)

diff --git a/src/analysis/retail/evoker/augmentation/modules/talents/BlisteringScalesStackTracker.tsx b/src/analysis/retail/evoker/augmentation/modules/talents/BlisteringScalesStackTracker.tsx
index 0c2cb73b118..b01c3830dc2 100644
--- a/src/analysis/retail/evoker/augmentation/modules/talents/BlisteringScalesStackTracker.tsx
+++ b/src/analysis/retail/evoker/augmentation/modules/talents/BlisteringScalesStackTracker.tsx
@@ -39,6 +39,11 @@ export default class BlisteringScalesStackTracker extends Analyzer {
     ? 20
     : 15;
 
+  //Unused in this module but exists because this is a custom implementation of BuffStackTracker that is used to render a graph that relies on these values being present
+  static workaroundWeirdBuffEvents_experimental = false;
+  buffDuration = 0;
+  buffActive = false;
+
   constructor(options: Options) {
     super(options);
     this.addEventListener(
@@ -70,6 +75,11 @@ export default class BlisteringScalesStackTracker extends Analyzer {
     return ctor.trackedBuff;
   }
 
+  get workaroundWeirdBuffEvents_experimental() {
+    const ctor = this.constructor as typeof BlisteringScalesStackTracker;
+    return ctor.workaroundWeirdBuffEvents_experimental;
+  }
+
   /** The player's buff stack amount at the current timestamp */
   get current(): number {
     const lastUpdate = this.buffStackUpdates.at(-1);
diff --git a/src/analysis/retail/hunter/beastmastery/CHANGELOG.tsx b/src/analysis/retail/hunter/beastmastery/CHANGELOG.tsx
index 59121b238fa..43bf41e80c8 100644
--- a/src/analysis/retail/hunter/beastmastery/CHANGELOG.tsx
+++ b/src/analysis/retail/hunter/beastmastery/CHANGELOG.tsx
@@ -5,6 +5,7 @@ import TALENTS from 'common/TALENTS/hunter';
 import SPELLS from 'common/SPELLS';
 import RESOURCE_TYPES from 'game/RESOURCE_TYPES';
 export default [
+  change(date(2024, 2, 15), <>Try to workaround weird buff events on bosses like Volcoross for <SpellLink spell={SPELLS.BARBED_SHOT_PET_BUFF} /> </>, Putro),
   change(date(2023, 11, 21), <>Update when <SpellLink spell={TALENTS.BARBED_SHOT_TALENT}  /> are marked as good or bad casts. </>, Putro),
   change(date(2023, 11, 21), <>Add <SpellLink spell={TALENTS.CALL_OF_THE_WILD_TALENT} /> to the spellbook specifying cooldown and global cooldown. </>, Putro),
   change(date(2023, 10, 18), <>Enable spec with 10.2 changes</>,Arlie),
diff --git a/src/analysis/retail/hunter/beastmastery/modules/guide/sections/rotation/FrenzyBuffStackTracker.tsx b/src/analysis/retail/hunter/beastmastery/modules/guide/sections/rotation/FrenzyBuffStackTracker.tsx
index 76ebc787056..7491a8df758 100644
--- a/src/analysis/retail/hunter/beastmastery/modules/guide/sections/rotation/FrenzyBuffStackTracker.tsx
+++ b/src/analysis/retail/hunter/beastmastery/modules/guide/sections/rotation/FrenzyBuffStackTracker.tsx
@@ -1,13 +1,22 @@
+import {
+  ORIGINAL_FRENZY_DURATION,
+  SAVAGERY_FRENZY_DURATION,
+} from 'analysis/retail/hunter/beastmastery/constants';
 import SPELLS from 'common/SPELLS';
+import { TALENTS_HUNTER } from 'common/TALENTS';
 import { Options } from 'parser/core/Analyzer';
 import BuffStackTracker from 'parser/shared/modules/BuffStackTracker';
 
 export default class FrenzyBuffStackTracker extends BuffStackTracker {
   static trackPets = true;
   static trackedBuff = SPELLS.BARBED_SHOT_PET_BUFF;
+  static workaroundWeirdBuffEvents_experimental = true;
 
   // eslint-disable-next-line @typescript-eslint/no-useless-constructor
   constructor(options: Options) {
     super(options);
+    this.buffDuration = this.selectedCombatant.hasTalent(TALENTS_HUNTER.SAVAGERY_TALENT)
+      ? SAVAGERY_FRENZY_DURATION
+      : ORIGINAL_FRENZY_DURATION;
   }
 }
diff --git a/src/analysis/retail/hunter/beastmastery/modules/talents/BarbedShot.tsx b/src/analysis/retail/hunter/beastmastery/modules/talents/BarbedShot.tsx
index 92c8280abd7..7822d164e8e 100644
--- a/src/analysis/retail/hunter/beastmastery/modules/talents/BarbedShot.tsx
+++ b/src/analysis/retail/hunter/beastmastery/modules/talents/BarbedShot.tsx
@@ -20,7 +20,6 @@ import Events, {
 } from 'parser/core/Events';
 import { ThresholdStyle, When } from 'parser/core/ParseResults';
 import { currentStacks } from 'parser/shared/modules/helpers/Stacks';
-import Pets from 'parser/shared/modules/Pets';
 import BoringSpellValueText from 'parser/ui/BoringSpellValueText';
 import { QualitativePerformance } from 'parser/ui/QualitativePerformance';
 import Statistic from 'parser/ui/Statistic';
@@ -44,12 +43,10 @@ import SpellUsable from '../core/SpellUsable';
 class BarbedShot extends Analyzer {
   static dependencies = {
     spellUsable: SpellUsable,
-    pets: Pets,
     globalCooldown: GlobalCooldown,
   };
 
   protected spellUsable!: SpellUsable;
-  protected pets!: Pets;
   protected globalCooldown!: GlobalCooldown;
 
   barbedShotStacks: number[][] = [];
@@ -162,6 +159,13 @@ class BarbedShot extends Analyzer {
       return;
     }
     this.lastBarbedShotUpdate = event.timestamp;
+    //Bosses like Volcoross are shit and will trigger ApplyBuffEvents without the buff having expired, and it should have been RefreshBuffEvent instead
+    if (
+      event.type === EventType.ApplyBuff &&
+      event.timestamp < this.lastBarbedShotUpdate + this.frenzyBuffDuration
+    ) {
+      return;
+    }
     this.lastBarbedShotStack = currentStacks(event);
   }
 
diff --git a/src/parser/shared/modules/BuffStackTracker.ts b/src/parser/shared/modules/BuffStackTracker.ts
index 122695a5147..2c1e997c06f 100644
--- a/src/parser/shared/modules/BuffStackTracker.ts
+++ b/src/parser/shared/modules/BuffStackTracker.ts
@@ -34,9 +34,18 @@ export default class BuffStackTracker extends Analyzer {
   /** Whether the module should look at player (default) or at pets when tracking buffs */
   static trackPets = false;
 
+  /** Experimental feature that makes it so we try and work around weird buff events in the game */
+  static workaroundWeirdBuffEvents_experimental = false;
+
   /** Time ordered list of buff stack updates */
   buffStackUpdates: BuffStackUpdate[] = [];
 
+  /** Duration of the buff */
+  buffDuration = 0;
+
+  /** Whether the buff is currently active */
+  buffActive = false;
+
   constructor(options: Options) {
     super(options);
     const trackTarget = !this.trackPets ? SELECTED_PLAYER : SELECTED_PLAYER_PET;
@@ -69,6 +78,11 @@ export default class BuffStackTracker extends Analyzer {
     return ctor.trackedBuff;
   }
 
+  get workaroundWeirdBuffEvents_experimental() {
+    const ctor = this.constructor as typeof BuffStackTracker;
+    return ctor.workaroundWeirdBuffEvents_experimental;
+  }
+
   /** The player's buff stack amount at the current timestamp */
   get current(): number {
     const lastUpdate = this.buffStackUpdates.at(-1);
@@ -80,6 +94,18 @@ export default class BuffStackTracker extends Analyzer {
   }
 
   onApplyBuff(event: ApplyBuffEvent) {
+    if (this.workaroundWeirdBuffEvents_experimental) {
+      const lastUpdate = this.buffStackUpdates.at(-1);
+      //If we are registering an ApplyBuffEvent before it's meant to be expire and we have not seen a RemoveBuffEvent then we should not log and push it as it was most likely a refresh
+      if (
+        this.buffActive &&
+        lastUpdate &&
+        lastUpdate.timestamp < event.timestamp + this.buffDuration
+      ) {
+        return;
+      }
+    }
+    this.buffActive = true;
     this._logAndPushUpdate(
       {
         type: event.type,
@@ -104,6 +130,7 @@ export default class BuffStackTracker extends Analyzer {
   }
 
   onRemoveBuff(event: RemoveBuffEvent) {
+    this.buffActive = false;
     this._logAndPushUpdate(
       {
         type: event.type,