<|start_filename|>scripts/actions/exampleActionHandler.js<|end_filename|>
import {ActionHandler} from './actionHandler.js';

export class ExampleActionHandler extends ActionHandler {
    constructor (filterManager) {
        super(filterManager);
    }

    /** @override */
    async doBuildActionList(token, multipleTokens) {
        let result = this.initializeEmptyActionList();

        if (!token)
            return result;

        let tokenId = token.data._id;

        result.tokenId = tokenId;

        let actor = token.actor;

        if (!actor)
            return result;

        result.actorId = actor._id;
    
        let inventoryCategory = this._buildInventoryCategory(actor, tokenId);
        
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.inventory'), inventoryCategory); // combines the inventory category with the list with the title given by the second argument.

        return result;
    }

    _buildInventoryCategory(actor, tokenId) {
        let result = this.initializeEmptyCategory('inventory'); // string given is an ID not a title.
        let macroType = 'item';

        let items = actor.items;
        
        let weapons = items.filter(i => i.data.type === 'weapons' && i.data.data.equipped);
        let weaponsActions = this._produceMap(tokenId, weapons, macroType);
        let weaponsSubcategory = this.initializeEmptySubcategory()
        weaponsSubcategory.actions = weaponsActions;
        this._combineSubcategoryWithCategory(result, 'weapons', weaponsSubcategory);

        let armor = items.filter(i => i.data.type === 'armor' && i.data.data.equipped);
        let armorActions = this._produceMap(tokenId, armor, macroType);
        let armorSubcategory = this.initializeEmptySubcategory()
        armorSubcategory.actions = armorActions;
        this._combineSubcategoryWithCategory(result, 'armor', armorSubcategory);
        
        let consumables = items.filter(i => i.data.type === 'consumables');
        let consumablesActions = this._produceMap(tokenId, consumables, macroType);
        let consumablesSubcategory = this.initializeEmptySubcategory()
        consumablesSubcategory.actions = consumablesActions;
        this._combineSubcategoryWithCategory(result, 'consumables', consumablesSubcategory);

        return result;
    }

    /** @private */
    _produceMap(tokenId, itemSet, macroType) {
        return itemSet.filter(i => !!i).map(i => {
            let encodedValue = [macroType, tokenId, i.data.id].join(this.delimiter);
            return { name: i.name, encodedValue: encodedValue, id: i.data.id };
        });
    }
}

<|start_filename|>scripts/rollHandlers/symbaroum/symbaroum-base.js<|end_filename|>
import { RollHandler } from '../rollHandler.js';
import * as settings from '../../settings.js';

export class RollHandlerBaseSymbaroum extends RollHandler {
    constructor() {
        super();
    }
    
    doHandleActionEvent(event, encodedValue) {
        let payload = encodedValue.split('|');
        if (payload.length != 3) {
            super.throwInvalidValueErr();
        }

        let macroType = payload[0];
        let tokenId = payload[1];
        let itemId = payload[2];

        let actor = super.getActor(tokenId);
        switch (macroType) {
            case 'weapon':
                this._handleWeapon(macroType, event, actor, itemId);
                break;
            case 'armor':
                this._handleArmor(macroType, event, actor, itemId);
                break;
            case 'ability':
                this._handleAbility(macroType, event, actor, itemId);
                break;
            case 'mysticalPower':
                this._handleMysticalPowers(macroType, event, actor, itemId);
                break;
            case 'attribute':
                this._handleAttributes(macroType, event, actor, itemId);
                break;
        }
        
    }

    _handleWeapon(macroType, event, actor, actionId) {

        let usedItem = actor.data.data.weapons.filter(item => item.id === actionId);
        actor.rollWeapon(usedItem[0]);
    }

    _handleArmor(macroType, event, actor, actionId) {

        actor.rollArmor();
    }

    _handleAbility(macroType, event, actor, actionId) {

        let usedPower = actor.items.filter(item => item.id === actionId);
        actor.usePower(usedPower[0]);
    }

    _handleMysticalPowers(macroType, event, actor, actionId) {
        
        let usedPower = actor.items.filter(item => item.id === actionId);
        actor.usePower(usedPower[0]);
    }

    _handleAttributes(macroType, event, actor, actionId) {
        actor.rollAttribute(actionId);
    }
}

<|start_filename|>scripts/actions/alienrpg/alienrpg-actions.js<|end_filename|>
import { ActionHandler } from '../actionHandler.js';
import * as settings from '../../settings.js';

export class ActionHandlerAlienrpg extends ActionHandler {
  constructor(filterManager, categoryManager) {
    super(filterManager, categoryManager);
  }

