<commit_before>import SPELLS from 'common/SPELLS';
import COVENANTS from 'game/shadowlands/COVENANTS';
import SPECS from 'game/SPECS';
import { SpellLink } from 'interface';
import Analyzer, { Options, SELECTED_PLAYER } from 'parser/core/Analyzer';
import Events, {
  AbilityEvent,
  ApplyBuffEvent,
  CastEvent,
  TargettedEvent,
} from 'parser/core/Events';
import Abilities from 'parser/core/modules/Abilities';
import BoringSpellValueText from 'parser/ui/BoringSpellValueText';
import Statistic from 'parser/ui/Statistic';
import STATISTIC_CATEGORY from 'parser/ui/STATISTIC_CATEGORY';
import STATISTIC_ORDER from 'parser/ui/STATISTIC_ORDER';
import React from 'react';

import ActiveDruidForm, { DruidForm } from '../core/ActiveDruidForm';

/*
 * For several spells we include multiple IDs for the same spell -
 * probably only one ID is being used but as of this writing neither Abelito nor Sref
 * has cared enough to find out which.
 */

/** All convokable spells that 'hit' with a buff application */
const CONVOKE_BUFF_SPELLS = [
  SPELLS.REJUVENATION,
  SPELLS.REJUVENATION_GERMINATION,
  SPELLS.REGROWTH,
  SPELLS.IRONFUR,
  SPELLS.TIGERS_FURY,
  SPELLS.FERAL_FRENZY_TALENT,
  SPELLS.WILD_GROWTH,
  SPELLS.FLOURISH_TALENT,
  SPELLS.STARFALL_CAST, // apparently this is also the ID for the buff
];
/** All convokable spells that 'hit' with a debuff application */
const CONVOKE_DEBUFF_SPELLS = [
  SPELLS.MOONFIRE,
  SPELLS.MOONFIRE_BEAR,
  SPELLS.MOONFIRE_FERAL,
  SPELLS.RAKE_BLEED,
  SPELLS.THRASH_BEAR_DOT,
];
/** All convokable spells that 'hit' with direct healing */
const CONVOKE_HEAL_SPELLS = [SPELLS.SWIFTMEND];
/** All convokable spells that 'hit' with direct damage */
const CONVOKE_DAMAGE_SPELLS = [
  SPELLS.WRATH,
  SPELLS.WRATH_MOONKIN,
  SPELLS.STARSURGE_AFFINITY,
  SPELLS.STARSURGE_MOONKIN,
  SPELLS.FULL_MOON,
  SPELLS.FEROCIOUS_BITE,
  SPELLS.SHRED,
  SPELLS.MANGLE_BEAR,
  SPELLS.PULVERIZE_TALENT,
];
/** Convokable spells that have travel time */
const SPELLS_WITH_TRAVEL_TIME = [
  SPELLS.STARSURGE_AFFINITY,
  SPELLS.STARSURGE_MOONKIN,
  SPELLS.FULL_MOON,
  SPELLS.WRATH,
  SPELLS.WRATH_MOONKIN,
];
const SPELL_IDS_WITH_TRAVEL_TIME = SPELLS_WITH_TRAVEL_TIME.map((s) => s.id);
/** Convokable spells that can hit multiple targets */
const SPELL_IDS_WITH_AOE = [
  SPELLS.MOONFIRE.id,
  SPELLS.MOONFIRE_BEAR.id,
  SPELLS.MOONFIRE_FERAL.id,
  SPELLS.FULL_MOON.id,
  SPELLS.THRASH_BEAR_DOT.id,
  SPELLS.WILD_GROWTH.id,
];

const SPELLS_CAST = 16;
const SPELLS_CAST_RESTO = 12;

const AOE_BUFFER_MS = 100;
const AFTER_CHANNEL_BUFFER_MS = 50;
const TRAVEL_BUFFER_MS = 2_000;

/**
 * **Convoke the Spirits**
 * Covenant - Night Fae
 *
 * Call upon the Night Fae for an eruption of energy,
 * channeling a rapid flurry of 16 (12 for Resto) Druid spells and abilities over 4 sec.
 */
class ConvokeSpirits extends Analyzer {
  static dependencies = {
    abilities: Abilities,
    activeDruidForm: ActiveDruidForm,
  };

  protected abilities!: Abilities;
  protected activeDruidForm!: ActiveDruidForm;

