<commit_before>import React from 'react';
import Analyzer from 'parser/core/Analyzer';
import {formatPercentage, formatNumber} from 'common/format';
import SPELLS from 'common/SPELLS';
import TraitStatisticBox, { STATISTIC_ORDER } from 'interface/others/TraitStatisticBox';
import { calculateAzeriteEffects } from 'common/stats';
import HIT_TYPES from 'game/HIT_TYPES';
import Combatants from 'parser/shared/modules/Combatants';
import { getSpellInfo } from '../../../SpellInfo';
import StatWeights from '../../features/StatWeights';
import Mastery from '../../core/Mastery';
import {getPrimaryStatForItemLevel, findItemLevelByPrimaryStat} from './/common';

const CRIT_EFFECT = 2;
const REGROWTH_DURATION = 12000;

/**
 Regrowth heals for 696 more over its duration, and its healing over time effect
 also applies to the target of your Lifebloom.

 How we're calculating the value of Rampant Growth:
 1. For every Regrowth healing event, credit the Rampant Growth HoT part to Rampant Growth (discounting overhealing).
 2. For every Regrowth healing event, if the target has LB credit the entire Regrowth HoT to Rampant Growth.
 3. For every healing event, if the target has LB and Regrowth credit one stack of mastery healing to Rampant Growth.
 4. For every Regrowth cast, if the target has LB "ban" all credits from point 2 and 3 for the next 12 seconds.
 */

class RampantGrowth extends Analyzer{
  static dependencies = {
    statWeights: StatWeights,
    mastery: Mastery,
    combatants: Combatants,
  };

  healingFromHotIncrease = 0;
  healingFromFreeRegrowthHoT = 0;
  healingFromFreeRegrowthMastery = 0;
  avgItemLevel = 0;
  traitLevel = 0;
  healingIncreasePerTick = 0;
  banPeriod = 0;
  discountHealing = 0;
  directRegrowthCastsOnLB = 0;

  constructor(...args){
    super(...args);
    this.active = this.selectedCombatant.hasTrait(SPELLS.RAMPANT_GROWTH_TRAIT.id);
    if(this.active) {
      this.avgItemLevel = this.selectedCombatant.traitsBySpellId[SPELLS.RAMPANT_GROWTH_TRAIT.id]
        .reduce((a, b) => a + b) / this.selectedCombatant.traitsBySpellId[SPELLS.RAMPANT_GROWTH_TRAIT.id].length;
      this.traitLevel = this.selectedCombatant.traitsBySpellId[SPELLS.RAMPANT_GROWTH_TRAIT.id].length;
      this.healingIncreasePerTick = this.selectedCombatant.traitsBySpellId[SPELLS.RAMPANT_GROWTH_TRAIT.id]
        .reduce((sum, rank) => sum + calculateAzeriteEffects(SPELLS.RAMPANT_GROWTH_TRAIT.id, rank)[0], 0);
    }
  }

  on_byPlayer_cast(event) {
    if(event.ability.guid !== SPELLS.REGROWTH.id) {
      return;
    }
    const combatant = this.combatants.players[event.targetID];
    if (!combatant) {
      return;
    }

    // If regrowth was cast on the LB target, disregard regrowth base HoT/mastery healing on that target for 12 seconds.
    if(combatant.hasBuff(SPELLS.LIFEBLOOM_HOT_HEAL.id, event.timestamp, 200, null, this.owner.playerId)) {
      this.directRegrowthCastsOnLB++;
      this.banPeriod = event.timestamp + REGROWTH_DURATION;
    } else {
      this.banPeriod = 0;
    }
  }