  /** @override */
  async doBuildActionList(token, multipleTokens) {
    let attributes = {};
    let skills = {};
    let weapons = {};
    let inventory = {};
    let talents = {};
    let agenda = {};
    let consumables = {};
    let power = {};
    let conditions = {};
    let utility = {};
    let attack = {};

    let result = this.initializeEmptyActionList();

    if (multipleTokens) {
      this._buildMultipleTokenList(result);
      return result;
    }

    if (!token) return result;

    let tokenId = token.data._id;
    result.tokenId = tokenId;

    let actor = token.actor;

    if (!actor) return result;

    let legitimateActors = ['character', 'synthetic', 'creature'];
    let actorType = actor.data.type;
    if (!legitimateActors.includes(actorType)) return result;

    result.actorId = actor._id;
    if (actorType === 'character' || actorType === 'synthetic') {
      attributes = this._getAttributes(actor, tokenId);
      skills = this._getSkills(actor, tokenId);
      weapons = this._getWeaponsList(actor, tokenId);
      inventory = this._getItemsList(actor, tokenId);
      talents = this._getTalentsList(actor, tokenId);
      agenda = this._getAgendaList(actor, tokenId);
      consumables = this._getConsumablesList(actor, tokenId);
      power = this._getPowerList(actor, tokenId);
      conditions = this._getConditionsList(actor, tokenId);
      utility = this._getUtilityList(actor, tokenId);
    } else {
      attributes = this._getCreatureAttributes(actor, tokenId);
      attack = this._getAttackList(actor, tokenId);
      utility = this._getUtilityList(actor, tokenId);
    }
    // // console.log('ActionHandlerAlienRPG -> doBuildActionList -> utility', utility);
    switch (actor.data.type) {
      case 'character':
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.attributes'), attributes);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.skills'), skills);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.weapons'), weapons);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.inventory'), inventory);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.talents'), talents);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.settings.alienrpg.agenda'), agenda);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.settings.alienrpg.consumables'), consumables);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.settings.alienrpg.power'), power);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.settings.alienrpg.conditions'), conditions);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.utility'), utility);
        this._setFilterSuggestions(actor);
        if (settings.get('showHudTitle')) result.hudTitle = token.data?.name;
        break;
      case 'synthetic':
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.attributes'), attributes);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.skills'), skills);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.weapons'), weapons);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.inventory'), inventory);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.talents'), talents);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.settings.alienrpg.agenda'), agenda);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.settings.alienrpg.power'), power);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.utility'), utility);
        this._setFilterSuggestions(actor);
        if (settings.get('showHudTitle')) result.hudTitle = token.data?.name;
        break;
      case 'creature':
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.attributes'), attributes);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.attack'), attack);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.utility'), utility);
        this._setFilterSuggestions(actor);
        if (settings.get('showHudTitle')) result.hudTitle = token.data?.name;
        break;

      default:
        break;
    }

    return result;
  }

  _getWeaponsList(actor, tokenId) {
    let macroType = 'weapon';
    let result = this.initializeEmptyCategory('items');

    let subcategory = this.initializeEmptySubcategory();
    subcategory.actions = this._produceMap(
      tokenId,
      actor.items.filter((i) => i.type == macroType),
      macroType
    );

    this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.weapons'), subcategory);

    return result;
  }

  _getItemsList(actor, tokenId) {
    let macroType = 'item';
    let result = this.initializeEmptyCategory('items');
    let filter = ['item', 'armor'];
    let items = (actor.items ?? []).filter((a) => filter.includes(a.type)).sort(this._foundrySort);

    let armourList = items.filter((i) => i.type === 'armor');
    let armourActions = this._buildItemActions(tokenId, 'armor', armourList);
    let armour = this.initializeEmptySubcategory();
    armour.actions = armourActions;

    let itemList = items.filter((i) => i.type === 'item');
    let itemActions = this._buildItemActions(tokenId, macroType, itemList);
    let item = this.initializeEmptySubcategory();
    item.actions = itemActions;

    this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.armour'), armour);
    this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.equipment'), item);

    return result;
  }
  _getTalentsList(actor, tokenId) {
    let macroType = 'item';
    let result = this.initializeEmptyCategory('items');
    let filter = ['talent'];
    let items = (actor.items ?? []).filter((a) => filter.includes(a.type)).sort(this._foundrySort);

    let talentList = items.filter((i) => i.type === 'talent');
    let talentActions = this._buildItemActions(tokenId, macroType, talentList);
    let talent = this.initializeEmptySubcategory();
    talent.actions = talentActions;
    this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.talents'), talent);

    return result;
  }
  _getAgendaList(actor, tokenId) {
    let macroType = 'item';
    let result = this.initializeEmptyCategory('items');
    let filter = ['agenda'];
    let items = (actor.items ?? []).filter((a) => filter.includes(a.type)).sort(this._foundrySort);

    let agendaList = items.filter((i) => i.type === 'agenda');
    let agendaActions = this._buildItemActions(tokenId, macroType, agendaList);
    let agenda = this.initializeEmptySubcategory();
    agenda.actions = agendaActions;
    this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.settings.alienrpg.agenda'), agenda);

    return result;
  }

  _getConsumablesList(actor, tokenId) {
    let result = this.initializeEmptyCategory('consumables');
    let consumables = this.initializeEmptySubcategory();
    let powConsumables = this.initializeEmptySubcategory();
    let macroType = 'consumables';

    let rollableConsumables = Object.entries(actor.data.data.consumables);
    // remove Power from the list
    rollableConsumables.splice(1, 1);
    let consumablesMap = rollableConsumables.map((c) => {
      let name = this.i18n('tokenactionhud.settings.alienrpg.consumables' + c[0]);
      let id = c[0];
      let encodedValue = [macroType, tokenId, id, name].join(this.delimiter);
      return { name: name, encodedValue: encodedValue, id: id };
    });
    consumables.actions = this._produceMap(tokenId, consumablesMap, macroType);
    this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.settings.alienrpg.consumables'), consumables);

    return result;
  }
  _getPowerList(actor, tokenId) {
    let result = this.initializeEmptyCategory('power');
    let power = this.initializeEmptySubcategory();
    let powConsumables = this.initializeEmptySubcategory();
    let macroType = 'power';
    // Power consumables
    let filter = ['item'];
    let items = (actor.items ?? []).filter((a) => filter.includes(a.type)).sort(this._foundrySort);
    let powerList = items.filter((i) => i.data.totalPower > 0);

    let powerMap = powerList.map((c) => {
      let name = c.data.name;
      let id = c.data._id;
      let pencodedValue = [macroType, tokenId, name, id].join(this.delimiter);
      return { name: name, encodedValue: pencodedValue, id: id };
    });
    powConsumables.actions = this._produceMap(tokenId, powerMap, macroType);

    this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.settings.alienrpg.power'), powConsumables);

    return result;
  }

  /** @private */
  _buildItemActions(tokenId, macroType, itemList, isPassive = false) {
    let result = this._produceMap(tokenId, itemList, macroType, isPassive);

    result.forEach((i) =>
      this._addItemInfo(
        itemList.find((item) => item.data._id === i.id),
        i
      )
    );

    return result;
  }

  /** @private */
  _addItemInfo(item, itemAction) {
    itemAction.info1 = this._getQuantityData(item);
  }

  /** @private */
  _getQuantityData(item) {
    let result = '';
    let quantity = item.data.data.quantity?.value;
    if (quantity > 1) {
      result = quantity;
    }

    return result;
  }

  _getAttributes(actor, tokenId) {
    let result = this.initializeEmptyCategory('attributes');
    let attributes = this.initializeEmptySubcategory();
    let macroType = 'attribute';

    let rollableAttributes = Object.entries(actor.data.data.attributes);
    let attributesMap = rollableAttributes.map((c) => {
      let name = this.i18n('tokenactionhud.settings.alienrpg.attribute' + c[0]);
      let id = c[0];
      let encodedValue = [macroType, tokenId, id].join(this.delimiter);
      return { name: name, encodedValue: encodedValue, id: id };
    });

    attributes.actions = this._produceMap(tokenId, attributesMap, macroType);

    this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.attributes'), attributes);

    return result;
  }

  _getCreatureAttributes(actor, tokenId) {
    let result = this.initializeEmptyCategory('attributes');
    let attributes = this.initializeEmptySubcategory();
    let battributes = this.initializeEmptySubcategory();
    let macroType = 'creatureattribute';

    let rollableAttributes = Object.entries(actor.data.data.attributes);
    let attributesMap = rollableAttributes.map((c) => {
      let name = this.i18n('tokenactionhud.settings.alienrpg.attribute' + c[0]);
      let id = c[0];
      let encodedValue = [macroType, tokenId, id].join(this.delimiter);
      return { name: name, encodedValue: encodedValue, id: id };
    });
    let rollableGeneral = Object.entries(actor.data.data.general);
    rollableGeneral.splice(2, 3);

    let generalMap = rollableGeneral.map((c) => {
      let name = this.i18n('tokenactionhud.settings.alienrpg.attribute' + c[0]);
      let id = c[0];
      let encodedValue = [macroType, tokenId, id].join(this.delimiter);
      return { name: name, encodedValue: encodedValue, id: id };
    });

    attributes.actions = this._produceMap(tokenId, attributesMap, macroType);
    battributes.actions = this._produceMap(tokenId, generalMap, macroType);

    this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.attributes'), attributes);
    this._combineSubcategoryWithCategory(result, '', battributes);

    return result;
  }

  _getSkills(actor, tokenId) {
    let result = this.initializeEmptyCategory('skills');
    let attributes = this.initializeEmptySubcategory();
    let macroType = 'skill';

    let rollableSkills = Object.entries(actor.data.data.skills);
    let skillMap = rollableSkills.map((c) => {
      let name = this.i18n('tokenactionhud.settings.alienrpg.skill' + c[0]);
      let id = c[0];
      let encodedValue = [macroType, tokenId, id].join(this.delimiter);
      return { name: name, encodedValue: encodedValue, id: id };
    });

    attributes.actions = this._produceMap(tokenId, skillMap, macroType);

    this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.skills'), attributes);

    return result;
  }

  _buildMultipleTokenList(list) {
    list.tokenId = 'multi';
    list.actorId = 'multi';

    const allowedTypes = ['monster', 'character'];
    let actors = canvas.tokens.controlled.map((t) => t.actor).filter((a) => allowedTypes.includes(a.data.type));

    this._addMultiUtilities(list, list.tokenId, actors);
  }

  _getAttackList(actor, tokenId) {
    let result = this.initializeEmptyCategory('attack');
    let attack = this.initializeEmptySubcategory();
    let macroType = 'attack';

    if (actor.data.type === 'creature') {
      let header = this.initializeEmptySubcategory();

      let creatureAttack = [];
      let cAttackValue = ['creatureAttack', tokenId, 'creatureAttack', ''].join(this.delimiter);
      creatureAttack = { id: 'creatureAttack', name: this.i18n('tokenactionhud.settings.alienrpg.creatureAttack'), encodedValue: cAttackValue };
      header.actions.push(creatureAttack);

      let acidSplash = [];
      let aSplashValue = ['acidSplash', tokenId, 'acidSplash', ''].join(this.delimiter);
      acidSplash = { id: 'acidSplash', name: this.i18n('tokenactionhud.settings.alienrpg.attributeacidSplash'), encodedValue: aSplashValue };
      header.actions.push(acidSplash);
      this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.settings.alienrpg.attributeacidSplash'), header);
    }

    this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.attack'), attack);

    return result;
  }

  _getUtilityList(actor, tokenId) {
    let result = this.initializeEmptyCategory('utility');
    let utility = this.initializeEmptySubcategory();
    let macroType = 'utility';
    let header = this.initializeEmptySubcategory();
    let headerActions = [];
    let health = 0;
    switch (actor.data.type) {
      case 'character':
        health = actor.data.data.header?.health;
        if (health) headerActions.push(this._getHeaderActions(tokenId, 'health', this.i18n('tokenactionhud.settings.alienrpg.health'), health.value, '10'));
        header.actions = headerActions;

        let stress = actor.data.data.header?.stress;
        if (stress) headerActions.push(this._getHeaderActions(tokenId, 'stress', this.i18n('tokenactionhud.settings.alienrpg.stresspoints'), stress.value, '10'));
        header.actions = headerActions;
        let stressActions = [];
        let stressValue = ['rollStress', tokenId, 'rollStress', ''].join(this.delimiter);
        stressActions = { id: 'rollStress', name: this.i18n('tokenactionhud.settings.alienrpg.rollStress'), encodedValue: stressValue };
        header.actions.push(stressActions);

        let rollCritActions = [];
        let rollCrit = ['rollCrit', tokenId, 'rollCrit', ''].join(this.delimiter);
        rollCritActions = { id: 'rollCrit', name: this.i18n('tokenactionhud.settings.alienrpg.rollCrit'), encodedValue: rollCrit };
        header.actions.push(rollCritActions);

        this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.settings.alienrpg.health'), header);
        break;

      case 'creature':
        health = actor.data.data.header?.health;
        if (health) headerActions.push(this._getHeaderActions(tokenId, 'health', this.i18n('tokenactionhud.settings.alienrpg.health'), health.value, '10'));
        header.actions = headerActions;
        this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.settings.alienrpg.health'), header);
        break;

      case 'synthetic':
        health = actor.data.data.header?.health;
        if (health) headerActions.push(this._getHeaderActions(tokenId, 'health', this.i18n('tokenactionhud.settings.alienrpg.health'), health.value, '10'));
        header.actions = headerActions;

        let rollSynCritActions = [];
        let rollSynCrit = ['rollCrit', tokenId, 'rollCrit', ''].join(this.delimiter);
        rollSynCritActions = { id: 'rollCrit', name: this.i18n('tokenactionhud.settings.alienrpg.rollCrit'), encodedValue: rollSynCrit };
        header.actions.push(rollSynCritActions);

        this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.settings.alienrpg.health'), header);
        if (actor.data.data.header.synthstress) {
          let stressActions = [];
          let stressValue = ['rollStress', tokenId, 'rollStress', 0].join(this.delimiter);
          stressActions = { id: 'rollStress', name: this.i18n('tokenactionhud.settings.alienrpg.rollStress'), encodedValue: stressValue };
          header.actions.push(stressActions);
        }
        break;

      default:
        break;
    }

    this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.utility'), utility);

    return result;
  }

  _getConditionsList(actor, tokenId) {
    let result = this.initializeEmptyCategory('conditions');
    let conditions = this.initializeEmptySubcategory();
    let macroType = 'conditions';

    if (actor.data.type === 'character') {
      let general = this.initializeEmptySubcategory();
      let generalActions = [];

      let starvingStateValue = [macroType, tokenId, 'toggleStarving', ''].join(this.delimiter);
      generalActions = { id: 'toggleStarving', encodedValue: starvingStateValue, name: this.i18n('tokenactionhud.settings.alienrpg.starving') };
      generalActions.cssClass = actor.data.data.general.starving.value ? 'active' : '';
      general.actions.push(generalActions);

      let dehydratedStateValue = [macroType, tokenId, 'toggleDehydrated', ''].join(this.delimiter);
      generalActions = { id: 'toggleDehydrated', encodedValue: dehydratedStateValue, name: this.i18n('tokenactionhud.settings.alienrpg.dehydrated') };
      generalActions.cssClass = actor.data.data.general.dehydrated.value ? 'active' : '';
      general.actions.push(generalActions);

      let exhaustedStateValue = [macroType, tokenId, 'toggleExhausted', ''].join(this.delimiter);
      generalActions = { id: 'toggleExhausted', encodedValue: exhaustedStateValue, name: this.i18n('tokenactionhud.settings.alienrpg.exhausted') };
      generalActions.cssClass = actor.data.data.general.exhausted.value ? 'active' : '';
      general.actions.push(generalActions);

      let freezingStateValue = [macroType, tokenId, 'toggleFreezing', ''].join(this.delimiter);
      generalActions = { id: 'toggleFreezing', encodedValue: freezingStateValue, name: this.i18n('tokenactionhud.settings.alienrpg.freezing') };
      generalActions.cssClass = actor.data.data.general.freezing.value ? 'active' : '';
      general.actions.push(generalActions);

      let panicStateValue = [macroType, tokenId, 'togglePanic', ''].join(this.delimiter);
      generalActions = { id: 'togglePanic', encodedValue: panicStateValue, name: this.i18n('tokenactionhud.settings.alienrpg.panic') };
      generalActions.cssClass = actor.data.data.general.panic.value ? 'active' : '';
      general.actions.push(generalActions);

      this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.settings.alienrpg.conditions'), general);
    }
    this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.utility'), conditions);

    return result;
  }

  _getHeaderActions(tokenId, macroType, attrName, attrVal, attrMax) {
    let id = attrName.slugify({ replacement: '_', strict: true });
    let labelValue = [macroType, tokenId, id, attrVal].join(this.delimiter);
    let headerActions = { name: attrName, encodedValue: labelValue, id: id };
    headerActions.info1 = `${attrVal}/${attrMax}`;

    return headerActions;
  }

  _addMultiUtilities(list, tokenId, actors) {
    let category = this.initializeEmptyCategory('utility');
    let macroType = 'utility';

    let utility = this.initializeEmptySubcategory();

    // if (actors.every((actor) => actor.data.type === 'character')) {
    //   let shortRestValue = [macroType, tokenId, 'stress', ''].join(this.delimiter);
    //   stress.actions.push({ id: 'stress', encodedValue: shortRestValue, name: this.i18n('tokenactionhud.settings.alienrpg.stresspoints') });
    // }

    this._combineSubcategoryWithCategory(category, this.i18n('tokenactionhud.utility'), utility);
    this._combineCategoryWithList(list, this.i18n('tokenactionhud.utility'), category);
  }

  /** @override */
  _setFilterSuggestions(id, items) {
    let suggestions = items?.map((s) => {
      return { id: s._id, value: s.name };
    });
    if (suggestions?.length > 0) this.filterManager.setSuggestions(id, suggestions);
  }

  _filterElements(categoryId, skills) {
    let filteredNames = this.filterManager.getFilteredNames(categoryId);
    let result = skills.filter((s) => !!s);
    if (filteredNames.length > 0) {
      if (this.filterManager.isBlocklist(categoryId)) {
        result = skills.filter((s) => !filteredNames.includes(s.name));
      } else {
        result = skills.filter((s) => filteredNames.includes(s.name));
      }
    }

    return result;
  }

  _produceMap(tokenId, itemSet, type) {
    return itemSet.map((i) => {
      let encodedValue = [type, tokenId, i.id, i.name.toLowerCase()].join(this.delimiter);
      let img = this._getImage(i);
      let result = { name: i.name, encodedValue: encodedValue, id: i.id, img: img };

      if (type === 'talent') result.info2 = this._getUsesData(i);

      return result;
    });
  }

  _getImage(item) {
    let result = '';
    if (settings.get('showIcons')) result = item.img ?? '';

    return !result?.includes('icons/svg/mystery-man.svg') ? result : '';
  }

  _getUsesData(item) {
    let result = '';

    let uses = item.data.data.uses;
    if (!uses) return result;

    if (!(uses.max || uses.value)) return result;

    result = uses.value ?? 0;

    if (uses.max > 0) {
      result += `/${uses.max}`;
    }

    return result;
  }
  /** @protected */
  _foundrySort(a, b) {
    if (!(a?.data?.sort || b?.data?.sort)) return 0;

    return a.data.sort - b.data.sort;
  }
}


<|start_filename|>scripts/actions/kamigakari/kg-actions.js<|end_filename|>
import {ActionHandler} from '../actionHandler.js';
import * as settings from '../../settings.js';

export class ActionHandlerKg extends ActionHandler {
    constructor (filterManager, categoryManager) {
        super(filterManager, categoryManager);
    }
	
	/** @override */
	async doBuildActionList(token, multipleTokens) {
		let result = this.initializeEmptyActionList();
		
		if (!token)
			return result;
		let tokenId = token.id;
		result.tokenId = tokenId;
		
		let actor = token.actor;
		if (!actor)
			return result;
		result.actorId = actor.id;
		
		let actorType = actor.data.type;
		if (actorType === "enemy") {
			let attack = this._getAttackOptions(actor, tokenId);
			
			this._combineCategoryWithList(result, this.i18n('tokenactionhud.attack'), attack);
		} else if (actorType === "character") {
			let mainStat = this._getMainStat(actor, tokenId);
			let subStat = this._getSubStat(actor, tokenId);
			let spiritBurn = this._getSpiritBurn(actor, tokenId);
			let talents = this._getTalents(actor, tokenId);
			let items = this._getItems(actor, tokenId);
			
			this._combineCategoryWithList(result, this.i18n('tokenactionhud.kamigakari.mainStats'), mainStat);
			this._combineCategoryWithList(result, this.i18n('tokenactionhud.kamigakari.subStats'), subStat);
			this._combineCategoryWithList(result, this.i18n('tokenactionhud.kamigakari.spiritBurn'), spiritBurn);
			this._combineCategoryWithList(result, this.i18n('tokenactionhud.talents'), talents);
			this._combineCategoryWithList(result, this.i18n('tokenactionhud.kamigakari.items'), items);

		}
		
		return result;
	}
	
	_getAttackOptions(actor, tokenId) {
		let result = this.initializeEmptyCategory('attackOption');
		
		let attack = actor.items.filter(a => a.data.type === "attackOption");
		let attackAction = this._produceMap(tokenId, attack, "attackOption");
		
		let attackCategory = this.initializeEmptySubcategory();
		attackCategory.actions = attackAction;
		this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.attack'), attackCategory);
		
		return result;
	}
	
