<commit_before>import React from 'react';

import SPELLS from 'common/SPELLS';
import fetchWcl from 'common/fetchWcl';
import SpellIcon from 'common/SpellIcon';
import { formatThousands, formatNumber } from 'common/format';

import LazyLoadStatisticBox, { STATISTIC_ORDER } from 'Main/LazyLoadStatisticBox';

import Analyzer from 'Parser/Core/Analyzer';

const AURA_OF_SACRIFICE_PASSIVE_DAMAGE_TRANSFER_REDUCTION = 0.5;
const AURA_OF_SACRIFICE_HEALTH_REQUIREMENT = 0.75;
const AURA_OF_SACRIFICE_ACTIVE_DAMAGE_TRANSFER = 0.3;

class AuraOfSacrifice extends Analyzer {
  passiveDamageTaken = 0;
  passiveDamageTransferred = 0;
  perSecond(amount) {
    return amount / this.owner.fightDuration * 1000;
  }
  get passiveDamageReduced() {
    return this.passiveDamageTransferred - this.passiveDamageTaken;
  }
  get passiveDrps() {
    return this.perSecond(this.passiveDamageReduced);
  }
  activeDamageTaken = 0;
  activeDamageTransferred = 0;
  get activeDamageReduced() {
    return this.activeDamageTransferred - this.activeDamageTaken;
  }
  get activeDrps() {
    return this.perSecond(this.activeDamageReduced);
  }
  get drps() {
    return this.passiveDrps + this.activeDrps;
  }

  constructor(...args) {
    super(...args);
    this.active = this.selectedCombatant.hasTalent(SPELLS.AURA_OF_SACRIFICE_TALENT.id);
    if (!this.active) {
      return;
    }
    this.addEventListener('damage', this.handlePassiveTransfer, {
      toPlayer: true,
    });
    this.addEventListener('damage', this.handleHealthUpdate, {
      toPlayer: true,
    });
    this.addEventListener('heal', this.handleHealthUpdate, {
      toPlayer: true,
    });
  }

  // TODO: Account for passive damage transferred during Divine Shield

  handlePassiveTransfer(event) {
    const spellId = event.ability.guid;
    if (spellId !== SPELLS.AURA_OF_SACRIFICE_TRANSFER.id) {
      return;
    }

    const damageTaken = event.amount + (event.absorbed || 0) + (event.overkill || 0);

    if (this.isAuraMasteryActive) {
      // Just track the actual damage taken to subtract from the transferred damage to get the damage reduced. The transferred damage is calculated elsewhere.
      this.activeDamageTaken += damageTaken;
      if (!this.isTransferring) {
        console.warn('AuraOfSacrifice', 'Took active damage while not actively transferring.');
      }
    } else {
      this.passiveDamageTaken += damageTaken;
      // We need to include mitigated damage so we can use the raw damage taken to calculate the original damage transferred
      const rawDamageTaken = damageTaken + (event.mitigated || 0);
      const damageTransferred = rawDamageTaken / AURA_OF_SACRIFICE_PASSIVE_DAMAGE_TRANSFER_REDUCTION;
      this.passiveDamageTransferred += damageTransferred;
    }
  }
  isAuraMasteryActive = false;
  isTransferring = false;
  on_toPlayer_applybuff(event) {
    const spellId = event.ability.guid;
    if (spellId !== SPELLS.AURA_MASTERY.id) {
      return;
    }
    this.isAuraMasteryActive = true;
    // TODO: Suggestion when user popped AM without meeting the health requirement
    this.updateTransferringState(this.hasSufficientHealth);
  }
  on_byPlayer_removebuff(event) {
    const spellId = event.ability.guid;
    if (spellId !== SPELLS.AURA_MASTERY.id) {
      return;
    }
    this.isAuraMasteryActive = false;
    this.updateTransferringState(false);
  }
  healthPercentage = null;
  handleHealthUpdate(event) {
    if (!event.maxHitPoints) {
      // Some events (such as immuned damage events) don't have hit point info
      return;
    }
    this.healthPercentage = event.hitPoints / event.maxHitPoints;
    if (this.isAuraMasteryActive) {
      this.updateTransferringState(this.hasSufficientHealth);
    }
  }
  get hasSufficientHealth() {
    return this.healthPercentage >= AURA_OF_SACRIFICE_HEALTH_REQUIREMENT;
  }
  auraMasteryTransferringHistory = [];
  updateTransferringState(isActive) {
    if (this.isTransferring === isActive) {
      // No change
      return;
    }
    this.isTransferring = isActive;
    if (isActive) {
      this.auraMasteryTransferringHistory.push({
        start: this.owner.currentTimestamp,
        end: null,
      });
    } else {
      const current = this.auraMasteryTransferringHistory[this.auraMasteryTransferringHistory.length - 1];
      current.end = this.owner.currentTimestamp;
    }
  }