  /** The number of times Convoke has been cast */
  cast: number = 0;
  /** The number of spells cast by a full Convoke channel */
  spellsPerCast: number;
  /** Timestamp of the most recent AoE spell hit registered during Convoke - used to avoid double counts */
  mostRecentAoeTimestamp = 0;
  /** Mapping from convoke cast number to a tracker for that cast - note that index zero will always be empty */
  convokeTracker: ConvokeCast[] = [];
  /**
   * A mapping from spellId of a spell with travel time to the last cast for that ID.
   * If we register a 'hit' during convoke, but the cast was within plausible travel time and
   * had same target, we assume the hit was due to a hardcast (and not convoke).
   * Cast entries will be cleared when we find a matching 'hit'.
   */
  lastTravelingSpellCast: Array<CastEvent | undefined> = [];

  constructor(options: Options) {
    super(options);
    this.active = this.selectedCombatant.hasCovenant(COVENANTS.NIGHT_FAE.id);

    this.spellsPerCast =
      this.selectedCombatant.spec === SPECS.RESTORATION_DRUID ? SPELLS_CAST_RESTO : SPELLS_CAST;

    // watch for convokes
    this.addEventListener(
      Events.applybuff.by(SELECTED_PLAYER).spell(SPELLS.CONVOKE_SPIRITS),
      this.onConvoke,
    );

    // watch for spell 'hits'
    this.addEventListener(
      Events.applybuff.by(SELECTED_PLAYER).spell(CONVOKE_BUFF_SPELLS),
      this.onHit,
    );
    this.addEventListener(
      Events.refreshbuff.by(SELECTED_PLAYER).spell(CONVOKE_BUFF_SPELLS),
      this.onHit,
    );
    this.addEventListener(
      Events.applybuffstack.by(SELECTED_PLAYER).spell(CONVOKE_BUFF_SPELLS),
      this.onHit,
    );

    this.addEventListener(
      Events.applydebuff.by(SELECTED_PLAYER).spell(CONVOKE_DEBUFF_SPELLS),
      this.onHit,
    );
    this.addEventListener(
      Events.refreshdebuff.by(SELECTED_PLAYER).spell(CONVOKE_DEBUFF_SPELLS),
      this.onHit,
    );
    this.addEventListener(
      Events.applydebuffstack.by(SELECTED_PLAYER).spell(CONVOKE_DEBUFF_SPELLS),
      this.onHit,
    );

    this.addEventListener(Events.heal.by(SELECTED_PLAYER).spell(CONVOKE_HEAL_SPELLS), this.onHit);

    this.addEventListener(
      Events.damage.by(SELECTED_PLAYER).spell(CONVOKE_DAMAGE_SPELLS),
      this.onHit,
    );

    // watch for travel time casts
    this.addEventListener(
      Events.cast.by(SELECTED_PLAYER).spell(SPELLS_WITH_TRAVEL_TIME),
      this.onTravelTimeCast,
    );
  }

  onConvoke(_: ApplyBuffEvent) {
    this.cast += 1;
    this.convokeTracker[this.cast] = { spellIdToCasts: [], form: 'No Form' };
  }

  onHit(event: AbilityEvent<any> & TargettedEvent<any>) {
    const spellId = event.ability.guid;

    const isNewHit = this.isNewHit(spellId);
    const isTravelTime = SPELL_IDS_WITH_TRAVEL_TIME.includes(spellId);
    const wasProbablyHardcast = isTravelTime && this.wasProbablyHardcast(event);
    const isConvoking = this.isConvoking();

    if (isNewHit && isConvoking && !wasProbablyHardcast) {
      // spell hit during convoke and was due to convoke
      this.registerConvokedSpell(spellId);
    } else if (isTravelTime && !wasProbablyHardcast && this.isWithinTravelFromConvoke()) {
      // spell hit soon after convoke but was due to convoke
      this.registerConvokedSpell(spellId);
    }

    if (isTravelTime) {
      this.onTravelTimeHit(spellId);
    }
  }