	_getMainStat(actor, tokenId) {
		let result = this.initializeEmptyCategory('mainStat');
		
		let mainStat = Object.entries(actor.data.data.attributes).filter(a => a[1]?.add !== undefined && a[1]?.base === undefined);
		let mainStatMap = mainStat.map(a => { return { id: a[0], name: a[1].label } });
		let actions = this._produceMap(tokenId, mainStatMap, 'stat');
		let mainStatCategory = this.initializeEmptySubcategory();
		mainStatCategory.actions = actions;
		
		this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.kamigakari.mainStats'), mainStatCategory);
		
		return result;
	}
	
	_getSubStat(actor, tokenId) {
		let result = this.initializeEmptyCategory('subStat');
		
		let subStat = Object.entries(actor.data.data.attributes).filter(a => a[1]?.add === undefined && a[1]?.label !== undefined);
		let subStatMap = subStat.map(a => { return { id: a[0], name: a[1].label } });
		let actions = this._produceMap(tokenId, subStatMap, 'stat');
		let subStatCategory = this.initializeEmptySubcategory();
		subStatCategory.actions = actions;
		
		this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.kamigakari.subStats'), subStatCategory);
		
		return result;
	}
	
	_getSpiritBurn(actor, tokenId) {
		let result = this.initializeEmptyCategory('spiritBurn');
		
		let burns = [{id: "transcend", name: this.i18n('tokenactionhud.kamigakari.transcend')},
					{id: "vitalIgnition", name: this.i18n('tokenactionhud.kamigakari.vitalIgnition')},
					{id: "conceptDestruction", name: this.i18n('tokenactionhud.kamigakari.conceptDestruction')}];
		let actions = this._produceMap(tokenId, burns, 'burn');
		let burnCategory = this.initializeEmptySubcategory();
		burnCategory.actions = actions;
		
		this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.kamigakari.spiritBurn'), burnCategory);
		
		return result;
	}
	
	_getTalents(actor, tokenId) {
		let result = this.initializeEmptyCategory('talent');
		
		let start = this._getTalentsByTiming(actor, tokenId, 'Start');
		let prep = this._getTalentsByTiming(actor, tokenId, 'Prep');
		let attack = this._getTalentsByTiming(actor, tokenId, 'Attack');
		let defense = this._getTalentsByTiming(actor, tokenId, 'Defense');
		let end = this._getTalentsByTiming(actor, tokenId, 'End');
		let constant = this._getTalentsByTiming(actor, tokenId, 'Constant');
		let free = this._getTalentsByTiming(actor, tokenId, 'Free');
		
		this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.kamigakari.start'), start);
		this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.kamigakari.prep'), prep);
		this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.kamigakari.attack'), attack);
		this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.kamigakari.free'), free);
		this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.kamigakari.defense'), defense);
		this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.kamigakari.end'), end);
		this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.kamigakari.constant'), constant);
		
		return result;
	}
	
	_getTalentsByTiming(actor, tokenId, timing) {
		let talent = actor.items.filter(a => a.data.data.timing === timing);
		let talentAction = this._produceMap(tokenId, talent, "item");
		
		let talentCategory = this.initializeEmptySubcategory();
		talentCategory.actions = talentAction;
	
		return talentCategory;
	}
	
	
	_getItems(actor, tokenId) {
		let result = this.initializeEmptyCategory('item');
		
		let equipment = this._getItemByType(actor, tokenId, 'equipment');
		let sacraments = this._getItemByType(actor, tokenId, 'sacraments');
		let consumables = this._getItemByType(actor, tokenId, 'consumables');
		
		this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.equipment'), equipment);
		this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.kamigakari.sacraments'), sacraments);
		this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.kamigakari.consumables'), consumables);
		
		return result;
	}
	
	_getItemByType(actor, tokenId, type) {
		let item = actor.items.filter(a => a.data.data.class === type || a.data.type == type);
		let itemAction = this._produceMap(tokenId, item, "item");
		
		let itemCategory = this.initializeEmptySubcategory();
		itemCategory.actions = itemAction;
	
		return itemCategory;
	}
	
	/** @private */
	_produceMap(tokenId, itemSet, macroType) {
		return itemSet.filter(i => !!i).map(i => {
			let encodedValue = [macroType, tokenId, i.id].join(this.delimiter);
			let item = {name: i.name, encodedValue: encodedValue, id: i.id};
			
			if (macroType == "item" && i.data.data.quantity !== undefined)
				item.name = i.name + " X " + i.data.data.quantity;
			
			return item;
		});
	}
	
}


<|start_filename|>scripts/actions/dnd5e/dnd5e-actions-by-type.js<|end_filename|>
import {ActionHandler5e} from './dnd5e-actions.js';
import * as settings from '../../settings.js';


export class ActionHandler5eGroupByType extends ActionHandler5e {

    /** @override */
    async _buildCategories(token) {
        const api = game.modules.get('character-actions-list-5e').api;
        const actionData = await api.getActorActionsData(token.actor);

        return [
            this._buildActionCategory(token, 'actions', actionData.action),
            this._buildActionCategory(token, 'bonusActions', actionData.bonus),
            this._buildActionCategory(token, 'reactions', actionData.reaction),
            this._buildActionCategory(token, 'crewActions', actionData.crew),
            this._buildActionCategory(token, 'legendaryActions', actionData.legendary),
            this._buildActionCategory(token, 'lairActions', actionData.lair),
            this._buildActionCategory(token, 'specialActions', actionData.other),
            this._buildSkillsCategory(token),
            this._buildAbilitiesCategory(token),
            this._buildEffectsCategory(token),
            this._buildConditionsCategory(token),
            this._buildUtilityCategory(token),
        ];
    }

    _buildActionCategory(token, title, actions) {
        const category = this.initializeEmptyCategory(title);
        category.name = this.i18n(`tokenactionhud.${title}`);

        let spells = [];
        for (const a of actions) {
            if (a.type == 'spell') {
                spells.push(a);
                actions.delete(a);
            }
        }
        if (token.actor.data.type === 'character' || !settings.get('showAllNpcItems')) {
            spells = this._filterNonpreparedSpells(spells);
        }
        spells = this._sortSpellsByLevel(spells);
        spells = this._categoriseSpells(token.actor, token.id, spells);

        this._subCategorizeEquipment(
            token, category, actions,
            this.i18n('tokenactionhud.weapons'),
            i => i.type == 'weapon'
        )
        this._subCategorizeEquipment(
            token, category, actions,
            this.i18n('tokenactionhud.equipment'),
            i => i.type == 'equipment'
        )
        this._subCategorizeEquipment(
            token, category, actions,
            this.i18n('tokenactionhud.consumables'),
            i => i.type == 'consumable'
        )
        this._subCategorizeEquipment(
            token, category, actions,
            this.i18n('tokenactionhud.tools'),
            i => i.type == 'tool'
        )
        this._subCategorizeEquipment(
            token, category, actions,
            this.i18n('tokenactionhud.other'),
            i => true
        )

        for (const subCat of spells.subcategories) {
            this._combineSubcategoryWithCategory(category, subCat.name, subCat);
        }

        return category;
    }

    // Adds actions matching predicate to a new subcategory, and returns it.
    // Deletes matches from the actions set as it does so.
    _subCategorizeEquipment(token, category, actions, title, predicate) {
        const subcat = this.initializeEmptySubcategory();
        const matches = new Set();
        for (const a of actions) {
            if (predicate(a)) {
                matches.add(a);
                actions.delete(a);
            }
        }
        if (matches.size > 0) {
            subcat.actions = [...matches].map(
                i => this._buildEquipmentItem(token.id, token.actor, 'item', i)
            );
            this._combineSubcategoryWithCategory(category, title, subcat);
        }
    }
}


<|start_filename|>scripts/rollHandlers/compendiumMacroPreHandler.js<|end_filename|>

import { PreRollHandler } from './preRollHandler.js';

export class CompendiumMacroPreHandler extends PreRollHandler {
    constructor() {super();}

    /** @override */
    prehandleActionEvent(event, encodedValue) {
        let delimiter = game.tokenActionHUD.actions.delimiter;
        
        let payload = encodedValue.split(delimiter);
        
        if (payload.length != 3)
            return false;
        
        let macroType = payload[0];
        let key = payload[1];
        let actionId = payload[2];
        
        let types = ['compendiumEntry', 'compendiumMacro', 'compendiumPlaylist', 'macro'];
        if (!types.includes(macroType))
            return false;

        switch (macroType) {
            case 'compendiumEntry':
                this.handleCompendium(macroType, event, key, actionId);
                break;
            case 'compendiumMacro':
                this.handleMacroCompendium(macroType, event, key, actionId);
                break;
            case 'compendiumPlaylist':
                this.handlePlaylistCompendium(macroType, event, key, actionId);
                break;
            case 'macro':
                this.handleMacro(macroType, event, key, actionId);
                break;
            default:
                return false;
        }   

        return true;
    }

    handleCompendium(macroType, event, compendiumKey, entityId) {
        let pack = game.packs.get(compendiumKey);

        pack.getEntity(entityId).then(e => e.sheet.render(true));
    }

    handleMacroCompendium(macroType, event, compendiumKey, entityId) {
        let pack = game.packs.get(compendiumKey);

        pack.getEntity(entityId).then(e => e.execute());
    }

    async handlePlaylistCompendium(macroType, event, compendiumKey, actionId) {
        let pack = game.packs.get(compendiumKey);

        let actionPayload = actionId.split('>');
        let playlistId = actionPayload[0];
        let soundId = actionPayload[1];

        let playlist = await pack.getEntity(playlistId);
        let sound = playlist.sounds.find(s => s._id === soundId);

        AudioHelper.play({src: sound.path}, {})
    }

    handleMacro(macroType, event, tokenId, actionId) {
        game.macros.find(i => i.data._id === actionId).execute();
    }
}

<|start_filename|>scripts/settings/sw5e-settings.js<|end_filename|>
export function register(app, updateSettings) {
    game.settings.register(app,'ignorePassiveFeats', {
        name: game.i18n.localize('tokenactionhud.settings.sw5e.ignorePassiveFeats.name'),
        hint: game.i18n.localize('tokenactionhud.settings.sw5e.ignorePassiveFeats.hint'),
        scope: "client",
        config: true,
        type: Boolean,
        default: false,
        onChange: value => { updateSettings(value); }
    });

    game.settings.register(app,'hideLongerActions', {
        name: game.i18n.localize('tokenactionhud.settings.sw5e.hideLongerActions.name'),
        hint: game.i18n.localize('tokenactionhud.settings.sw5e.hideLongerActions.hint'),
        scope: "client",
        config: true,
        type: Boolean,
        default: false,
        onChange: value => { updateSettings(value); }
    });

    game.settings.register(app,'abbreviateSkills', {
        name: game.i18n.localize('tokenactionhud.settings.sw5e.abbreviateSkills.name'),
        hint: game.i18n.localize('tokenactionhud.settings.sw5e.abbreviateSkills.hint'),
        scope: "client",
        config: true,
        type: Boolean,
        default: false,
        onChange: value => { updateSettings(value); }
    });
    
    game.settings.register(app,'showEmptyItems', {
        name: game.i18n.localize('tokenactionhud.settings.sw5e.showEmptyItems.name'),
        hint: game.i18n.localize('tokenactionhud.settings.sw5e.showEmptyItems.hint'),
        scope: "client",
        config: true,
        type: Boolean,
        default: false,
        onChange: value => { updateSettings(value); }
    });
	
	    game.settings.register(app,'showConditionsCategory', {
        name: game.i18n.localize('tokenactionhud.settings.dnd5e.showConditionsCategory.name'),
        hint: game.i18n.localize('tokenactionhud.settings.dnd5e.showConditionsCategory.hint'),
        scope: "client",
        config: true,
        type: Boolean,
        default: true,
        onChange: value => { updateFunc(value); }
    });
}
    

<|start_filename|>scripts/settings/dungeonworld-settings.js<|end_filename|>
export function register(app, updateSettings) {
    game.settings.register(app,'showGmCompendiums', {
        name: game.i18n.localize('tokenactionhud.settings.dungeonworld.showGmCompendiums.name'),
        hint: game.i18n.localize('tokenactionhud.settings.dungeonworld.showGmCompendiums.hint'),
        scope: "client",
        config: true,
        type: Boolean,
        default: true,
        onChange: value => { updateSettings(value); }
    });
}
    

<|start_filename|>scripts/actions/od6s/od6s-actions.js<|end_filename|>
import {ActionHandler} from '../actionHandler.js';

export class ActionHandlerOD6S extends ActionHandler {
    constructor (filterManager, categoryManager) {
        super(filterManager, categoryManager);
    }

    /** @override */
    async doBuildActionList(token, multipleTokens) {
        let result = this.initializeEmptyActionList();

        if (!token)
            return result;

        let tokenId = token.data._id;

        result.tokenId = tokenId;

        let actor = token.actor;

        if (!actor)
            return result;

        result.actorId = actor._id;

        //let inventoryCategory = this._buildInventoryCategory(actor, tokenId);
        let combatCategory = this._buildCombatActionsCategory(actor, tokenId);
        let attributeCategory = this._buildAttributesCategory(actor, tokenId, 'attributes');
        let skillCategory = this._buildSkillsCategory(actor, tokenId, 'skills');

        //this._combineCategoryWithList(result, this.i18n('tokenactionhud.inventory'), inventoryCategory); // combines the inventory category with the list with the title given by the second argument.
        this._combineCategoryWithList(result, game.i18n.localize('OD6S.COMBAT'), combatCategory);
        this._combineCategoryWithList(result,  game.i18n.localize('OD6S.ATTRIBUTES'), attributeCategory);
        this._combineCategoryWithList(result, game.i18n.localize('OD6S.SKILLS'), skillCategory);

        return result;
    }