  load() {
    const transferringHistory = this.auraMasteryTransferringHistory;
    if (transferringHistory.length === 0) {
      return Promise.resolve();
    }
    // WCL's filter requires the timestamp to be relative to fight start
    const fightStart = this.owner.fight.start_time;
    const timestampFilter = transferringHistory.map(period => `(timestamp>=${period.start - fightStart} AND timestamp<=${period.end - fightStart})`).join(' OR ');
    const filter = `(${timestampFilter}) AND (IN RANGE FROM type='applybuff' AND ability.id=${SPELLS.AURA_OF_SACRIFICE_BUFF.id} TO type='removebuff' AND ability.id=${SPELLS.AURA_OF_SACRIFICE_BUFF.id} GROUP BY target END)`;

    return fetchWcl(`report/tables/damage-taken/${this.owner.report.code}`, {
      start: this.owner.fight.start_time,
      end: this.owner.fight.end_time,
      filter,
    })
      .then(json => {
        console.log('Received AM damage taken', json);
        const actualRaidDamageTaken = json.entries
          .filter(entry => entry.id !== this.owner.playerId) // the Paladin doesn't enjoy the DR
          .reduce((damageTaken, entry) => damageTaken + entry.total, 0);
        const rawRaidDamageTaken = actualRaidDamageTaken / (1 - AURA_OF_SACRIFICE_ACTIVE_DAMAGE_TRANSFER);
        const damageTransferred = rawRaidDamageTaken * AURA_OF_SACRIFICE_ACTIVE_DAMAGE_TRANSFER;
        this.activeDamageTransferred = damageTransferred;
      });
  }