  on_byPlayer_heal(event) {
    const spellId = event.ability.guid;
    const combatant = this.combatants.players[event.targetID];

    // If target has LB and Regrowth and is not marked under the ban period, credit one stack of mastery healing given that the heal scales with mastery.
    if(combatant
      && combatant.hasBuff(SPELLS.LIFEBLOOM_HOT_HEAL.id, event.timestamp, 200, null, this.owner.playerId)
      && combatant.hasBuff(SPELLS.REGROWTH.id, event.timestamp, 200, null, this.owner.playerId)
      && getSpellInfo(spellId).mastery) {
      if(this.banPeriod < event.timestamp) {
        const oneMasteryStackHealing = this.mastery.decomposeHeal(event).oneStack;
        this.healingFromFreeRegrowthMastery += (oneMasteryStackHealing || 0);
      } else {
        this.discountHealing += this.mastery.decomposeHeal(event).oneStack;
      }
    }

    if (spellId !== SPELLS.REGROWTH.id || !event.tick) {
      return;
    }

    // If target has LB and Regrowth and is not marked under the ban period, credit all regrowth HoT.
    if(combatant.hasBuff(SPELLS.LIFEBLOOM_HOT_HEAL.id, event.timestamp, 200, null, this.owner.playerId)) {
      if(this.banPeriod < event.timestamp) {
        this.healingFromFreeRegrowthHoT += event.amount;
      } else {
        this.discountHealing += event.amount;
      }
    } else if(event.hitType === HIT_TYPES.CRIT) {
      this.healingFromHotIncrease += Math.max((this.healingIncreasePerTick * CRIT_EFFECT) - (event.overheal || 0), 0);
    } else {
      this.healingFromHotIncrease += Math.max((this.healingIncreasePerTick) - (event.overheal || 0), 0);
    }
  }