    _buildCombatActionsCategory(actor, tokenId) {
        let macroType = 'action';
        let result = this.initializeEmptyCategory('combatactions');
        let items = actor.items;

        const resistanceTypes = ['pr','er'];
        let resistances = [];
        for (let r of resistanceTypes) {
            let name = game.i18n.localize(actor.data.data[r].label);
            let encodedValue = [macroType, tokenId, r].join(this.delimiter);
            resistances.push({ name: name, id: r, encodedValue: encodedValue});
        }
        let resistancesSubcategory = this.initializeEmptySubcategory();
        resistancesSubcategory.actions = resistances;
        this._combineSubcategoryWithCategory(result, game.i18n.localize('OD6S.RESISTANCE'), resistancesSubcategory);

        let weapons = items.filter(i => i.data.type === 'weapon').sort((a,b) => a.name.localeCompare(b.name));
        let meleeWeapons = items.filter(i => i.data.type === 'weapon' && i.data.data.subtype === 'Melee').sort((a,b) => a.name.localeCompare(b.name)).valueOf();
        let weaponActions = this._produceMap(tokenId, weapons, macroType);
        let weaponsSubcategory = this.initializeEmptySubcategory();
        weaponsSubcategory.actions = weaponActions;
        this._combineSubcategoryWithCategory(result, game.i18n.localize('OD6S.WEAPONS'), weaponsSubcategory);

        let combatActions = [];
        for (let action in game.od6s.config.actions) {
            if (game.od6s.config.actions[action].rollable) {
                let name = game.i18n.localize(action);
                let encodedValue = [macroType, tokenId, game.od6s.config.actions[action].type].join(this.delimiter);
                combatActions.push({name: name, id: action, encodedValue: encodedValue});
                if (name === game.i18n.localize('OD6S.ACTION_PARRY')) {
                    for (let weapon = 0; weapon < meleeWeapons.length; weapon++) {
                        const name = game.i18n.localize('OD6S.ACTION_PARRY') + " (" + meleeWeapons[weapon].data.name +")";
                        const encodedValue = ['parry', tokenId, meleeWeapons[weapon].data._id].join(this.delimiter);
                        combatActions.push({name: name, encodedValue: encodedValue, id: meleeWeapons[weapon].data._id});
                    }
                }
            }
        }

        let actionsSubcategory = this.initializeEmptySubcategory();
        actionsSubcategory.actions = combatActions;
        this._combineSubcategoryWithCategory(result, game.i18n.localize('OD6S.ACTIONS'), actionsSubcategory);
        return result;
    }

    _buildAttributesCategory(actor, tokenId, categoryName) {
        let macroType = 'attribute';
        let result = this.initializeEmptyCategory('attributes');
        let attributes = actor.data.data.attributes;

        let actions = Object.entries(attributes).map(e => {
            if (e[1].score === 0) return;
            let name = game.od6s.config.attributes[e[0]].name;
            let encodedValue = [macroType, tokenId, e[0]].join(this.delimiter);
            return { name: name, id: e[0], encodedValue: encodedValue};
        })

        let attributesCategory = this.initializeEmptySubcategory();
        attributesCategory.actions = actions.filter(a => !!a);
        this._combineSubcategoryWithCategory(result, categoryName, attributesCategory);
        return result;
    }

    _buildSkillsCategory(actor, tokenId, categoryName) {
        let macroType = 'skill';
        let result = this.initializeEmptyCategory(categoryName);
        let items = actor.items;
        let skills = items.filter(i => i.data.type === 'skill');
        skills.sort((a,b) => a.name.localeCompare(b.name));
        let skillActions = this._produceMap(tokenId, skills, macroType);
        let skillsSubcategory = this.initializeEmptySubcategory();
        skillsSubcategory.actions = skillActions;
        this._combineSubcategoryWithCategory(result, categoryName, skillsSubcategory);
        return result;
    }

    /** @private */
    _produceMap(tokenId, itemSet, macroType) {
        return itemSet.filter(i => !!i).map(i => {
            let encodedValue = [macroType, tokenId, i.data._id].join(this.delimiter);
            return { name: i.name, encodedValue: encodedValue, id: i.data._id };
        });
    }
}

<|start_filename|>scripts/managers/od6s.js<|end_filename|>
import { SystemManager } from './manager.js';
import { ActionHandlerOD6S as ActionHandler } from '../actions/od6s/od6s-actions.js';
import { RollHandlerCoreOD6S as Core} from '../rollHandlers/od6s/od6s-base.js';
import * as settings from '../settings/od6s-settings.js';

export class OD6SSystemManager extends SystemManager {

    constructor(appName) {
        super(appName);
    }

    /** @override */
    doGetActionHandler(filterManager, categoryManager) {
        let actionHandler = new ActionHandler(filterManager, categoryManager);
        return actionHandler;
    }

    /** @override */
    getAvailableRollHandlers() {
        let choices = { 'core': 'Core OD6S' };

        return choices;
    }

    /** @override */
    doGetRollHandler(handlerId) {
        return new Core();
    }

    /** @override */
    doRegisterSettings(appName, updateFunc) {
        settings.register(appName, updateFunc);
    }
}

<|start_filename|>scripts/utilities/handlebars.js<|end_filename|>
import * as settings from '../settings.js';

export function registerHandlerbars() {
    Handlebars.registerHelper('cap', function(string) {
        if (!string || string.length < 1)
            return '';
        return string[0].toUpperCase() + string.slice(1); 
    });
    
    Handlebars.registerHelper('ifCond', function (v1, operator, v2, options) {

        switch (operator) {
            case '==':
                return (v1 == v2) ? options.fn(this) : options.inverse(this);
            case '===':
                return (v1 === v2) ? options.fn(this) : options.inverse(this);
            case '!=':
                return (v1 != v2) ? options.fn(this) : options.inverse(this);
            case '!==':
                return (v1 !== v2) ? options.fn(this) : options.inverse(this);
            case '<':
                return (v1 < v2) ? options.fn(this) : options.inverse(this);
            case '<=':
                return (v1 <= v2) ? options.fn(this) : options.inverse(this);
            case '>':
                return (v1 > v2) ? options.fn(this) : options.inverse(this);
            case '>=':
                return (v1 >= v2) ? options.fn(this) : options.inverse(this);
            case '&&':
                return (v1 && v2) ? options.fn(this) : options.inverse(this);
            case '||':
                return (v1 || v2) ? options.fn(this) : options.inverse(this);
            default:
                return options.inverse(this);
        }
    });
    
    Handlebars.registerHelper('activeText', function(block) {
        if (settings.get('activeCssAsText')) {
            return block.fn(this);
        }

        return block.inverse(this);
    });

    loadTemplates([
        'modules/token-action-hud/templates/category.hbs',
        'modules/token-action-hud/templates/subcategory.hbs',
        'modules/token-action-hud/templates/actionSet.hbs',
        'modules/token-action-hud/templates/action.hbs',
        'modules/token-action-hud/templates/tagdialog.hbs'
    ]);
}

<|start_filename|>scripts/actions/actionListExtender.js<|end_filename|>
import {ActionHandler} from './actionHandler.js';

export class ActionListExtender extends ActionHandler {
    constructor() {super();}

    extendActionList(actionList, multipleTokens) {}
}

<|start_filename|>scripts/settings/starwarsffg-settings.js<|end_filename|>
export function register(app, updateSettings) {
}
    

<|start_filename|>scripts/actions/entities/actionList.js<|end_filename|>
export class ActionList {
    constructor() {
        this.hudTitle = '';
        this.tokenId = '';
        this.actorId = '';
        this.categories = [];
    }
}

<|start_filename|>scripts/actions/actionHandler.js<|end_filename|>
import {ActionList} from './entities/actionList.js';
import {ActionCategory} from './entities/actionCategory.js';
import {ActionSubcategory} from './entities/actionSubcategory.js';
import {ActionSet} from './entities/actionSet.js';
import {Action} from './entities/action.js';
import * as settings from '../settings.js';
import { GenericActionHandler } from './genericActionHandler.js';

export class ActionHandler {
    i18n = (toTranslate) => game.i18n.localize(toTranslate);
    
    linkedCompendiumsGm = [];
    linkedCompendiumsPlayer = [];
    furtherActionHandlers = [];
    delimiter = '|';

    filterManager = null;

    constructor(filterManager, categoryManager) {
        this.filterManager = filterManager;
        this.categoryManager = categoryManager;
        this.genericActionHandler = new GenericActionHandler(this);
    }

    /** @public */
    async registerCoreCategories(categories) {
        await this.categoryManager.addCoreCategories(categories);
    }

    /** @public */
    async buildActionList(token, multipleTokens) {
        let actionList = await this.doBuildActionList(token, multipleTokens);
        this._addGenericCategories(token, actionList, multipleTokens);
        this._doBuildFurtherActions(token, actionList, multipleTokens);
        await this.registerCoreCategories(actionList.categories);
        await this.categoryManager.addCategoriesToActionList(this, actionList);
        return actionList;
    }

    /** @public */
    doBuildActionList(token) {};

    /** @protected */
    _addGenericCategories(token, actionList, multipleTokens) {
        if (token || multipleTokens)
            this.genericActionHandler.addGenericCategories(token, actionList, multipleTokens);
    }

    /** @protected */
    _doBuildFurtherActions(token, actionList, multipleTokens) {
        this.furtherActionHandlers.forEach(handler => handler.extendActionList(actionList, multipleTokens))
    }

    /** @public */
    addFurtherActionHandler(handler) {
        settings.Logger.debug(`Adding further action handler: ${handler.constructor.name}`)
        this.furtherActionHandlers.push(handler);
    }

    /** @public */
    initializeEmptyActionList() {
        return new ActionList();
    }

    /** @public */
    initializeEmptyActionSet() {
        return new ActionSet();
    }

    /** @public */
    initializeEmptyAction() {
        return new Action();
    }

    /** @public */
    initializeEmptyCategory(categoryId) {
        let category = new ActionCategory();
        category.id = categoryId;
        return category;
    }

    initializeEmptySubcategory(id = '') {
        let subcategory = new ActionSubcategory();
        subcategory.id = id;
        return subcategory;
    }

    /** @protected */
    _combineCategoryWithList(result, categoryName, category, push = true) {
        if (!category)
            return;

        if (category.subcategories.length === 0 && (category.core || category.core === undefined))
            return;

        if (categoryName?.length > 0)
            category.name = categoryName;

        if (push)
            result.categories.push(category);
        else
            result.categories.unshift(category);
    }

    /** @protected */
    _combineSubcategoryWithCategory(category, subcategoryName, subcategory) {
        if (!subcategory)
            return;
            
        if (subcategoryName?.length > 0)
            subcategory.name = subcategoryName;
        
        if (subcategory.subcategories.length > 0 || subcategory.actions.length > 0 || subcategory.canFilter)
            category.subcategories.push(subcategory);
        else
            settings.Logger.debug('subcategory criteria not met, disposing of', subcategoryName)
    }

    /** @protected */
    _foundrySort(a, b) {
        if (!(a?.data?.sort || b?.data?.sort))
            return 0;

        return a.data.sort - b.data.sort;
    }
}

<|start_filename|>scripts/rollHandlers/dnd5e/dnd5e-minorqol.js<|end_filename|>
import { RollHandlerBase5e } from "./dnd5e-base.js"

export class RollHandlerMinorQol5e extends RollHandlerBase5e {
    constructor() {
        super();
    }

    /** @override */
    rollItemMacro(event, tokenId, itemId) {
        let actor = super.getActor(tokenId);
        let item = actor.getOwnedItem(itemId);

        if (this.needsRecharge(item)) {
            item.rollRecharge();
            return;
        }
        
        if (this.rightClick && this.ctrl) {
            item.rollAttack();
            return;
        }
        if (this.rightClick && this.alt) {
            item.rollDamage();
            return;
        }

        var versatile;
        if (item.data.data.properties?.ver) {
            versatile = this.rightClick;
        }

        MinorQOL.doCombinedRoll({actor, item, event, versatile});
    }
}

<|start_filename|>scripts/actions/entities/actionSubcategory.js<|end_filename|>

export class ActionSubcategory {
    constructor() {
        this.id = '';
        this.name = '';
        this.info1 = '';
        this.canFilter = false;
        this.actions = [];
        this.subcategories = [];
    }
}

<|start_filename|>scripts/settings/swade-settings.js<|end_filename|>
export function register(app, updateSettings) {
    game.settings.register(app,'abbreviateAttributes', {
        name: game.i18n.localize('tokenactionhud.settings.swade.abbreviateAttributes.name'),
        hint: game.i18n.localize('tokenactionhud.settings.swade.abbreviateAttributes.hint'),
        scope: "client",
        config: true,
        type: Boolean,
        default: false,
        onChange: value => { updateFunc(value); }
    });
}
    