  statistic() {
    return (
      <LazyLoadStatisticBox
        loader={this.load.bind(this)}
        icon={<SpellIcon id={SPELLS.AURA_OF_SACRIFICE_TALENT.id} />}
        value={`>=${formatNumber(this.drps)} DRPS`}
        label="Damage reduced"
        tooltip={`The <b>passive effect</b> transferred ${formatThousands(this.passiveDamageTransferred)} damage (${formatThousands(this.perSecond(this.passiveDamageTransferred))} DTPS) and effectively reduced damage by ${formatThousands(this.passiveDamageReduced)} (${formatThousands(this.passiveDrps)} DRPS).<br />
          The <b>Aura Mastery effect</b> transferred ${formatThousands(this.activeDamageTransferred)} damage (${formatThousands(this.perSecond(this.activeDamageTransferred))} DTPS) and effectively reduced damage by ${formatThousands(this.activeDamageReduced)} (${formatThousands(this.activeDrps)} DRPS).<br />
          The <b>total damage transferred</b> (i.e. the damage reduced on other players) was ${formatThousands(this.passiveDamageTransferred + this.activeDamageTransferred)} damage (${formatThousands(this.perSecond(this.passiveDamageTransferred + this.activeDamageTransferred))} DTPS).<br /><br />

          This is the lowest possible value. This value is pretty accurate for this log if you are looking at the actual gain over not having Aura of Sacrifice bonus at all, but the actual gain may be higher when accounting for other damage reductions.<br /><br />
<commit_msg>Report AoS filter to console<commit_after>import React from 'react';

import SPELLS from 'common/SPELLS';
import fetchWcl from 'common/fetchWcl';
import SpellIcon from 'common/SpellIcon';
import { formatThousands, formatNumber } from 'common/format';

import LazyLoadStatisticBox, { STATISTIC_ORDER } from 'Main/LazyLoadStatisticBox';

import Analyzer from 'Parser/Core/Analyzer';

const AURA_OF_SACRIFICE_PASSIVE_DAMAGE_TRANSFER_REDUCTION = 0.5;
const AURA_OF_SACRIFICE_HEALTH_REQUIREMENT = 0.75;
const AURA_OF_SACRIFICE_ACTIVE_DAMAGE_TRANSFER = 0.3;

class AuraOfSacrifice extends Analyzer {
  passiveDamageTaken = 0;
  passiveDamageTransferred = 0;
  perSecond(amount) {
    return amount / this.owner.fightDuration * 1000;
  }
  get passiveDamageReduced() {
    return this.passiveDamageTransferred - this.passiveDamageTaken;
  }
  get passiveDrps() {
    return this.perSecond(this.passiveDamageReduced);
  }
  activeDamageTaken = 0;
  activeDamageTransferred = 0;
  get activeDamageReduced() {
    return this.activeDamageTransferred - this.activeDamageTaken;
  }
  get activeDrps() {
    return this.perSecond(this.activeDamageReduced);
  }
  get drps() {
    return this.passiveDrps + this.activeDrps;
  }

  constructor(...args) {
    super(...args);
    this.active = this.selectedCombatant.hasTalent(SPELLS.AURA_OF_SACRIFICE_TALENT.id);
    if (!this.active) {
      return;
    }
    this.addEventListener('damage', this.handlePassiveTransfer, {
      toPlayer: true,
    });
    this.addEventListener('damage', this.handleHealthUpdate, {
      toPlayer: true,
    });
    this.addEventListener('heal', this.handleHealthUpdate, {
      toPlayer: true,
    });
  }

  // TODO: Account for passive damage transferred during Divine Shield

  handlePassiveTransfer(event) {
    const spellId = event.ability.guid;
    if (spellId !== SPELLS.AURA_OF_SACRIFICE_TRANSFER.id) {
      return;
    }

    const damageTaken = event.amount + (event.absorbed || 0) + (event.overkill || 0);

    if (this.isAuraMasteryActive) {
      // Just track the actual damage taken to subtract from the transferred damage to get the damage reduced. The transferred damage is calculated elsewhere.
      this.activeDamageTaken += damageTaken;
      if (!this.isTransferring) {
        console.warn('AuraOfSacrifice', 'Took active damage while not actively transferring.');
      }
    } else {
      this.passiveDamageTaken += damageTaken;
      // We need to include mitigated damage so we can use the raw damage taken to calculate the original damage transferred
      const rawDamageTaken = damageTaken + (event.mitigated || 0);
      const damageTransferred = rawDamageTaken / AURA_OF_SACRIFICE_PASSIVE_DAMAGE_TRANSFER_REDUCTION;
      this.passiveDamageTransferred += damageTransferred;
    }
  }
  isAuraMasteryActive = false;
  isTransferring = false;
  on_toPlayer_applybuff(event) {
    const spellId = event.ability.guid;
    if (spellId !== SPELLS.AURA_MASTERY.id) {
      return;
    }
    this.isAuraMasteryActive = true;
    // TODO: Suggestion when user popped AM without meeting the health requirement
    this.updateTransferringState(this.hasSufficientHealth);
  }
  on_byPlayer_removebuff(event) {
    const spellId = event.ability.guid;
    if (spellId !== SPELLS.AURA_MASTERY.id) {
      return;
    }
    this.isAuraMasteryActive = false;
    this.updateTransferringState(false);
  }
  healthPercentage = null;
  handleHealthUpdate(event) {
    if (!event.maxHitPoints) {
      // Some events (such as immuned damage events) don't have hit point info
      return;
    }
    this.healthPercentage = event.hitPoints / event.maxHitPoints;
    if (this.isAuraMasteryActive) {
      this.updateTransferringState(this.hasSufficientHealth);
    }
  }
  get hasSufficientHealth() {
    return this.healthPercentage >= AURA_OF_SACRIFICE_HEALTH_REQUIREMENT;
  }
  auraMasteryTransferringHistory = [];
  updateTransferringState(isActive) {
    if (this.isTransferring === isActive) {
      // No change
      return;
    }
    this.isTransferring = isActive;
    if (isActive) {
      this.auraMasteryTransferringHistory.push({
        start: this.owner.currentTimestamp,
        end: null,
      });
    } else {
      const current = this.auraMasteryTransferringHistory[this.auraMasteryTransferringHistory.length - 1];
      current.end = this.owner.currentTimestamp;
    }
  }

  load() {
    const transferringHistory = this.auraMasteryTransferringHistory;
    if (transferringHistory.length === 0) {
      return Promise.resolve();
    }
    // WCL's filter requires the timestamp to be relative to fight start
    const fightStart = this.owner.fight.start_time;
    const timestampFilter = transferringHistory.map(period => `(timestamp>=${period.start - fightStart} AND timestamp<=${period.end - fightStart})`).join(' OR ');
    const filter = `(${timestampFilter}) AND (IN RANGE FROM type='applybuff' AND ability.id=${SPELLS.AURA_OF_SACRIFICE_BUFF.id} TO type='removebuff' AND ability.id=${SPELLS.AURA_OF_SACRIFICE_BUFF.id} GROUP BY target END)`;
    // Just leave this here to make it easy to switch to WCL
    console.log(filter);

    return fetchWcl(`report/tables/damage-taken/${this.owner.report.code}`, {
      start: this.owner.fight.start_time,
      end: this.owner.fight.end_time,
      filter,
    })
      .then(json => {
        console.log('Received AM damage taken', json);
        const actualRaidDamageTaken = json.entries
          .filter(entry => entry.id !== this.owner.playerId) // the Paladin doesn't enjoy the DR
          .reduce((damageTaken, entry) => damageTaken + entry.total, 0);
        const rawRaidDamageTaken = actualRaidDamageTaken / (1 - AURA_OF_SACRIFICE_ACTIVE_DAMAGE_TRANSFER);
        const damageTransferred = rawRaidDamageTaken * AURA_OF_SACRIFICE_ACTIVE_DAMAGE_TRANSFER;
        this.activeDamageTransferred = damageTransferred;
      });
  }

  statistic() {
    return (
      <LazyLoadStatisticBox
        loader={this.load.bind(this)}
        icon={<SpellIcon id={SPELLS.AURA_OF_SACRIFICE_TALENT.id} />}
        value={`>=${formatNumber(this.drps)} DRPS`}
        label="Damage reduced"
        tooltip={`The <b>passive effect</b> transferred ${formatThousands(this.passiveDamageTransferred)} damage (${formatThousands(this.perSecond(this.passiveDamageTransferred))} DTPS) and effectively reduced damage by ${formatThousands(this.passiveDamageReduced)} (${formatThousands(this.passiveDrps)} DRPS).<br />
          The <b>Aura Mastery effect</b> transferred ${formatThousands(this.activeDamageTransferred)} damage (${formatThousands(this.perSecond(this.activeDamageTransferred))} DTPS) and effectively reduced damage by ${formatThousands(this.activeDamageReduced)} (${formatThousands(this.activeDrps)} DRPS).<br />
          The <b>total damage transferred</b> (i.e. the damage reduced on other players) was ${formatThousands(this.passiveDamageTransferred + this.activeDamageTransferred)} damage (${formatThousands(this.perSecond(this.passiveDamageTransferred + this.activeDamageTransferred))} DTPS).<br /><br />

          This is the lowest possible value. This value is pretty accurate for this log if you are looking at the actual gain over not having Aura of Sacrifice bonus at all, but the actual gain may be higher when accounting for other damage reductions.<br /><br />

{'fraction_non_alphanumeric': 0.10411372845271616, 'fraction_numerical': 0.003021770482796511, 'mean_word_length': 3.844311377245509, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4624783', 'n_tokens_mistral': 4685, 'n_tokens_neox': 4555, 'n_words': 979}