  statistic(){
    const healingFromHotIncrease = this.owner.getPercentageOfTotalHealingDone(this.healingFromHotIncrease);
    const freeRegrowthHoT = this.owner.getPercentageOfTotalHealingDone(this.healingFromFreeRegrowthHoT);
    const freeRegrowthMastery = this.owner.getPercentageOfTotalHealingDone(this.healingFromFreeRegrowthMastery);
<commit_msg>Resto druid - rampant growth: Fixed an edge case<commit_after>import React from 'react';
import Analyzer from 'parser/core/Analyzer';
import {formatPercentage, formatNumber} from 'common/format';
import SPELLS from 'common/SPELLS';
import TraitStatisticBox, { STATISTIC_ORDER } from 'interface/others/TraitStatisticBox';
import { calculateAzeriteEffects } from 'common/stats';
import HIT_TYPES from 'game/HIT_TYPES';
import Combatants from 'parser/shared/modules/Combatants';
import { getSpellInfo } from '../../../SpellInfo';
import StatWeights from '../../features/StatWeights';
import Mastery from '../../core/Mastery';
import {getPrimaryStatForItemLevel, findItemLevelByPrimaryStat} from './/common';

const CRIT_EFFECT = 2;
const REGROWTH_DURATION = 12000;

/**
 Regrowth heals for 696 more over its duration, and its healing over time effect
 also applies to the target of your Lifebloom.

 How we're calculating the value of Rampant Growth:
 1. For every Regrowth healing event, credit the Rampant Growth HoT part to Rampant Growth (discounting overhealing).
 2. For every Regrowth healing event, if the target has LB credit the entire Regrowth HoT to Rampant Growth.
 3. For every healing event, if the target has LB and Regrowth credit one stack of mastery healing to Rampant Growth.
 4. For every Regrowth cast, if the target has LB "ban" all credits from point 2 and 3 for the next 12 seconds.
 */

class RampantGrowth extends Analyzer{
  static dependencies = {
    statWeights: StatWeights,
    mastery: Mastery,
    combatants: Combatants,
  };

  healingFromHotIncrease = 0;
  healingFromFreeRegrowthHoT = 0;
  healingFromFreeRegrowthMastery = 0;
  avgItemLevel = 0;
  traitLevel = 0;
  healingIncreasePerTick = 0;
  banPeriod = 0;
  discountHealing = 0;
  directRegrowthCastsOnLB = 0;

  constructor(...args){
    super(...args);
    this.active = this.selectedCombatant.hasTrait(SPELLS.RAMPANT_GROWTH_TRAIT.id);
    if(this.active) {
      this.avgItemLevel = this.selectedCombatant.traitsBySpellId[SPELLS.RAMPANT_GROWTH_TRAIT.id]
        .reduce((a, b) => a + b) / this.selectedCombatant.traitsBySpellId[SPELLS.RAMPANT_GROWTH_TRAIT.id].length;
      this.traitLevel = this.selectedCombatant.traitsBySpellId[SPELLS.RAMPANT_GROWTH_TRAIT.id].length;
      this.healingIncreasePerTick = this.selectedCombatant.traitsBySpellId[SPELLS.RAMPANT_GROWTH_TRAIT.id]
        .reduce((sum, rank) => sum + calculateAzeriteEffects(SPELLS.RAMPANT_GROWTH_TRAIT.id, rank)[0], 0);
    }
  }

  on_byPlayer_cast(event) {
    if(event.ability.guid !== SPELLS.REGROWTH.id) {
      return;
    }
    const combatant = this.combatants.players[event.targetID];
    if (!combatant) {
      return;
    }

    // If regrowth was cast on the LB target, disregard regrowth base HoT/mastery healing on that target for 12 seconds.
    if(combatant.hasBuff(SPELLS.LIFEBLOOM_HOT_HEAL.id, event.timestamp, 200, null, this.owner.playerId)) {
      this.directRegrowthCastsOnLB++;
      this.banPeriod = event.timestamp + REGROWTH_DURATION;
    } else {
      this.banPeriod = 0;
    }
  }

  on_byPlayer_heal(event) {
    const spellId = event.ability.guid;
    const combatant = this.combatants.players[event.targetID];

    // If target has LB and Regrowth and is not marked under the ban period, credit one stack of mastery healing given that the heal scales with mastery.
    if(combatant
      && combatant.hasBuff(SPELLS.LIFEBLOOM_HOT_HEAL.id, event.timestamp, 200, null, this.owner.playerId)
      && combatant.hasBuff(SPELLS.REGROWTH.id, event.timestamp, 200, null, this.owner.playerId)
      && getSpellInfo(spellId).mastery) {
      if(this.banPeriod < event.timestamp) {
        const oneMasteryStackHealing = this.mastery.decomposeHeal(event).oneStack;
        this.healingFromFreeRegrowthMastery += (oneMasteryStackHealing || 0);
      } else {
        this.discountHealing += this.mastery.decomposeHeal(event).oneStack;
      }
    }

    if (spellId !== SPELLS.REGROWTH.id || !event.tick) {
      return;
    }

    // If target has LB and Regrowth and is not marked under the ban period, credit all regrowth HoT.
    if(combatant.hasBuff(SPELLS.LIFEBLOOM_HOT_HEAL.id, event.timestamp, 200, null, this.owner.playerId)) {
      if (this.banPeriod < event.timestamp) {
        this.healingFromFreeRegrowthHoT += event.amount;
        // No need to check the healing from HoT increase as it's already included in the statement above
        return;
      } else {
        this.discountHealing += event.amount;
      }
    }
    // Even if we are in the "banPeriod" the increased HoT healing still applies and should be credited.
    if(event.hitType === HIT_TYPES.CRIT) {
      this.healingFromHotIncrease += Math.max((this.healingIncreasePerTick * CRIT_EFFECT) - (event.overheal || 0), 0);
    } else {
      this.healingFromHotIncrease += Math.max((this.healingIncreasePerTick) - (event.overheal || 0), 0);
    }
  }

  statistic(){
    const healingFromHotIncrease = this.owner.getPercentageOfTotalHealingDone(this.healingFromHotIncrease);
    const freeRegrowthHoT = this.owner.getPercentageOfTotalHealingDone(this.healingFromFreeRegrowthHoT);
    const freeRegrowthMastery = this.owner.getPercentageOfTotalHealingDone(this.healingFromFreeRegrowthMastery);

{'fraction_non_alphanumeric': 0.10099531615925059, 'fraction_numerical': 0.00936768149882904, 'mean_word_length': 3.8944603629417385, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, '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': '7061471', 'n_tokens_mistral': 3548, 'n_tokens_neox': 3366, 'n_words': 796}