<|start_filename|>scripts/rollHandlers/starwarsffg/starwarsffg-base.js<|end_filename|>
import { RollHandler } from '../rollHandler.js';
import * as settings from '../../settings.js';

export class RollHandlerBaseStarWarsFFG extends RollHandler {
    constructor() {
        super();
    }
    
    doHandleActionEvent(event, encodedValue) {
        let payload = encodedValue.split('|');
        if (payload.length != 3) {
            super.throwInvalidValueErr();
        }
        
        let macroType = payload[0];
        let tokenId = payload[1];
        let actionId = payload[2];

        let actor = super.getActor(tokenId);

        switch (macroType) {
            case 'gm' :
                switch (actionId) {
                    case 'destiny': return this._rollDestiny()
                }
            case 'weapon':
                return game.ffg.DiceHelpers.rollItem(actionId, actor.id);
            case 'skill':
                return this._rollSkill(actor, actionId, event);
            case 'forcepower':
                return this._rollForcePower(actor, actionId);
        }
    }

    _rollForcePower(actor, itemId) {
        let item = actor.getOwnedItem(itemId);
        if (!item) {
          item = game.items.get(itemId);
        }
        if (!item) {
          item = ImportHelpers.findCompendiumEntityById("Item", itemId);
        }
        const forcedice = actor.data.data.stats.forcePool.max - actor.data.data.stats.forcePool.value;
        if (forcedice > 0) {
          let sheet = actor.sheet.getData();
          const dicePool = new DicePoolFFG({
            force: forcedice,
          });
          game.ffg.DiceHelpers.displayRollDialog(sheet, dicePool, `${game.i18n.localize("SWFFG.Rolling")} ${item.name}`, item.name, item);
        }
    }

    _rollDestiny() {
        game.settings.set("starwarsffg", "dPoolLight", 0);
        game.settings.set("starwarsffg", "dPoolDark", 0);
        const messageText = `<button class="ffg-destiny-roll">${game.i18n.localize("SWFFG.DestinyPoolRoll")}</button>`;

        new Map([...game.settings.settings].filter(([k, v]) => v.key.includes("destinyrollers"))).forEach((i) => {
            game.settings.set(i.module, i.key, undefined);
        });

        CONFIG.FFG.DestinyGM = game.user.id;

        ChatMessage.create({
            user: game.user._id,
            content: messageText,
        });
    }

    _rollSkill(actor, skillname, event) {
        let difficulty = 2;
        if (event.ctrlKey && !event.shiftKey) {
            difficulty = 3;
        } else if (!event.ctrlKey && event.shiftKey) {
            difficulty = 1;
        }
        const actorSheet = actor.sheet.getData();
        const skill = actor.data.data.skills[skillname];
        const characteristic = actorSheet.data.characteristics[skill.characteristic];
        game.ffg.DiceHelpers.rollSkillDirect(skill, characteristic, difficulty, actorSheet);
    }
}


<|start_filename|>scripts/actions/demonlord/demonlord-actions.js<|end_filename|>
import { ActionHandler } from '../actionHandler.js';
import * as settings from '../../settings.js';

export class ActionHandlerDemonlord extends ActionHandler {
    constructor(filterManager, categoryManager) {
        super(filterManager, categoryManager);
    }

    /** @override */
    async doBuildActionList(token, multipleTokens) {
        let result = this.initializeEmptyActionList();

        if (multipleTokens) {
            this._buildMultipleTokenList(result);
            return result;
        }

        if (!token)
            return result;

        let tokenId = token.data._id;
        result.tokenId = tokenId;

        let actor = token.actor;

        if (!actor)
            return result;

        result.actorId = actor._id;

        let attributes = this._getAttributes(actor, tokenId);
        let weapons = this._getItemsList(actor, tokenId);
        let talents = this._getTalents(actor, tokenId);
        let spells = this._getSpells(actor, tokenId);
        let utility = this._getUtilityList(actor, tokenId);

        this._combineCategoryWithList(result, this.i18n('tokenactionhud.settings.demonlord.challenge'), attributes);

        if (actor.data.type === 'character')
            this._combineCategoryWithList(result, this.i18n('tokenactionhud.weapons'), weapons);
        else
            this._combineCategoryWithList(result, this.i18n('tokenactionhud.settings.demonlord.attackoptions'), weapons);

        if (actor.data.type === 'character')
            this._combineCategoryWithList(result, this.i18n('tokenactionhud.talents'), talents);
        else
            this._combineCategoryWithList(result, this.i18n('tokenactionhud.settings.demonlord.specialattacks'), talents);

        this._combineCategoryWithList(result, this.i18n('tokenactionhud.spells'), spells);
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.utility'), utility);

        this._setFilterSuggestions(actor);

        if (settings.get('showHudTitle'))
            result.hudTitle = token.data?.name;

        return result;
    }

    _getItemsList(actor, tokenId) {
        let macroType = 'weapon';
        let result = this.initializeEmptyCategory('items');

        let subcategory = this.initializeEmptySubcategory();

        subcategory.actions = this._produceMap(tokenId, actor.items.filter(i => i.type == macroType).map(item => {
            return { 
                name: item.name,
                encodedValue: [item.type, tokenId, item._id].join(this.delimiter),
            }
        }), macroType);

        this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.weapons'), subcategory);

        return result;
    }

    _getAttributes(actor, tokenId) {
        let result = this.initializeEmptyCategory('attributes');
        let attributes = this.initializeEmptySubcategory();
        let macroType = 'challenge';

        let rollableAttributes = Object.entries(actor.data.data.attributes);
        let attributesMap = rollableAttributes.map(c => {
            let name = this.i18n('tokenactionhud.attribute.' + c[0]);
            let encodedValue = [macroType, tokenId, c[0]].join(this.delimiter);
            return { name: name, encodedValue: encodedValue, id: c[0] }
        });

        attributes.actions = this._produceMap(tokenId, attributesMap, macroType);

        this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.settings.demonlord.challenge'), attributes);

        return result;
    }

    _addMultiAttributes(list, tokenId, actors) {
        let result = this.initializeEmptyCategory('attributes');
        let attributes = this.initializeEmptySubcategory();
        let macroType = 'challenge';

        let attributesMap = null;

        if (actors.every(actor => {
            let rollableAttributes = Object.entries(actor.data.data.attributes);

            attributesMap = rollableAttributes.map(c => {
                let name = this.i18n('tokenactionhud.attribute.' + c[0]);
                let encodedValue = [macroType, tokenId, c[0]].join(this.delimiter);
                return { name: name, encodedValue: encodedValue, id: c[0] }
            });
        }));

        if (attributesMap != null) {
            attributes.actions = this._produceMap(tokenId, attributesMap, macroType);

            this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.settings.demonlord.challenge'), attributes);
            this._combineCategoryWithList(list, this.i18n('tokenactionhud.settings.demonlord.challenge'), result)
        }
    }

    _getTalents(actor, tokenId) {
        let macroType = 'talent';
        let result = this.initializeEmptyCategory('talents');

        let talents = actor.items.filter(i => i.type == macroType);

        const groups = [... new Set(talents.map(talent => talent.data.data.groupname))];
        groups.sort().forEach(group => {
            if (group != undefined) {
                let groupCategory = this.initializeEmptySubcategory();
                groupCategory.name = group;
                result.subcategories.push(groupCategory);

                let levelSubcategory = this.initializeEmptySubcategory();
                talents.forEach(talentEntry => {
                    if (talentEntry.data.data.groupname == group) {
                        let encodedValue = [macroType, tokenId, talentEntry._id].join(this.delimiter);
                        let addTalent = { name: talentEntry.name, encodedValue: encodedValue, id: talentEntry._id };
                        addTalent.img = this._getImage(talentEntry);
                        addTalent.info2 = this._getUsesData(talentEntry);

                        levelSubcategory.actions.push(addTalent);
                    }
                });

                this._combineSubcategoryWithCategory(groupCategory, group, levelSubcategory);
            }
        });

        return result;
    }

    _getSpells(actor, tokenId) {
        let macroType = 'spell';
        let result = this.initializeEmptyCategory('spells');

        let spells = actor.items.filter(i => i.type === macroType);

        let spellsSorted = this._sortSpellsByRank(spells);
        let spellCategories = this._categoriseSpells(tokenId, spellsSorted);

        this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.spells'), spellCategories);

        return result;
    }

    _sortSpellsByRank(spells) {
        let result = Object.values(spells);

        result.sort((a, b) => {
            if (a.data.rank === b.data.rank)
                return a.name.toUpperCase().localeCompare(b.name.toUpperCase(), undefined, { sensitivity: 'base' });
            return a.data.rank - b.data.rank;
        });

        return result;
    }

    _categoriseSpells(tokenId, spells) {
        const macroType = 'spell';
        let result = this.initializeEmptySubcategory();

        const traditions = [... new Set(spells.map(spell => spell.data.data.tradition))];
        traditions.sort().forEach(tradition => {
            if (tradition != undefined) {
                let traditionCategory = this.initializeEmptySubcategory();
                traditionCategory.name = tradition;
                result.subcategories.push(traditionCategory);

                let levelSubcategory = this.initializeEmptySubcategory();
                spells.forEach(spellEntry => {
                    if (spellEntry.data.data.tradition == tradition) {
                        let encodedValue = [macroType, tokenId, spellEntry._id].join(this.delimiter);
                        let addSpell = { name: spellEntry.name, encodedValue: encodedValue, id: spellEntry._id };
                        addSpell.img = this._getImage(spellEntry);
                        addSpell.info2 = this._getCastingsData(spellEntry);

                        levelSubcategory.actions.push(addSpell);
                    }
                });

                this._combineSubcategoryWithCategory(traditionCategory, tradition, levelSubcategory);
            }
        });

        return result;
    }

    _buildMultipleTokenList(list) {
        list.tokenId = 'multi';
        list.actorId = 'multi';

        const allowedTypes = ['creature', 'character'];
        let actors = canvas.tokens.controlled.map(t => t.actor).filter(a => allowedTypes.includes(a.data.type));

        this._addMultiAttributes(list, list.tokenId, actors);
        this._addMultiUtilities(list, list.tokenId, actors);
    }

    _getUtilityList(actor, tokenId) {
        let result = this.initializeEmptyCategory('utility');
        let macroType = 'utility';

        let rests = this.initializeEmptySubcategory()

        if (actor.data.type === 'character') {
            let shortRestValue = [macroType, tokenId, 'rest'].join(this.delimiter);
            rests.actions.push({ id: 'rest', encodedValue: shortRestValue, name: this.i18n('tokenactionhud.settings.demonlord.rest') })
        }

        this._combineSubcategoryWithCategory(result, this.i18n('tokenactionhud.settings.demonlord.rest'), rests);

        return result;
    }

    _addMultiUtilities(list, tokenId, actors) {
        let category = this.initializeEmptyCategory('utility');
        let macroType = 'utility';

        let rests = this.initializeEmptySubcategory();

        if (actors.every(actor => actor.data.type === 'character')) {
            let shortRestValue = [macroType, tokenId, 'rest', ''].join(this.delimiter);
            rests.actions.push({ id: 'rest', encodedValue: shortRestValue, name: this.i18n('tokenactionhud.settings.demonlord.rest') })
        }

        this._combineSubcategoryWithCategory(category, this.i18n('tokenactionhud.settings.demonlord.rest'), rests);
        this._combineCategoryWithList(list, this.i18n('tokenactionhud.utility'), category)
    }

    /** @override */
    _setFilterSuggestions(id, items) {
        let suggestions = items?.map(s => { return { id: s._id, value: s.name } })
        if (suggestions?.length > 0)
            this.filterManager.setSuggestions(id, suggestions);
    }

    _filterElements(categoryId, skills) {
        let filteredNames = this.filterManager.getFilteredNames(categoryId);
        let result = skills.filter(s => !!s);
        if (filteredNames.length > 0) {
            if (this.filterManager.isBlocklist(categoryId)) {
                result = skills.filter(s => !filteredNames.includes(s.name));
            }
            else {
                result = skills.filter(s => filteredNames.includes(s.name));
            }
        }

        return result;
    }

    _produceMap(tokenId, itemSet, type) {
        return itemSet.map(i => {
            let icon = this._getImage(i);
            let result = { name: i.name, encodedValue: i.encodedValue, id: i._id, icon };

            if (type === "talent")
                result.info2 = this._getUsesData(i);

            return result;
        });
    }

    _getImage(item) {
        let result = '';
        if (settings.get('showIcons'))
            result = item.img ?? '';

        return !result?.includes('icons/svg/mystery-man.svg') ? result : '';
    }

    _getUsesData(item) {
        let result = '';

        let uses = item.data.data.uses;
        if (!uses)
            return result;

        if (!(uses.max || uses.value))
            return result;

        result = uses.value ?? 0;

        if (uses.max > 0) {
            result += `/${uses.max}`
        }

        return result;
    }

    _getCastingsData(item) {
        let result = '';

        let uses = item.data.data.castings;
        if (!uses)
            return result;

        if (!(uses.max || uses.value))
            return result;

        result = uses.value ?? 0;

        if (uses.max > 0) {
            result += `/${uses.max}`
        }

        return result;
    }
}

