apps/action-dialog.js

import { logger } from '../logger.js';
import { MODULE } from '../module.js';

/**
 * Simple form app that allows you to list abilities for a combatant
 * 
 * options = {
 *  action, bonus, reaction, legendary, lair, special
 * }
 */
export class ActionDialog extends Dialog {
  /** @override */
  constructor(combatants, options = { action : true, bonus : true, reaction : true, legendary : true, lair : true, special : true, id: ActionDialog.DEFAULT_ID }){
    /*
      Build Options
    */
    super(options);
    foundry.utils.mergeObject(this.options, options);

    /*
      Build Data
     */
    this.data = {};
    this.data.title = options?.title ?? "Action Dialog";
    this.data.buttons = { 
      close: { label: MODULE.format("Close"), callback: ActionDialog.storePosition}
    };
    this.data.default = "close";
    this.data.combatants = combatants;
    mergeObject(this.position, ActionDialog._lastPosition.get(this.options.id) ?? {});
  }

  static DEFAULT_ID = 'dnd5e-helpers-action-dialog';
  static _lastPosition = new Map(); 

  static storePosition(html) {
    const id = html.attr('id');
    const position = html.position();
    ActionDialog._lastPosition.set(id, {top: position.top, left: position.left});
  }

  /** @inheritdoc */
  static get defaultOptions() {
    return foundry.utils.mergeObject(super.defaultOptions, {
      template : `modules/${MODULE.data.name}/templates/ActionDialog.html`,
      classes: ["dnd5ehelpers","action-dialog"], 
      resizable: false,
      id: ActionDialog.DEFAULT_ID,
      jQuery : true,
      height: "100%",
      close: ActionDialog.storePosition,
      popOutModuleDisable: true,
    });
  }

  get title() {
    return this.data.title;
  }

  _generateCombatantData() {
    return this.data.combatants.map((combatant) => {
      return {
        id : combatant.id,
        combatId: combatant.combat.id,
        img : combatant.img,
        name : combatant.name,
        economy : this.getCombatantItemData(combatant),
        items : {
          action : this.options?.action ? this.getCombatantItemData(combatant, "action") : undefined,
          bonus : this.options?.bonus ? this.getCombatantItemData(combatant, "bonus") : undefined,
          reaction : this.options?.reaction ? this.getCombatantItemData(combatant, "reaction"): undefined,
          legendary : this.options?.legendary ? this.getCombatantItemData(combatant, "legendary") : undefined,
          lair : this.options?.lair ? this.getCombatantItemData(combatant, "lair") : undefined,
          special : this.options?.special ? this.getCombatantItemData(combatant, "special") : undefined,
        }
      }
    });

  }

  getCombatantItemData(combatant, type){
    return combatant.actor.items
      .filter(item => item?.data?.data?.activation?.type === type)
      .map( (item) => {
        /* common data */
        let data = {
          name : item.name, 
          id : item.id,
          activation : mergeObject(getProperty(item,'data.data.activation'), {canUse: true}),
          description : getProperty(item ,'data.data.description.value'), 
          img : item.img, 
          uuid : item.uuid,
        }

        /* special case data -- legendary actions REQUIRE available resources */
        switch(type){
          case 'legendary':
            mergeObject(data.activation, { available : getProperty(combatant.actor, 'data.data.resources.legact.value') } ); 
            data.activation.canUse = data.activation.available >= data.activation.cost;
            break;
        }

        return data;
      });
  }

  getCombatantEconomyData(combatant){
    return {
      action : 1,
      bonus : 1,
      reaction : 1,
      legendary : combatant.actor.data.data.resources?.legact,
      lair : combatant.actor.data.data.resources?.lair
    }
  }

  getData(options) {
    let data = super.getData(options);  
    data.combatants = this._generateCombatantData();
    return data;
  }

  update(){
    return this.render(true);
  }

  setPosition(options = {}) {
    options.height = '100%'
    const position = super.setPosition(options);
    return position;
  }


  /*
    Overwrite
  */
  activateListeners(html){
    super.activateListeners(html);

    /* register img and item clicks for each combatant */
    this.data.combatants.forEach( (combatant) => {
      html.find(`#${combatant.id}`).on('click', this._onImgClick);
    });

    html.find('.item').on('click', this._onButtonClick.bind(this));
  }


  _onImgClick(event){
    logger.debug("_onImgClick | DATA | ", { event });
    const combatantID = event.currentTarget.id;
    if(!combatantID || !game.combats.active) return;

    const token = game.combats.active.combatants.get(combatantID)?.token.object;
    if(!token) return;

    token.control({ releaseOthers : true });
    canvas.animatePan({ x : token.x, y : token.y });
  }

  async _onButtonClick(event){
    const itemUUID = event.currentTarget.id;
    const item = await fromUuid(itemUUID);

    if(!item || !(item instanceof Item)) return;

    await item.roll();
    logger.debug("onButtonClick | DATA | ", { event, itemUUID, item });

    /* close the dialog if only one combatant can do a thing here */
    if (this.data.combatants.length == 1) {
      return this.close();
    } else {
      return this.update();
    }
  }
}