modules/LegendaryActionManagement.js

import { MODULE } from '../module.js';
import { logger } from '../logger.js';
import { ActionDialog } from '../apps/action-dialog.js'
import { queueUpdate } from './update-queue.js'

const NAME = "LegendaryActionManagement";

/** @todo need to support an array of actors, not just a single one */
class LegendaryActionDialog extends ActionDialog {

  /** @override */
  constructor(combatants) {
    
    /* Localize title */
    const title = MODULE.format("DND5E.LegAct");

    /* construct an action dialog using only legendary actions */
    super(combatants, {legendary: true, title, id:'legact-action-dialog'});
  }

}


/**
 * LegendaryActionManagement
 *  This Module strictly manages Legendary action economy per the dnd5e rules.
 */
export class LegendaryActionManagement{

  /** @public */
  static register(){
    this.settings();
    this.hooks();
  }

  /** @public */
  static settings(){
    const config = false;
    const settingsData = {
      legendaryActionRecharge : {
        scope : "world", config, group: "npc-features", default: false, type: Boolean,
      },
      legendaryActionHelper : {
        scope : "world", config, group: "npc-features", default: false, type: Boolean,
      }
    };

    MODULE.applySettings(settingsData);
  }

  /** @public */
  static hooks() {
    Hooks.on('createCombatant', LegendaryActionManagement._createCombatant);
    Hooks.on('updateCombat', LegendaryActionManagement._updateCombat);
  }

  /**
   * Check Combatant for Legendary Actions, store information on the combat.
   *  actorid, [itemid], 
   * 
   * @param {Combatant} combatant 
   */
  static _createCombatant(combatant) {

    /* do not run if not the first GM, but always flag regardless of enable state */
    if (!MODULE.isFirstGM()) return;

    const hasLegendary = !!combatant.actor?.items.find((i) => i.data?.data?.activation?.type === "legendary")

    /* flag this combatant as a legendary actor for quick filtering */
    if (hasLegendary){
      logger.debug(`${NAME} | flagging as legendary combatant: ${combatant.name}`, combatant);
      queueUpdate( async () => await combatant.setFlag(MODULE.data.name, 'hasLegendary', true) )
    }

  }

  /** @private */
  /* 
   * @param {*} combat 
   * @param {*} changed 
   * @returns 
   */
  static _updateCombat(combat, changed) {

    /* do not run if not the first GM or the feature is not enabled */
    if (!MODULE.isFirstGM()) return;

    /* only trigger legendary actions on a legit turn change */
    if (!MODULE.isTurnChange(combat, changed)) return;

    const previousId = combat.previous?.combatantId;

    /* run the leg action helper dialog if enabled */
    if (MODULE.setting('legendaryActionHelper')) {

      /* Collect legendary combatants (but not the combatant whose turn just ended) */
      let legendaryCombatants = combat.combatants.filter( combatant => combatant.getFlag(MODULE.data.name, 'hasLegendary') && combatant.id != previousId );

      /* only prompt for actions from alive creatures with leg acts remaining */
      legendaryCombatants = legendaryCombatants.filter( combatant => getProperty(combatant.actor, 'data.data.resources.legact.value') ?? 0 > 0 );
      legendaryCombatants = legendaryCombatants.filter( combatant => getProperty(combatant.actor, 'data.data.attributes.hp.value') ?? 0 > 0 );

      /* send list of combantants to the action dialog subclass */
      if (legendaryCombatants.length > 0) {
        LegendaryActionManagement.showLegendaryActions(legendaryCombatants);
      }

    }

    /* recharge the legendary actions, if enabled */
    if (MODULE.setting('legendaryActionRecharge')) {

      /* once the dialog for the "in-between" turn has been rendered, recharge legendary actions
       * for the creature whose turn just ended. This is not entirely RAW, but due to order
       * of operations it must be done 'late'. Since a creature cannot use a legendary
       * action at the end of its own turn, nor on its own turn, recharging at end of turn
       * rather than beginning of turn is functionally equivalent. */
      if (previousId) {

        /* does the previous combatant have legendary actions? */
        const previousCombatant = combat.combatants.get(previousId);
        if(!!previousCombatant?.getFlag(MODULE.data.name, 'hasLegendary')) {
          LegendaryActionManagement.rechargeLegendaryActions(previousCombatant);
        }
      }

    }
  }

  /** @private */
  /*
   * Generates the action dialog for legendary actions 
   * @param {Array of Object} combatants
   */
  static showLegendaryActions(combatants) {
    new LegendaryActionDialog(combatants).render(true);
  }

  /** @private */
  /*
   * @param {Combatant} combatant
   *
   * @return {Actor5e} modified actor document
   */
  static rechargeLegendaryActions(combatant) {

    if (!combatant.actor || !combatant.token) {
      return;
    }

    let legact = getProperty(combatant.actor, 'data.data.resources.legact');

    /* does this creature have the legendary action counter? */
    if (!!legact && legact.value !== null) {

      /* only reset if needed */
      if (legact.value < legact.max) {
        ui.notifications.info(game.i18n.format("DND5EH.CombatLegendary_notification", {max: legact.max, tokenName: combatant.token.name}))

        /* send the reset update and sheet refresh */
        queueUpdate( async () => {
          const newActor = await combatant.actor.update({'data.resources.legact.value': legact.max});
          newActor.sheet.render(false);
        });

      }
    }

    return;
  }

}