<|start_filename|>scripts/rollHandlers/alienrpg/alienrpg-base.js<|end_filename|>
import { RollHandler } from '../rollHandler.js';
import * as settings from '../../settings.js';

export class RollHandlerBaseAlienrpg extends RollHandler {
  constructor() {
    super();
  }

  async doHandleActionEvent(event, encodedValue) {
    let payload = encodedValue.split('|');
    if (payload.length != 4) {
      super.throwInvalidValueErr();
    }

    let macroType = payload[0];
    let tokenId = payload[1];
    let actionId = payload[2];
    let attributename = payload[3];
    let actor = super.getActor(tokenId);
    let charType;
    if (actor) charType = actor.data.type;
    let item = actionId ? actor.getOwnedItem(actionId) : null;

    let renderable = ['item', 'armor'];
    if (renderable.includes(macroType) && this.isRenderItem()) return this.doRenderItem(tokenId, actionId);

    if (tokenId === 'multi') {
      if (macroType === 'utility' && actionId.includes('toggle')) {
        this.performMultiToggleUtilityMacro(actionId);
      } else {
        canvas.tokens.controlled.forEach((t) => {
          let idToken = t.data._id;
          this._handleMacros(event, macroType, idToken, actionId, attributename);
        });
      }
    } else {
      let sharedActions = ['utility'];

      if (!sharedActions.includes(macroType)) {
        switch (charType) {
          case 'character':
          case 'creature':
          case 'synthetic':
            await this._handleUniqueActionsChar(macroType, event, tokenId, actor, actionId);
            break;
        }
      }
      let rData = [];
      switch (macroType) {
        case 'attribute':
          rData = { roll: actor.data.data.attributes[actionId].value, label: actor.data.data.attributes[actionId].label };
          if (event.type === 'click') {
            actor.rollAbility(actor, rData);
          } else {
            actor.rollAbilityMod(actor, rData);
          }
          break;
        case 'creatureattribute':
          switch (actionId) {
            case 'mobility':
            case 'observation':
              rData = { roll: actor.data.data.general[actionId].value, label: actor.data.data.general[actionId].label };
              break;
            default:
              let clabel = attributename[0].toUpperCase() + attributename.substring(1);
              rData = { roll: actor.data.data.attributes[actionId].value, label: [clabel] };
              break;
          }
          if (event.type === 'click') {
            actor.rollAbility(actor, rData);
          } else {
            actor.rollAbilityMod(actor, rData);
          }
          break;
        case 'skill':
          rData = { roll: actor.data.data.skills[actionId].mod, label: actor.data.data.skills[actionId].label };
          if (event.type === 'click') {
            actor.rollAbility(actor, rData);
          } else {
            actor.rollAbilityMod(actor, rData);
          }
          break;
        case 'weapon':
          if (event.type === 'click') {
            actor.nowRollItem(item);
          } else {
            actor.rollItemMod(item);
          }
          break;
        case 'item':
          this._rollItem(actor, tokenId, actionId, macroType);
          break;
        case 'armor':
          rData = { roll: actor.data.data.general.armor.value, spbutt: 'armor' };
          actor.rollAbility(actor, rData);
          break;
        case 'consumables':
          const lTemp = 'ALIENRPG.' + (attributename[0].toUpperCase() + attributename.substring(1));
          const label = game.i18n.localize(lTemp) + ' ' + game.i18n.localize('ALIENRPG.Supply');
          actor.consumablesCheck(actor, actionId, label);
          break;
        case 'power':
          const pTemp = 'ALIENRPG.' + (macroType[0].toUpperCase() + macroType.substring(1));
          const plabel = game.i18n.localize(pTemp) + ' ' + game.i18n.localize('ALIENRPG.Supply');
          actor.consumablesCheck(actor, macroType, plabel, actionId);
          break;
        case 'conditions':
          this.performConditionMacro(event, tokenId, actionId);
          break;

        case 'utility':
          this.performUtilityMacro(event, tokenId, actionId);
        default:
          break;
      }
    }
  }

  /** @private */
  async _handleUniqueActionsChar(macroType, event, tokenId, actor, actionId) {
    let rData = 0;
    switch (macroType) {
      case 'stress':
        await this._adjustAttribute(event, actor, 'stress', 'value', actionId);
        break;
      case 'rollStress':
        if (actor.data.type === 'character') {
          rData = { panicroll: actor.data.data.header.stress };
        } else {
          rData = { panicroll: { value: 0, label: 'Stress' } };
        }
        if (event.type === 'click') {
          actor.rollAbility(actor, rData);
        } else {
          actor.rollAbilityMod(actor, rData);
        }
        break;
      case 'health':
        await this._adjustAttribute(event, actor, 'health', 'value', actionId);
        break;
      case 'creatureAttack':
        let rAttData = { atttype: actor.data.data.rTables };
        actor.creatureAttackRoll(actor, rAttData);
        break;
      case 'acidSplash':
        let aSplashData = { roll: actor.data.data.general.acidSplash.value, label: actor.data.data.general.acidSplash.label };
        actor.creatureAcidRoll(actor, aSplashData);
        break;
      case 'rollCrit':
        actor.rollCrit(actor.data.type);
        break;
    }
  }

  async _adjustAttribute(event, actor, property, valueName, actionId) {
    let value = actor.data.data.header[property][valueName];
    let max = '10';

    if (this.rightClick) {
      if (value <= 0) return;
      value--;
    } else {
      if (value >= max) return;
      value++;
    }

    let update = { data: { header: { [property]: { [valueName]: value } } } };

    await actor.update(update);
  }

  async togggleConditionState(event, actor, property, valueName, actionId) {
    let value = actor.data.data.general[property][valueName];
    let max = '1';

    if (this.rightClick) {
      if (value <= 0) return;
      value--;
      if (property === 'panic') {
        actor.checkAndEndPanic(actor);
      }
    } else {
      if (value >= max) return;
      value++;
      if (property === 'panic') {
        actor.checkAndEndPanic(actor);
      }
    }

    let update = { data: { general: { [property]: { [valueName]: value } } } };
    await actor.update(update);
  }

  performUtilityMacro(event, tokenId, actionId) {
    let actor = super.getActor(tokenId);
    let token = super.getToken(tokenId);

    switch (actionId) {
      case 'toggleVisibility':
        token.toggleVisibility();
        break;
      case 'toggleCombat':
        token.toggleCombat();
        Hooks.callAll('forceUpdateTokenActionHUD');
        break;
    }
  }

  async performMultiToggleUtilityMacro(actionId) {
    if (actionId === 'toggleVisibility') {
      const allVisible = canvas.tokens.controlled.every((t) => !t.data.hidden);
      canvas.tokens.controlled.forEach((t) => {
        if (allVisible) t.toggleVisibility();
        else if (t.data.hidden) t.toggleVisibility();
      });
    }

    if (actionId === 'toggleCombat') {
      const allInCombat = canvas.tokens.controlled.every((t) => t.data.inCombat);
      for (let t of canvas.tokens.controlled) {
        if (allInCombat) await t.toggleCombat();
        else if (!t.data.inCombat) await t.toggleCombat();
      }
      Hooks.callAll('forceUpdateTokenActionHUD');
    }
  }

  performConditionMacro(event, tokenId, actionId) {
    let actor = super.getActor(tokenId);
    let token = super.getToken(tokenId);

    switch (actionId) {
      case 'toggleStarving':
        this.togggleConditionState(event, actor, 'starving', 'value');
        break;
      case 'toggleDehydrated':
        this.togggleConditionState(event, actor, 'dehydrated', 'value');
        break;
      case 'toggleExhausted':
        this.togggleConditionState(event, actor, 'exhausted', 'value');
        break;
      case 'toggleFreezing':
        this.togggleConditionState(event, actor, 'freezing', 'value');
        break;
      case 'togglePanic':
        this.togggleConditionState(event, actor, 'panic', 'value');
        break;
    }
  }

  /** @private */
  _rollItem(actor, tokenId, actionId, macroType) {
    let item = actor.getOwnedItem(actionId);
    let renderable = ['item'];
    if (renderable.includes(macroType)) {
      return this.doRenderItem(tokenId, actionId);
    } else {
      console.warn('armor roll');
    }
  }
}


<|start_filename|>scripts/actions/starwarsffg/starwarsffg-actions.js<|end_filename|>
import {ActionHandler} from '../actionHandler.js';
import * as settings from '../../settings.js';

export class ActionHandlerStarWarsFFG extends ActionHandler {
    constructor(filterManager, categoryManager) {
        super(filterManager, categoryManager);
        this.filterManager.createOrGetFilter('skills');
    }    

    /** @override */
    async doBuildActionList(token, multipleTokens) {
        let result = this.initializeEmptyActionList();

        if (!token)
            return result;

        let tokenId = token.data._id;

        result.tokenId = tokenId;

        let actor = token.actor;
        if (!actor)
            return result;

        result.actorId = actor._id;
        
        let weapons = this._getItemsList(actor, tokenId, 'weapon');
        this._combineCategoryWithList(result, this.i18n('tokenactionhud.weapons'), weapons);
        
        let forcePowers = this._getItemsList(actor, tokenId, 'forcepower');
        this._combineCategoryWithList(result, this.i18n('SWFFG.ForcePower'), forcePowers);


        const data = actor.data.data

        data.skilltypes.forEach((type) => {
            let skills = this._getSkills(type, data, tokenId);
            this._combineCategoryWithList(result, type.label, skills);
        })
        
        if (game.user.isGM) {
            this._combineCategoryWithList(result, 'utility', this._getGMList(multipleTokens));
        }
        
        if (settings.get('showHudTitle'))
            result.hudTitle = token.data?.name;
            
        return result;
    }

    /** @private */
    _getGMList(multipleTokens) {
        let result = this.initializeEmptyCategory('utility');
        let subcategory = this.initializeEmptySubcategory();
        let items = [
            {name: 'Destiny Pool Init', encodedValue: 'gm|destiny|destiny', id: 'gm_destiny'}
        ]
        subcategory.actions = items
        
        this._combineSubcategoryWithCategory(result, 'GM', subcategory);

        return result;
    }

    /** @private */
    _getItemsList(actor, tokenId, type) {
        let result = this.initializeEmptyCategory(type);

        let subcategory = this.initializeEmptySubcategory();
        
        let items = actor.items.filter(i => i.type === type);
        subcategory.actions = this._produceItemMap(tokenId, items, type);

        this._combineSubcategoryWithCategory(result, "", subcategory);

        return result;
    }

    /** @private */
    _getSkills(type, data, tokenId) {
        let categoryId = 'skills';
        let macroType = 'skill';
        
        let result = this.initializeEmptyCategory(categoryId);

        const skills = Object.keys(data.skills)
            .filter((s) => data.skills[s].type === type.type)
            .sort((a, b) => {
            let comparison = 0;
            if (a.toLowerCase() > b.toLowerCase()) {
                comparison = 1;
            } else if (a.toLowerCase() < b.toLowerCase()) {
                comparison = -1;
            }
            return comparison;
            });
        settings.Logger.debug(skills)
        let skillCat = this.initializeEmptySubcategory();
        skillCat.actions = this._produceSkillMap(tokenId, data, skills, macroType);
        
        this._combineSubcategoryWithCategory(result, "", skillCat);

        return result;
    }

    /** @private */
    _produceItemMap(tokenId, itemSet, type) {
        return itemSet.map(i => {
            let encodedValue = [type, tokenId, i._id].join(this.delimiter);
            return { name: i.name, encodedValue: encodedValue, id: i._id};
        });
    }

    /** @private */
    _produceSkillMap(tokenId, data, skills, type) {
        return skills.map(i => {
            let encodedValue = [type, tokenId, i].join(this.delimiter);
            return { name: data.skills[i].label, encodedValue: encodedValue, id: i };
        });
    }
}

<|start_filename|>scripts/rollHandlers/dnd5e/dnd5e-obsidian.js<|end_filename|>
import { RollHandlerBase5e } from "./dnd5e-base.js";

export class RollHandlerObsidian extends RollHandlerBase5e {
  constructor() {
    super();
  }

  /** @override */
  rollAbilityCheckMacro(event, tokenId, checkId) {
    let actor = super.getActor(tokenId);
    OBSIDIAN.Items.roll(actor, { roll: "abl", abl: checkId });
  }

  /** @override */
  rollAbilitySaveMacro(event, tokenId, checkId) {
    let actor = super.getActor(tokenId);
    OBSIDIAN.Items.roll(actor, { roll: "save", save: checkId });
  }

  /** @override */
  rollSkillMacro(event, tokenId, checkId) {
    let actor = super.getActor(tokenId);
    OBSIDIAN.Items.roll(actor, { roll: "skl", skl: checkId });
  }

  /** @override */
  rollItemMacro(event, tokenId, itemId) {
    let actor = super.getActor(tokenId);
    OBSIDIAN.Items.roll(actor, { roll: "item", id: itemId });
  }
}