  /**
   * Tallies the given spellId as a convoked cast during the current convoke.
   */
  registerConvokedSpell(spellId: number): void {
    const currentConvoke = this.convokeTracker[this.cast];
    if (!currentConvoke.spellIdToCasts[spellId]) {<commit_msg>Re-added form to Convoke<commit_after>import SPELLS from 'common/SPELLS';
import COVENANTS from 'game/shadowlands/COVENANTS';
import SPECS from 'game/SPECS';
import { SpellLink } from 'interface';
import Analyzer, { Options, SELECTED_PLAYER } from 'parser/core/Analyzer';
import Events, {
  AbilityEvent,
  ApplyBuffEvent,
  CastEvent,
  TargettedEvent,
} from 'parser/core/Events';
import Abilities from 'parser/core/modules/Abilities';
import BoringSpellValueText from 'parser/ui/BoringSpellValueText';
import Statistic from 'parser/ui/Statistic';
import STATISTIC_CATEGORY from 'parser/ui/STATISTIC_CATEGORY';
import STATISTIC_ORDER from 'parser/ui/STATISTIC_ORDER';
import React from 'react';

import ActiveDruidForm, { DruidForm } from '../core/ActiveDruidForm';

/*
 * For several spells we include multiple IDs for the same spell -
 * probably only one ID is being used but as of this writing neither Abelito nor Sref
 * has cared enough to find out which.
 */

/** All convokable spells that 'hit' with a buff application */
const CONVOKE_BUFF_SPELLS = [
  SPELLS.REJUVENATION,
  SPELLS.REJUVENATION_GERMINATION,
  SPELLS.REGROWTH,
  SPELLS.IRONFUR,
  SPELLS.TIGERS_FURY,
  SPELLS.FERAL_FRENZY_TALENT,
  SPELLS.WILD_GROWTH,
  SPELLS.FLOURISH_TALENT,
  SPELLS.STARFALL_CAST, // apparently this is also the ID for the buff
];
/** All convokable spells that 'hit' with a debuff application */
const CONVOKE_DEBUFF_SPELLS = [
  SPELLS.MOONFIRE,
  SPELLS.MOONFIRE_BEAR,
  SPELLS.MOONFIRE_FERAL,
  SPELLS.RAKE_BLEED,
  SPELLS.THRASH_BEAR_DOT,
];
/** All convokable spells that 'hit' with direct healing */
const CONVOKE_HEAL_SPELLS = [SPELLS.SWIFTMEND];
/** All convokable spells that 'hit' with direct damage */
const CONVOKE_DAMAGE_SPELLS = [
  SPELLS.WRATH,
  SPELLS.WRATH_MOONKIN,
  SPELLS.STARSURGE_AFFINITY,
  SPELLS.STARSURGE_MOONKIN,
  SPELLS.FULL_MOON,
  SPELLS.FEROCIOUS_BITE,
  SPELLS.SHRED,
  SPELLS.MANGLE_BEAR,
  SPELLS.PULVERIZE_TALENT,
];
/** Convokable spells that have travel time */
const SPELLS_WITH_TRAVEL_TIME = [
  SPELLS.STARSURGE_AFFINITY,
  SPELLS.STARSURGE_MOONKIN,
  SPELLS.FULL_MOON,
  SPELLS.WRATH,
  SPELLS.WRATH_MOONKIN,
];
const SPELL_IDS_WITH_TRAVEL_TIME = SPELLS_WITH_TRAVEL_TIME.map((s) => s.id);
/** Convokable spells that can hit multiple targets */
const SPELL_IDS_WITH_AOE = [
  SPELLS.MOONFIRE.id,
  SPELLS.MOONFIRE_BEAR.id,
  SPELLS.MOONFIRE_FERAL.id,
  SPELLS.FULL_MOON.id,
  SPELLS.THRASH_BEAR_DOT.id,
  SPELLS.WILD_GROWTH.id,
];

const SPELLS_CAST = 16;
const SPELLS_CAST_RESTO = 12;

const AOE_BUFFER_MS = 100;
const AFTER_CHANNEL_BUFFER_MS = 50;
const TRAVEL_BUFFER_MS = 2_000;

/**
 * **Convoke the Spirits**
 * Covenant - Night Fae
 *
 * Call upon the Night Fae for an eruption of energy,
 * channeling a rapid flurry of 16 (12 for Resto) Druid spells and abilities over 4 sec.
 */
class ConvokeSpirits extends Analyzer {
  static dependencies = {
    abilities: Abilities,
    activeDruidForm: ActiveDruidForm,
  };

  protected abilities!: Abilities;
  protected activeDruidForm!: ActiveDruidForm;

  /** The number of times Convoke has been cast */
  cast: number = 0;
  /** The number of spells cast by a full Convoke channel */
  spellsPerCast: number;
  /** Timestamp of the most recent AoE spell hit registered during Convoke - used to avoid double counts */
  mostRecentAoeTimestamp = 0;
  /** Mapping from convoke cast number to a tracker for that cast - note that index zero will always be empty */
  convokeTracker: ConvokeCast[] = [];
  /**
   * A mapping from spellId of a spell with travel time to the last cast for that ID.
   * If we register a 'hit' during convoke, but the cast was within plausible travel time and
   * had same target, we assume the hit was due to a hardcast (and not convoke).
   * Cast entries will be cleared when we find a matching 'hit'.
   */
  lastTravelingSpellCast: Array<CastEvent | undefined> = [];

  constructor(options: Options) {
    super(options);
    this.active = this.selectedCombatant.hasCovenant(COVENANTS.NIGHT_FAE.id);

    this.spellsPerCast =
      this.selectedCombatant.spec === SPECS.RESTORATION_DRUID ? SPELLS_CAST_RESTO : SPELLS_CAST;

    // watch for convokes
    this.addEventListener(
      Events.applybuff.by(SELECTED_PLAYER).spell(SPELLS.CONVOKE_SPIRITS),
      this.onConvoke,
    );

    // watch for spell 'hits'
    this.addEventListener(
      Events.applybuff.by(SELECTED_PLAYER).spell(CONVOKE_BUFF_SPELLS),
      this.onHit,
    );
    this.addEventListener(
      Events.refreshbuff.by(SELECTED_PLAYER).spell(CONVOKE_BUFF_SPELLS),
      this.onHit,
    );
    this.addEventListener(
      Events.applybuffstack.by(SELECTED_PLAYER).spell(CONVOKE_BUFF_SPELLS),
      this.onHit,
    );

    this.addEventListener(
      Events.applydebuff.by(SELECTED_PLAYER).spell(CONVOKE_DEBUFF_SPELLS),
      this.onHit,
    );
    this.addEventListener(
      Events.refreshdebuff.by(SELECTED_PLAYER).spell(CONVOKE_DEBUFF_SPELLS),
      this.onHit,
    );
    this.addEventListener(
      Events.applydebuffstack.by(SELECTED_PLAYER).spell(CONVOKE_DEBUFF_SPELLS),
      this.onHit,
    );

    this.addEventListener(Events.heal.by(SELECTED_PLAYER).spell(CONVOKE_HEAL_SPELLS), this.onHit);

    this.addEventListener(
      Events.damage.by(SELECTED_PLAYER).spell(CONVOKE_DAMAGE_SPELLS),
      this.onHit,
    );

    // watch for travel time casts
    this.addEventListener(
      Events.cast.by(SELECTED_PLAYER).spell(SPELLS_WITH_TRAVEL_TIME),
      this.onTravelTimeCast,
    );
  }

  onConvoke(_: ApplyBuffEvent) {
    this.cast += 1;
    this.convokeTracker[this.cast] = { spellIdToCasts: [], form: this.activeDruidForm.form };
  }

  onHit(event: AbilityEvent<any> & TargettedEvent<any>) {
    const spellId = event.ability.guid;

    const isNewHit = this.isNewHit(spellId);
    const isTravelTime = SPELL_IDS_WITH_TRAVEL_TIME.includes(spellId);
    const wasProbablyHardcast = isTravelTime && this.wasProbablyHardcast(event);
    const isConvoking = this.isConvoking();

    if (isNewHit && isConvoking && !wasProbablyHardcast) {
      // spell hit during convoke and was due to convoke
      this.registerConvokedSpell(spellId);
    } else if (isTravelTime && !wasProbablyHardcast && this.isWithinTravelFromConvoke()) {
      // spell hit soon after convoke but was due to convoke
      this.registerConvokedSpell(spellId);
    }

    if (isTravelTime) {
      this.onTravelTimeHit(spellId);
    }
  }

  /**
   * Tallies the given spellId as a convoked cast during the current convoke.
   */
  registerConvokedSpell(spellId: number): void {
    const currentConvoke = this.convokeTracker[this.cast];
    if (!currentConvoke.spellIdToCasts[spellId]) {
{'fraction_non_alphanumeric': 0.0974712988669618, 'fraction_numerical': 0.003151496961056502, 'mean_word_length': 4.003003003003003, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 11, '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': '2006149', 'n_tokens_mistral': 4861, 'n_tokens_neox': 4739, 'n_words': 1040}