<commit_before>import React from 'react';

import Analyzer, { SELECTED_PLAYER } from 'parser/core/Analyzer';
import Events from 'parser/core/Events';
import StatTracker from 'parser/shared/modules/StatTracker';
import calculateBonusAzeriteDamage from 'parser/core/calculateBonusAzeriteDamage';

import SPELLS from 'common/SPELLS';
import { calculateAzeriteEffects } from 'common/stats';
import { formatThousands } from 'common/format';

import TraitStatisticBox from 'interface/others/TraitStatisticBox';
import ItemDamageDone from 'interface/others/ItemDamageDone';

import { poissonBinomialCDF, poissonBinomialPMF } from 'parser/warlock/shared/probability';
import SoulShardTracker from '../soulshards/SoulShardTracker';

const HOG_SP_COEFFICIENT = 0.16; // taken from Simcraft SpellDataDump

/*
    Demonic Meteor
      Hand of Gul'dan deals X additional damage and has a 5% chance per Soul Shard spent of refunding a Soul Shard.
 */
class DemonicMeteor extends Analyzer {
  static dependencies = {
    soulShardTracker: SoulShardTracker,
    statTracker: StatTracker,
  };

  traitBonus = 0;
  damage = 0;

  constructor(...args) {
    super(...args);
    this.active = this.selectedCombatant.hasTrait(SPELLS.DEMONIC_METEOR.id);
    if (!this.active) {
      return;
    }

    this.traitBonus = this.selectedCombatant.traitsBySpellId[SPELLS.DEMONIC_METEOR.id].reduce((total, rank) => {
      const [ damage ] = calculateAzeriteEffects(SPELLS.DEMONIC_METEOR.id, rank);
      return total + damage;
    }, 0);

    this.addEventListener(Events.damage.by(SELECTED_PLAYER).spell(SPELLS.HAND_OF_GULDAN_DAMAGE), this.onHogDamage);
  }

  onHogDamage(event) {
    const [ bonusDamage ] = calculateBonusAzeriteDamage(event, [this.traitBonus], HOG_SP_COEFFICIENT, this.statTracker.currentIntellectRating);
    this.damage += bonusDamage;
  }

  statistic() {
    const shardsGained = this.soulShardTracker.getGeneratedBySpell(SPELLS.DEMONIC_METEOR_SHARD_GEN.id);
    this.log('Poisson PMF test');
    const pmf = poissonBinomialPMF(2, 3, [0.15, 0.1, 0.15]);
    this.log(`Result: ${pmf}`);
    this.log('Poisson CDF test');
    const { probability: cdf } = poissonBinomialCDF(2, 3, [0.15, 0.1, 0.15]);
    this.log(`Result: ${cdf}`);
    return (
      <TraitStatisticBox
        trait={SPELLS.DEMONIC_METEOR.id}
        value={<ItemDamageDone amount={this.damage} approximate />}
        tooltip={`Estimated bonus Hand of Gul'dan damage: ${formatThousands(this.damage)}<br />
                Shards gained with this trait: ${shardsGained}<br /><br />
                The damage is an approximation using current Intellect values at given time, but because we might miss some Intellect buffs (e.g. trinkets, traits), the value of current Intellect might be a little incorrect.`}
      />
    );
  }
}
export default DemonicMeteor;<commit_msg>Used PBD in Demonic Meteor<commit_after>import React from 'react';

import Analyzer, { SELECTED_PLAYER } from 'parser/core/Analyzer';
import Events from 'parser/core/Events';
import StatTracker from 'parser/shared/modules/StatTracker';
import calculateBonusAzeriteDamage from 'parser/core/calculateBonusAzeriteDamage';

import RESOURCE_TYPES from 'game/RESOURCE_TYPES';
import SPELLS from 'common/SPELLS';
import { calculateAzeriteEffects } from 'common/stats';
import { formatPercentage, formatThousands } from 'common/format';

import TraitStatisticBox from 'interface/others/TraitStatisticBox';
import ItemDamageDone from 'interface/others/ItemDamageDone';