<|start_filename|>scripts/managers/manager.js<|end_filename|>
import * as settings from '../settings.js';
import {Logger} from '../logger.js';
import {FilterManager} from '../actions/filter/filterManager.js';
import {CategoryManager} from '../actions/categories/categoryManager.js';
import {ItemMacroActionListExtender} from '../actions/itemMacroExtender.js';
import {CompendiumMacroPreHandler} from '../rollHandlers/compendiumMacroPreHandler.js';
import {ItemMacroPreRollHandler} from '../rollHandlers/pre-itemMacro.js';

export class SystemManager {

    i18n = (toTranslate) => game.i18n.localize(toTranslate);

    appName;
    constructor(appName) {
        this.appName = appName;
    }


    /** ACTION HANDLERS */

    async getActionHandler(user) {
        this.filterManager = new FilterManager(user);
        this.categoryManager = new CategoryManager(user, this.filterManager);

        await this.categoryManager.init();

        let actionHandler = this.doGetActionHandler(this.filterManager, this.categoryManager);
        this.addActionExtenders(actionHandler);
        return actionHandler;
    }

    doGetActionHandler() {}

    addActionExtenders(actionHandler) {
        if (SystemManager.isModuleActive('itemacro'))
            actionHandler.addFurtherActionHandler(new ItemMacroActionListExtender())
    }
    
    filterManager;
    getFilterManager() {
        return this.filterManager;
    }
    
    categoryManager;
    getCategoryManager() {
        return this.categoryManager;
    }


    /** ROLL HANDLERS */

    getRollHandler() {
        let rollHandlerId = settings.get('rollHandler');
        
        if (! (rollHandlerId === 'core' || SystemManager.isModuleActive(rollHandlerId)) ) {
            Logger.error(rollHandlerId, this.i18n('tokenactionhud.handlerNotFound'));
            rollHandlerId = 'core';
            settings.set('rollHandler', rollHandlerId);
        }

        let rollHandler = this.doGetRollHandler(rollHandlerId);
        this.addPreHandlers(rollHandler);
        return rollHandler;
    }

    doGetRollHandler(handlerId) {}

    addPreHandlers(rollHandler) {
        rollHandler.addPreRollHandler(new CompendiumMacroPreHandler())

        if (SystemManager.isModuleActive('itemacro'))
            rollHandler.addPreRollHandler(new ItemMacroPreRollHandler())
    }

    getAvailableRollHandlers() {}


    /** SETTINGS */

    registerSettings() {
        let rollHandlers = this.getAvailableRollHandlers();
        settings.registerSettings(this.appName, this, rollHandlers)
    }


    /** UTILITY */
    
    static addHandler(choices, id) {
        if (SystemManager.isModuleActive(id)) {
            let title = SystemManager.getModuleTitle(id);
            mergeObject(choices, { [id]: title })
        }
    }

    static isModuleActive(id) {
        let module = game.modules.get(id);
        return module && module.active;
    }

    static getModuleTitle(id) {
        return game.modules.get(id)?.data.title ?? '';
    }
}

<|start_filename|>scripts/rollHandlers/lancer/lancer-base.js<|end_filename|>
import { RollHandler } from "../rollHandler.js";

export class RollHandlerBaseLancer extends RollHandler {
    constructor() {
        super();
    }

    /** @override */
    doHandleActionEvent(event, encodedValue) {
        let payload = encodedValue.split('|');
        
        if (payload.length != 3 && payload.length != 4) {
            super.throwInvalidValueErr();
        }
        
        let macroType = payload[0];
        let tokenId = payload[1];
        let actionId = payload[2];
        let option = JSON.parse(payload[3]);

        let actor = super.getActor(tokenId);

        let hasSheet = ['item']
        if (this.isRenderItem() && hasSheet.includes(macroType))
            return this.doRenderItem(tokenId, actionId);

        switch (macroType) {
            case "hase":
                this._rollHaseMacro(actor, actionId);
                break;
            case "stat":
                this._rollStatMacro(actor, actionId);
                break;
            case "item":
                this._rollWeaponOrFeatureMacro(actor, actionId, option);
                break;
            case "coreActive":
                this._rollCoreActiveMacro(actor);
                break;
            case "corePassive":
                this._rollCorePassiveMacro(actor);
                break;
        }
    }

    _rollHaseMacro(actor, action) {
        game.lancer.prepareStatMacro(actor._id, `data.mech.${action}`);
    }

    _rollStatMacro(actor, action) {
        game.lancer.prepareStatMacro(actor._id, `data.${action}`);
    }

    _rollWeaponOrFeatureMacro(actor, itemId, option) {
        game.lancer.prepareItemMacro(actor._id, itemId, option);
    }

    _rollCoreActiveMacro(actor) {
        game.lancer.prepareCoreActiveMacro(actor._id);
    }

    _rollCorePassiveMacro(actor) {
        game.lancer.prepareCorePassiveMacro(actor._id);
    }
}

<|start_filename|>scripts/tokenactionhud.js<|end_filename|>
import * as settings from './settings.js';
import { TagDialogHelper } from './utilities/tagDialogHelper.js';
import { CategoryResizer } from './utilities/categoryResizer.js';

export class TokenActionHUD extends Application {
    i18n = (toTranslate) => game.i18n.localize(toTranslate);

    refresh_timeout = null;
    tokens = null;
    rendering = false;
    categoryHovered = '';
    defaultLeftPos = 150;
    defaultTopPos = 80;

    constructor(systemManager) {
        super();
        this.systemManager = systemManager;
    }

    async init(user) {
        this.actions = await this.systemManager.getActionHandler(user);

        this.rollHandler = this.systemManager.getRollHandler();
        this.filterManager = this.systemManager.getFilterManager();
        this.categoryManager = this.systemManager.getCategoryManager();
    }

    updateSettings() {
        this.updateRollHandler();
        this.update();
    }

    updateRollHandler() {
        this.rollHandler = this.systemManager.getRollHandler();
    }

    setTokensReference(tokens) {
        this.tokens = tokens;
    }

    /** @override */
    static get defaultOptions() {
    return mergeObject(super.defaultOptions, {
        template: '/modules/token-action-hud/templates/template.hbs',
        id: 'token-action-hud',
        classes: [],
        width: 200,
        height: 20,
        left: 150,
        top: 80,
        scale: 1,
        background: 'none',
        popOut: false,
        minimizable: false,
        resizable: false,
        title: 'token-action-hud',
        dragDrop: [],
        tabs: [],
        scrollY: []
        });
    }

    getScale() {
        const scale = parseFloat(settings.get('scale'));
        
        if (scale < 0.8)
            return 0.8;

        if (scale > 2)
            return 2;

        return scale;
    }

    getBackground() {
        return settings.get('background');
    }

    /** @override */
    getData(options = {}) {
        const data = super.getData();
        data.actions = this.targetActions;
        data.id = 'token-action-hud';
        data.hovering = settings.get('onTokenHover');;
        data.scale = this.getScale();
        data.background = this.getBackground();
        settings.Logger.debug('HUD data:', data);
        return data;
    }

    /** @override */
    activateListeners(html) {
        const tokenactionhud = '#token-action-hud';
        const repositionIcon = '#tah-reposition';
        const categoriesIcon = '#tah-categories';
        const action = '.tah-action';   

        const handleClick = e => {
            let target = e.target;

            if (target.tagName !== 'BUTTON')
                target = e.currentTarget.children[0];

            let value = target.value;
            try {
                this.rollHandler.handleActionEvent(e, value);
            } catch (error) {
                settings.Logger.error(e);
            }
        }

        html.find(action).on('click', e => {
            handleClick(e);
        });

        html.find(action).contextmenu(e => {
            handleClick(e);
        });

        function handlePossibleFilterButtonClick(e) {
            let target = e.target;
            if(target.value.length === 0)
                return;

            let id = target.value;
            let categoryTitle = target.innerText ?? target.outerText;

            if (game.tokenActionHUD.categoryManager.isCompendiumCategory(id))
                TagDialogHelper.showSubcategoryDialogue(game.tokenActionHUD.categoryManager, id, categoryTitle)
            else
                TagDialogHelper.showFilterDialog(game.tokenActionHUD.filterManager, id);
        }     

        function handlePossibleFilterSubtitleClick(e) {
            let target = e.target;
            if(target.id.length === 0)
                return;

            let id = target.id;

            TagDialogHelper.showFilterDialog(game.tokenActionHUD.filterManager, id);
        }
        
        function closeCategory(event) {
            if (game.tokenActionHUD.rendering)
                return;
            let category = $(this)[0];
            $(category).removeClass('hover');
            let id = category.id;
            game.tokenActionHUD.clearHoveredCategory(id);
        }

        function openCategory(event) {
            let category = $(this)[0];
            closeAllCategories(event);
            $(category).addClass('hover');
            let id = category.id;
            game.tokenActionHUD.setHoveredCategory(id);
            CategoryResizer.resizeHoveredCategory(id);
        }

        function closeAllCategories(event) {
            html.find('.tah-category').removeClass('hover');
        }

        function toggleCategory(event) {
            let category = $(this.parentElement);
            let boundClick;
            if ($(category).hasClass('hover')) {
                boundClick = closeCategory.bind(this.parentElement);
                boundClick(event);
            }             
            else {
                boundClick = openCategory.bind(this.parentElement);
                boundClick(event);
            }
        }

        html.find('.tah-title-button').contextmenu('click', e => handlePossibleFilterButtonClick(e));
        
        html.find('.tah-subtitle').click('click', e => handlePossibleFilterSubtitleClick(e));
        html.find('.tah-subtitle').contextmenu('click', e => handlePossibleFilterSubtitleClick(e));

        if (settings.get('clickOpenCategory')) {
            html.find('.tah-title-button').click('click', toggleCategory);
        } else {
            html.find('.tah-category').hover(openCategory,closeCategory);
        }

        html.find(categoriesIcon).mousedown(ev => {
            ev.preventDefault();
            ev = ev || window.event;

            TagDialogHelper._showCategoryDialog(this.categoryManager)
        })

        html.find(repositionIcon).mousedown(ev => {
            ev.preventDefault();
            ev = ev || window.event;

            let hud = $(document.body).find(tokenactionhud);
            let marginLeft = parseInt(hud.css('marginLeft').replace('px', ''));
            let marginTop = parseInt(hud.css('marginTop').replace('px', ''));

            dragElement(document.getElementById('token-action-hud'));
            let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
  
            function dragElement(elmnt) {
                elmnt.onmousedown = dragMouseDown;

                function dragMouseDown(e) {
                    e = e || window.event;
                    e.preventDefault();
                    pos3 = e.clientX;
                    pos4 = e.clientY;
                    
                    document.onmouseup = closeDragElement;
                    document.onmousemove = elementDrag;
                }
            
                function elementDrag(e) {
                    e = e || window.event;
                    e.preventDefault();
                    // calculate the new cursor position:
                    pos1 = pos3 - e.clientX;
                    pos2 = pos4 - e.clientY;
                    pos3 = e.clientX;
                    pos4 = e.clientY;
                    // set the element's new position:
                    elmnt.style.top = (elmnt.offsetTop - pos2) - marginTop + 'px';
                    elmnt.style.left = (elmnt.offsetLeft - pos1) - marginLeft + 'px';
                    elmnt.style.position = 'fixed';
                    elmnt.style.zIndex = 100;
                }
            
                function closeDragElement() {
                    // stop moving when mouse button is released:
                    elmnt.onmousedown = null;
                    document.onmouseup = null;
                    document.onmousemove = null;
                    let xPos = (elmnt.offsetLeft - pos1) > window.innerWidth ? window.innerWidth : (elmnt.offsetLeft - pos1);
                    let yPos = (elmnt.offsetTop - pos2) > window.innerHeight-20 ? window.innerHeight-100 : (elmnt.offsetTop - pos2)
                    xPos = xPos < 0 ? 0 : xPos
                    yPos = yPos < 0 ? 0 : yPos
                    if(xPos != (elmnt.offsetLeft - pos1) || yPos != (elmnt.offsetTop - pos2)){
                        elmnt.style.top = (yPos) + 'px';
                        elmnt.style.left = (xPos) + 'px';
                    }
                    settings.Logger.info(`Setting position to x: ${xPos}px, y: ${yPos}px, and saving in user flags.`)
                    game.user.update({flags: {'token-action-hud':{ hudPos: {top: yPos, left: xPos}}}})
                }
            }
        });

        $(document).find('.tah-filterholder').parents('.tah-subcategory').css('cursor', 'pointer');
    }

    applySettings() {
        if (!settings.get('dropdown')) {
            $(document).find('.tah-content').css({
                    'bottom': '40px',
                    'flex-direction': 'column-reverse'
                });
        }
    }

    // Positioning
    trySetPos() {
        if (!(this.targetActions && this.targetActions.tokenId))
            return;

        let hudTitle = $(document).find('#tah-hudTitle');
        if (hudTitle.length > 0)
            hudTitle.css('top', -hudTitle[0].getBoundingClientRect().height)

        let token = canvas?.tokens?.placeables.find(t => t.data.id === this.targetActions?.tokenId);
        if (settings.get('onTokenHover') && token) {           
            this.setHoverPos(token);
        } else {
            this.setUserPos();
        }

        this.restoreCategoryHoverState();
        this.rendering = false;
    }