import { poissonBinomialCDF } from 'parser/warlock/shared/probability';
import SoulShardTracker from '../soulshards/SoulShardTracker';

const HOG_SP_COEFFICIENT = 0.16; // taken from Simcraft SpellDataDump
const HOG_CHANCE_PER_SHARD = 0.05;

/*
    Demonic Meteor
      Hand of Gul'dan deals X additional damage and has a 5% chance per Soul Shard spent of refunding a Soul Shard.
 */
class DemonicMeteor extends Analyzer {
  static dependencies = {
    soulShardTracker: SoulShardTracker,
    statTracker: StatTracker,
  };

  traitBonus = 0;
  damage = 0;
  probabilities = [];

  constructor(...args) {
    super(...args);
    this.active = this.selectedCombatant.hasTrait(SPELLS.DEMONIC_METEOR.id);
    if (!this.active) {
      return;
    }

    this.traitBonus = this.selectedCombatant.traitsBySpellId[SPELLS.DEMONIC_METEOR.id].reduce((total, rank) => {
      const [ damage ] = calculateAzeriteEffects(SPELLS.DEMONIC_METEOR.id, rank);
      return total + damage;
    }, 0);

    this.addEventListener(Events.damage.by(SELECTED_PLAYER).spell(SPELLS.HAND_OF_GULDAN_DAMAGE), this.onHogDamage);
    this.addEventListener(Events.cast.by(SELECTED_PLAYER).spell(SPELLS.HAND_OF_GULDAN_CAST), this.onHogCast);
  }

  onHogCast(event) {
    if (!event.classResources) {
      return;
    }
    const shards = event.classResources.find(resource => resource.type === RESOURCE_TYPES.SOUL_SHARDS.id);
    if (!shards) {
      return;
    }
    // store shards spent on Hand of Gul'dan - percentage chance to proc the trait (5% per shard)
    this.probabilities.push(shards.cost * HOG_CHANCE_PER_SHARD);
  }

  onHogDamage(event) {
    const [ bonusDamage ] = calculateBonusAzeriteDamage(event, [this.traitBonus], HOG_SP_COEFFICIENT, this.statTracker.currentIntellectRating);
    this.damage += bonusDamage;
  }

  statistic() {
    const shardsGained = this.soulShardTracker.getGeneratedBySpell(SPELLS.DEMONIC_METEOR_SHARD_GEN.id);
    // we need to get the amount of shards we were most likely to get given certain probabilities
    // this corresponds to the highest PMF in the distribution
    // which should be calculated when calling CDF for a high enough k (since it recursively calculates values for lesser k, we should be able to get it from the lookup table)
    const { lookup } = poissonBinomialCDF(Math.round(this.probabilities.length / 2), this.probabilities.length, this.probabilities);
    let maxP = 0; // maximum probability
    let max = -1; // k of the maximum probability - should be the most likely amount of shards to get with given probabilities
    lookup.forEach((row, k) => {
      // row is an array of values Ek,j where for each row, k is fixed and we're finding the maximum of the last value (which is Ek,n)
      // last = Ek,n = chance to proc k out of n times
      const last = row[row.length - 1];
      if (last > maxP) {
        maxP = last;
        max = k;
      }
    });
    return (
      <TraitStatisticBox
        trait={SPELLS.DEMONIC_METEOR.id}
        value={<ItemDamageDone amount={this.damage} approximate />}
        tooltip={`Estimated bonus Hand of Gul'dan damage: ${formatThousands(this.damage)}<br />
                Shards gained with this trait: ${shardsGained} (<strong>${formatPercentage(shardsGained / max)} %</strong> of procs you could expect in this fight.)<br /><br />
                The damage is an approximation using current Intellect values at given time, but because we might miss some Intellect buffs (e.g. trinkets, traits), the value of current Intellect might be a little incorrect.`}
      />
    );
  }
}
export default DemonicMeteor;
{'fraction_non_alphanumeric': 0.10488525974940166, 'fraction_numerical': 0.005912994509362241, 'mean_word_length': 3.8824742268041237, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 20, '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': '3108175', 'n_tokens_mistral': 2340, 'n_tokens_neox': 2281, 'n_words': 583}