    setUserPos() {
        if(!(game.user.data.flags['token-action-hud'] && game.user.data.flags['token-action-hud'].hudPos))
            return;

        let pos = game.user.data.flags['token-action-hud'].hudPos;
        let defaultLeftPos = this.defaultLeftPos;
        let defaultTopPos = this.defaultTopPos;

        return new Promise(resolve => {
            function check() {
                let elmnt = document.getElementById('token-action-hud')
                if (elmnt) {
                    elmnt.style.bottom = null;
                    elmnt.style.top = pos.top < 5 || pos.top > window.innerHeight + 5 ? (defaultTopPos) + 'px' : (pos.top) + 'px';
                    elmnt.style.left = pos.left < 5 || pos.left > window.innerWidth + 5 ? (defaultLeftPos) + 'px': (pos.left) + 'px';
                    elmnt.style.position = 'fixed';
                    elmnt.style.zIndex = 100;
                resolve();
                } else {
                    setTimeout(check, 30);
                }
            }

            check();
        });
    }

    setHoverPos(token) { 
        return new Promise(resolve => {
            function check(token) {
                let elmnt = $('#token-action-hud');
                if (elmnt) {
                    elmnt.css('bottom', null);
                    elmnt.css('left', (token.worldTransform.tx + (((token.data.width * canvas.dimensions.size) + 55) * canvas.scene._viewPosition.scale)) + 'px');
                    elmnt.css('top', (token.worldTransform.ty + 0) + 'px');
                    elmnt.css('position', 'fixed');
                    elmnt.css('zIndex', 100);
                    resolve();
                } else {
                    setTimeout(check, 30);
                }
            }

            check(token);
        });
    }

    setHoveredCategory(catId) {
        this.categoryHovered = catId;
    }

    clearHoveredCategory(catId) {            
        if (this.categoryHovered === catId)
            this.categoryHovered = '';
    }

    restoreCategoryHoverState() {
        if (this.categoryHovered === '')
            return;

        let id = `#${this.categoryHovered}`;
        let category = $(id);
        
        if (!category[0])
            return;

        if (settings.get('clickOpenCategory')) {
            let button = category.find('.tah-title-button')[0];
            button.click();
        } else {
            category.mouseenter();
        }
    }

    async resetHud() {
        await this.resetFlags();
        this.resetPosition();
    }

    resetPosition() {
        settings.Logger.info(`Resetting HUD position to x: 80px, y: 150px, and saving in user flags. \nIf HUD is still not visible, something else may be wrong.\nFeel free to contact ^ and stick#0520 on Discord`)
        game.user.update({flags: {'token-action-hud': {hudPos: { top: 80, left: 150 }}}})
        this.update();
    }

    async resetFlags() {
        settings.Logger.info(`Resetting Token Action HUD filter and category flags`);
        await this.categoryManager.reset();
        await this.filterManager.reset();
        this.update();
    }

    update() {
        // Delay refresh because switching tokens could cause a controlToken(false) then controlToken(true) very fast
        if (this.refresh_timeout)
            clearTimeout(this.refresh_timeout)
        this.refresh_timeout = setTimeout(this.updateHud.bind(this), 100)
    }

    async updateHud() {
        settings.Logger.debug('Updating HUD');

        let token = this._getTargetToken(this.tokens?.controlled);

        let multipleTokens = this.tokens?.controlled.length > 1 && !token;
        this.targetActions = await this.actions.buildActionList(token, multipleTokens);

        if (!this.showHudEnabled()) {
            this.close();
            return;
        }

        this.rendering = true;
        this.render(true);
    }

    // Really just checks if only one token is being controlled. Not smart.
    validTokenChange(token) {
        if (settings.get('alwaysShowHud'))
            return this.isRelevantToken(token) || token.actorId === game.user.character?.id;
        else
            return this.isRelevantToken(token);
    }

    isRelevantToken(token) {
        let controlled = this.tokens?.controlled;
        return controlled?.some(t => t.id === this.getTokenId(token)) || (controlled?.length === 0 && canvas?.tokens?.placeables?.some(t => t.id === this.targetActions?.tokenId));
    }

    getTokenId(token) {
        return token.id ?? token.id;
    }

    // Is something being hovered on, is the setting on, and is it the token you're currently selecting.
    validTokenHover(token, hovered) {
        return hovered && settings.get('onTokenHover') && token.id === this.targetActions?.tokenId;
    }

    // Basically update any time. All this logic could be improved.
    validActorOrItemUpdate(actor) {
        settings.Logger.debug(`actor change, comparing actors`);
        settings.Logger.debug(`actor.id: ${actor.id}; this.targetActions.actorId: ${this.targetActions?.actorId}`);

        if (!actor) {
            settings.Logger.debug('No actor, possibly deleted, should update HUD.');
            return true;
        }
            
        if (this.targetActions && actor.id === this.targetActions.actorId) {
            settings.Logger.debug('Same actor IDs, should update HUD.');
            return true;
        }

        settings.Logger.debug('Different actor, no need to update HUD.');
        return false;
    }

    showHudEnabled() {
        settings.Logger.debug('showHudEnabled()', `isGM: ${game.user.isGM}`, `enabledForUser: ${settings.get('enabledForUser')}`, `playerPermission: ${settings.get('playerPermission')}`);

        if (!settings.get('enabledForUser'))            
            return false;

        return (settings.get('playerPermission') || game.user.isGM);
    }

    isLinkedCompendium(compendiumKey) {
        settings.Logger.debug('Compendium hook triggered. Checking if compendium is linked.')
        return this.categoryManager.isLinkedCompendium(compendiumKey);
    }

    /** @private */
    _getTargetToken(controlled = []) {
        if (controlled.length > 1)
            return null;

        if (controlled.length === 0 && canvas.tokens?.placeables && game.user.character) {
            if (!settings.get('alwaysShowHud'))
                return null;
            
            let character = game.user.character
            let token = canvas?.tokens?.placeables.find(t => t.actor?.id === character?.id)
            if (token)
                return token;
            
            return null;
        }

        let ct = controlled[0];

        if (!ct)
            return null;

        if(this._userHasPermission(ct))
            return ct;
            
        return null;
    }

    /** @private */
    _userHasPermission(token = '') {
        let actor = token.actor;
        let user = game.user;
        return game.user.isGM || actor?.hasPerm(user, "OWNER");
    }
}


<|start_filename|>scripts/enums/subcategoryType.js<|end_filename|>
export const SubcategoryType = {
    MACRO: 'macro',
    COMPENDIUM: 'comp'
}

<|start_filename|>scripts/actions/categories/filterCategory.js<|end_filename|>
import {CompendiumSubcategory} from './compendiumSubcategory.js';
import {MacroSubcategory} from './macroSubcategory.js';
import {CompendiumHelper} from './compendiumHelper.js';
import {SubcategoryType} from '../../enums/subcategoryType.js';

export class FilterCategory {
    subcategories = [];
    id = '';
    key = '';
    title = '';
    push = false; // push or shift when adding to actionList
    core = false; // is it a base category; if so, don't always display

    constructor(filterManager, id, title, push, core) {
        this.filterManager = filterManager;
        this.id = id;
        this.key = id.slugify({replacement: '_', strict:true})
        this.title = title;
        this.push = push;
        this.core = core ?? false;
    }

    async addToActionList(actionHandler, actionList) {
        if (actionList.categories.some(c => c.name === this.title)) {
            let existingCat = actionList.categories.find(c => c.name === this.title);

            if (!existingCat.subcategories.length)
                return;

            await this.addSubcategoriesToCategory(actionHandler, existingCat);
        } else {
            await this.doAddToActionList(actionHandler, actionList);
        }
    }

    async doAddToActionList(actionHandler, actionList) {
        if (this.core)
            return;

        let result = actionHandler.initializeEmptyCategory(this.id);
        result.core = this.core;

        await this.addSubcategoriesToCategory(actionHandler, result);

        actionHandler._combineCategoryWithList(actionList, this.title, result, this.push);
    }

    async addSubcategoriesToCategory(actionHandler, category) {
        for (let subcategory of this.subcategories) {
            await subcategory.addToCategory(actionHandler, category);
        }
    }

    async selectSubcategories(selection) {
        for (let subcat of selection) {
            if (subcat.type === SubcategoryType.COMPENDIUM)
                await this.addCompendiumSubcategory(subcat);
            else
                this.addMacroSubcategory(subcat);
        }

        if (this.subcategories.length === 0)
            return;

        let titleMap = selection.map(subcat => subcat.title);
        for (var i = this.subcategories.length - 1; i >= 0; i--) {
            let subcat = this.subcategories[i];
            if (!titleMap.includes(subcat.title))
               await this.removeSubcategory(i)
        }

        await this.updateFlag();
    }

    async addCompendiumSubcategory(compendium) {
        if (this.subcategories.some(c => c.compendiumId === compendium.id))
            return;

        if (!CompendiumHelper.exists(compendium.id))
            return;

        let hudCompendium = new CompendiumSubcategory(this.filterManager, this.key, compendium.id, compendium.title);
        hudCompendium.createFilter();
        await hudCompendium.submitFilterSuggestions();

        this.subcategories.push(hudCompendium);
    }
    
    addMacroSubcategory(choice) {
        if (this.subcategories.some(c => c.title === choice.title))
            return;

        let subcategory = new MacroSubcategory(this.filterManager, this.key, choice.title);
        subcategory.createFilter();
        subcategory.submitFilterSuggestions();

        this.subcategories.push(subcategory);
    }

    async updateFlag() {
        let update = {categories: {[this.key]: {title: this.title, id: this.id, push: this.push, core: this.core}}};
        await game.user.update({flags: {'token-action-hud': update}});

        for (let subcategory of this.subcategories) {
            await subcategory.updateFlag(this.key);
        }
    }

    async removeSubcategory(index) {
        let subcategory = this.subcategories[index];
        await subcategory.clearFilter();
        await subcategory.unsetFlag(this.key);
        this.subcategories.splice(index, 1);
    }

    async prepareForDelete() {
        await this.clearFilters();
        await this.unsetFlag();
    }

    async clearFilters() {
        for (let c of this.subcategories) {
            await c.clearFilter();
        }
    }

    async unsetFlag() {
        await game.user.setFlag('token-action-hud', 'categories', {[`-=${this.key}`]: null});
    }

    asTagifyEntry() {
        return {id: this.id, value: this.title}
    }

    getSubcategoriesAsTagifyEntries() {
        return this.subcategories.map(c => c.asTagifyEntry())
    }
}

<|start_filename|>scripts/rollHandlers/dnd5e/pre-magicItems.js<|end_filename|>

import { PreRollHandler } from '../preRollHandler.js';

export class MagicItemsPreRollHandler extends PreRollHandler {
    constructor() {super();}

    /** @override */
    prehandleActionEvent(event, encodedValue) {
        let payload = encodedValue.split('|');
        
        if (payload.length != 3)
            return false;
        
        let macroType = payload[0];
        let tokenId = payload[1];
        let actionId = payload[2];

        if (macroType != 'magicItem')
            return false;

        this._magicItemMacro(event, tokenId, actionId);
        return true;
    }

    _magicItemMacro(event, tokenId, actionId) {
        let actor = super.getActor(tokenId);
        let actionParts = actionId.split('>');

        let itemId = actionParts[0];
        let magicEffectId = actionParts[1];

        let magicItemActor = MagicItems.actor(actor._id);

        magicItemActor.roll(itemId, magicEffectId);
        
        Hooks.callAll('forceUpdateTokenActionHUD');
    }
}

<|start_filename|>scripts/rollHandlers/bitd/bitd-base.js<|end_filename|>
import { RollHandler } from "../rollHandler.js"

export class RollHandlerBaseBitD extends RollHandler {
    constructor() {
        super();
    }

    /** @override */
    doHandleActionEvent(event, encodedValue) {
        let payload = encodedValue.split('|');
        
        if (payload.length != 3) {
            super.throwInvalidValueErr();
        }
        
        let macroType = payload[0];
        let tokenId = payload[1];
        let actionId = payload[2];

        let actor = super.getActor(tokenId);

        actor.rollAttributePopup(actionId)
    }

    rollItemMacro(event, actor, actionId) {
        actor.item.find(i => i.data.id === actionId).roll(event);
    }
}


<|start_filename|>scripts/managers/cthack.js<|end_filename|>
import { SystemManager } from "./manager.js";
import { ActionHandlerCthack as ActionHandler } from "../actions/cthack/cthack-actions.js"
import { RollHandlerBaseCthack as Core } from "../rollHandlers/cthack/cthack-base.js"
import * as settings from '../settings/cthack-settings.js';

export class CthackSystemManager extends SystemManager {

    constructor(appName) {
        super(appName);
    }

    /** @override */
    doGetActionHandler(filterManager, categoryManager) {
        console.log("startup");
        let actionHandler = new ActionHandler(filterManager, categoryManager);
        return actionHandler;
    }

    /** @override */
    getAvailableRollHandlers() {
        let choices = { 'core': 'Core Cthack' };

        return choices;
    }

    /** @override */
    doGetRollHandler(handlerId) {
        return new Core();
    }

    /** @override */
    doRegisterSettings(appName, updateFunc) {
        settings.register(appName, updateFunc);
    }
}