diff --git a/languages/br.json b/languages/br.json index 1e88b72..e6c9aa3 100644 --- a/languages/br.json +++ b/languages/br.json @@ -18,8 +18,10 @@ "bonusActions": "Ações de bônus", "cantrips": "Truques", "channelDivinity": "Channel Divinity", + "check": "Check", "checks": "Testes", "classFeatures": "Class Features", + "condition": "Condition", "conditions": "Condições", "crewActions": "Ações da tripulação", "defensiveTactics": "Defensive Tactics", @@ -29,6 +31,7 @@ "fightingStyles": "Fighting Styles", "huntersPrey": "Hunter's Prey", "innateSpells": "Innate Spells", + "item": "Item", "kiAbilities": "Ki Abilities", "lair": "Covil", "lairActions": "Ações de covil", @@ -50,6 +53,7 @@ "runes": "Runes", "saves": "Salvaguarda", "superiorHuntersDefense": "Superior Hunter's Defense", + "skill": "Skill", "skills": "Pericias", "settings": { "abbreviateSkills": { diff --git a/languages/cn.json b/languages/cn.json index 52abfe2..5db7ef3 100644 --- a/languages/cn.json +++ b/languages/cn.json @@ -18,8 +18,10 @@ "bonusActions": "獎勵行動", "cantrips": "Cantrips", "channelDivinity": "Channel Divinity", + "check": "Check", "checks": "检定", "classFeatures": "Class Features", + "condition": "Condition", "conditions": "状态", "crewActions": "船員行動", "defensiveTactics": "Defensive Tactics", @@ -29,6 +31,7 @@ "fightingStyles": "Fighting Styles", "huntersPrey": "Hunter's Prey", "innateSpells": "Innate Spells", + "item": "Item", "kiAbilities": "Ki Abilities", "lair": "巢穴", "lairActions": "巢穴行動", @@ -48,6 +51,7 @@ "rests": "休息", "rollInitiative": "先攻掷骰", "runes": "Runes", + "skill": "Skill", "skills": "技能", "superiorHuntersDefense": "Superior Hunter's Defense", "settings": { diff --git a/languages/de.json b/languages/de.json index a2dd45a..5d168f9 100644 --- a/languages/de.json +++ b/languages/de.json @@ -18,8 +18,10 @@ "bonusActions": "Bonus Actions", "cantrips": "Cantrips", "channelDivinity": "Channel Divinity", + "check": "Check", "checks": "Checks", "classFeatures": "Class Features", + "condition": "Condition", "conditions": "Conditions", "crewActions": "Crew Actions", "defensiveTactics": "Defensive Tactics", @@ -29,6 +31,7 @@ "fightingStyles": "Fighting Styles", "huntersPrey": "Hunter's Prey", "innateSpells": "Innate Spells", + "item": "Item", "kiAbilities": "Ki Abilities", "lair": "Lair", "lairActions": "Lair Actions", @@ -48,6 +51,7 @@ "rests": "Rests", "rollInitiative": "Initiativewurf", "runes": "Runes", + "skill": "Skill", "skills": "Skills", "superiorHuntersDefense": "Superior Hunter's Defense", "settings": { diff --git a/languages/en.json b/languages/en.json index e0d91a0..5023779 100644 --- a/languages/en.json +++ b/languages/en.json @@ -18,8 +18,10 @@ "bonusActions": "Bonus Actions", "cantrips": "Cantrips", "channelDivinity": "Channel Divinity", + "check": "Check", "checks": "Checks", "classFeatures": "Class Features", + "condition": "Condition", "conditions": "Conditions", "crewActions": "Crew Actions", "defensiveTactics": "Defensive Tactics", @@ -29,6 +31,7 @@ "fightingStyles": "Fighting Styles", "huntersPrey": "Hunter's Prey", "innateSpells": "Innate Spells", + "item": "Item", "kiAbilities": "Ki Abilities", "lair": "Lair", "lairActions": "Lair Actions", @@ -48,6 +51,7 @@ "rests": "Rests", "rollInitiative": "Roll Initiative", "runes": "Runes", + "skill": "Skill", "skills": "Skills", "superiorHuntersDefense": "Superior Hunter's Defense", "settings": { diff --git a/languages/es.json b/languages/es.json index 830d48e..a1eaac1 100644 --- a/languages/es.json +++ b/languages/es.json @@ -18,8 +18,10 @@ "bonusActions": "Acciones de bonificación", "cantrips": "Cantrips", "channelDivinity": "Channel Divinity", + "check": "Check", "checks": "Pruebas", "classFeatures": "Class Features", + "condition": "Condition", "conditions": "Estados", "crewActions": "Acciones de la tripulación", "defensiveTactics": "Defensive Tactics", @@ -29,6 +31,7 @@ "fightingStyles": "Fighting Styles", "huntersPrey": "Hunter's Prey", "innateSpells": "Innate Spells", + "item": "Item", "kiAbilities": "Ki Abilities", "lair": "Guarida", "lairActions": "Acciones de guarida", @@ -48,6 +51,7 @@ "rests": "Descansos", "rollInitiative": "Tirar iniciativa", "runes": "Runes", + "skill": "Skill", "skills": "Habilidades", "superiorHuntersDefense": "Superior Hunter's Defense", "settings": { diff --git a/languages/fr.json b/languages/fr.json index c3f8248..afd2da6 100644 --- a/languages/fr.json +++ b/languages/fr.json @@ -18,8 +18,10 @@ "bonusActions": "Actions bonus", "cantrips": "Cantrips", "channelDivinity": "Channel Divinity", + "check": "Check", "checks": "Tests", "classFeatures": "Class Features", + "condition": "Condition", "conditions": "Conditions", "crewActions": "Actions de l'équipage", "defensiveTactics": "Defensive Tactics", @@ -29,6 +31,7 @@ "fightingStyles": "Fighting Styles", "huntersPrey": "Hunter's Prey", "innateSpells": "Innate Spells", + "item": "Item", "kiAbilities": "Ki Abilities", "lair": "Antre", "lairActions": "Actions de antre", @@ -48,6 +51,7 @@ "rests": "Repos", "rollInitiative": "Lancer l'initiative", "runes": "Runes", + "skill": "Skill", "skills": "Compétences", "superiorHuntersDefense": "Superior Hunter's Defense", "settings": { diff --git a/languages/it.json b/languages/it.json index 5a3bc7c..e7b8916 100644 --- a/languages/it.json +++ b/languages/it.json @@ -18,8 +18,10 @@ "bonusActions": "Azioni bonus", "cantrips": "Cantrips", "channelDivinity": "Channel Divinity", + "check": "Check", "checks": "Caratteristiche", "classFeatures": "Class Features", + "condition": "Condition", "conditions": "Condizioni", "crewActions": "Azioni dell'equipaggio", "defensiveTactics": "Defensive Tactics", @@ -29,6 +31,7 @@ "fightingStyles": "Fighting Styles", "huntersPrey": "Hunter's Prey", "innateSpells": "Innate Spells", + "item": "Item", "kiAbilities": "Ki Abilities", "lair": "Tana", "lairActions": "Azioni di Tana", @@ -48,6 +51,7 @@ "rests": "Riposi", "rollInitiative": "Tira Iniziativa", "runes": "Runes", + "skill": "Skill", "skills": "Abilità", "superiorHuntersDefense": "Superior Hunter's Defense", "settings": { diff --git a/languages/ja.json b/languages/ja.json index 5ac541d..19f3337 100644 --- a/languages/ja.json +++ b/languages/ja.json @@ -18,8 +18,10 @@ "bonusActions": "ボーナス・アクション", "cantrips": "Cantrips", "channelDivinity": "Channel Divinity", + "check": "Check", "checks": "判定", "classFeatures": "Class Features", + "condition": "Condition", "conditions": "状態", "crewActions": "乗組員の行動", "defensiveTactics": "Defensive Tactics", @@ -29,6 +31,7 @@ "fightingStyles": "Fighting Styles", "huntersPrey": "Hunter's Prey", "innateSpells": "Innate Spells", + "item": "Item", "kiAbilities": "Ki Abilities", "lair": "住処", "lairActions": "住処アクション", @@ -48,6 +51,7 @@ "rests": "休憩", "rollInitiative": "イニシアチブロール", "runes": "Runes", + "skill": "Skill", "skills": "技能", "superiorHuntersDefense": "Superior Hunter's Defense", "settings": { diff --git a/languages/ko.json b/languages/ko.json index 54cd1bd..f1795b5 100644 --- a/languages/ko.json +++ b/languages/ko.json @@ -18,8 +18,10 @@ "bonusActions": "보너스 액션", "cantrips": "Cantrips", "channelDivinity": "Channel Divinity", + "check": "Check", "checks": "판정", "classFeatures": "Class Features", + "condition": "Condition", "conditions": "상태", "crewActions": "승무원 조치", "defensiveTactics": "Defensive Tactics", @@ -29,6 +31,7 @@ "fightingStyles": "Fighting Styles", "huntersPrey": "Hunter's Prey", "innateSpells": "Innate Spells", + "item": "Item", "kiAbilities": "Ki Abilities", "lair": "소굴", "lairActions": "소굴 액션", @@ -48,6 +51,7 @@ "rests": "휴식", "rollInitiative": "우선권 굴림", "runes": "Runes", + "skill": "Skill", "skills": "기술", "superiorHuntersDefense": "Superior Hunter's Defense", "settings": { diff --git a/languages/pl.json b/languages/pl.json index 1567172..2745f03 100644 --- a/languages/pl.json +++ b/languages/pl.json @@ -18,8 +18,10 @@ "bonusActions": "Akcje bonusowe", "cantrips": "Cantrips", "channelDivinity": "Channel Divinity", + "check": "Check", "checks": "Sprawdzenie", "classFeatures": "Class Features", + "condition": "Condition", "conditions": "Stany", "crewActions": "Akcje załogi", "defensiveTactics": "Defensive Tactics", @@ -29,6 +31,7 @@ "fightingStyles": "Fighting Styles", "huntersPrey": "Hunter's Prey", "innateSpells": "Innate Spells", + "item": "Item", "kiAbilities": "Ki Abilities", "lair": "Matecznik", "lairActions": "Matecznik akcje", @@ -48,6 +51,7 @@ "rests": "Odpoczynki", "rollInitiative": "Rzut na Inicjatywę", "runes": "Runes", + "skill": "Skill", "skills": "Umiejętności", "superiorHuntersDefense": "Superior Hunter's Defense", "settings": { diff --git a/module.json b/module.json index d45896c..855fdb0 100644 --- a/module.json +++ b/module.json @@ -89,7 +89,7 @@ "compatibility": [ { "minimum": "2.1.0", - "verified": "2.1.4" + "verified": "2.1.5" } ] } @@ -100,9 +100,9 @@ "type": "module", "compatibility": [ { - "minimum": "1.2.0", - "maximum": "1.2", - "verified": "1.2.0" + "minimum": "1.3.0", + "maximum": "1.3", + "verified": "1.3.0" } ] } diff --git a/scripts/action-handler.js b/scripts/action-handler.js index d65be26..a7b3f48 100644 --- a/scripts/action-handler.js +++ b/scripts/action-handler.js @@ -1,142 +1,101 @@ // System Module Imports -import { ACTIVATION_TYPE_ICON, PREPARED_ICON, PROFICIENCY_LEVEL_ICON } from './constants.js' +import { ACTIVATION_TYPE_ICON, ACTION_TYPE, PREPARED_ICON, PROFICIENCY_LEVEL_ICON, SUBCATEGORY } from './constants.js' import { Utils } from './utils.js' -// Core Module Imports -import { CoreActionHandler, CoreUtils, Logger } from './config.js' +export let ActionHandler = null -export class ActionHandler extends CoreActionHandler { +Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => { + ActionHandler = class ActionHandler extends coreModule.api.ActionHandler { // Initialize actor and token variables - actor = null - actors = null - actorId = null - actorType = null - character = null - token = null - tokenId = null - - // Initialize items variable - items = null - - // Initialize setting variables - abbreviateSkills = null - displaySpellInfo = null - showItemsWithoutActivationCosts = null - showUnchargedItems = null - showUnequippedItems = null - showUnpreparedSpells = null - - // Initialize subcategoryIds variables - subcategoryIds = null - activationSubcategoryIds = null - effectSubcategoryIds = null - featureSubcategoryIds = null - inventorySubcategoryIds = null - spellSubcategoryIds = null - - // Initialize action variables - featureActions = null - inventoryActions = null - spellActions = null - - /** - * Build System Actions - * @override - * @param {object} actionList - * @param {object} character - * @param {array} subcategoryIds - * @returns {object} - */ - async buildSystemActions (character, subcategoryIds) { + actors = null + tokens = null + actorType = null + + // Initialize items variable + items = null + + // Initialize setting variables + abbreviateSkills = null + displaySpellInfo = null + showItemsWithoutActivationCosts = null + showUnchargedItems = null + showUnequippedItems = null + showUnpreparedSpells = null + + // Initialize subcategoryIds variables + activationSubcategoryIds = null + featureSubcategoryIds = null + inventorySubcategoryIds = null + spellSubcategoryIds = null + + // Initialize action variables + featureActions = null + inventoryActions = null + spellActions = null + + /** + * Build System Actions + * @override + * @param {array} subcategoryIds + * @returns {object} + */ + async buildSystemActions (subcategoryIds) { // Set actor and token variables - this.actor = character?.actor - this.actorId = this.actor?.id ?? 'multi' - this.actors = (this.actorId === 'multi') ? this._getActors() : [this.actor] - this.actorType = this.actor?.type - this.token = character?.token - this.tokenId = this.token?.id ?? 'multi' - - // Set items variable - if (this.actorId !== 'multi') { - let items = this.actor.items - items = this._discardSlowItems(items) - items = this.sortItemsByName(items) - this.items = items - } + this.actors = (!this.actor) ? this._getActors() : [this.actor] + this.tokens = (!this.token) ? this._getTokens() : [this.token] + this.actorType = this.actor?.type + + // Set items variable + if (this.actor) { + let items = this.actor.items + items = this._discardSlowItems(items) + items = this.sortItemsByName(items) + this.items = items + } + + // Set settings variables + this.abbreviateSkills = Utils.getSetting('abbreviateSkills') + this.displaySpellInfo = Utils.getSetting('displaySpellInfo') + this.showItemsWithoutActivationCosts = Utils.getSetting('showItemsWithoutActivationCosts') + this.showUnchargedItems = Utils.getSetting('showUnchargedItems') + this.showUnequippedItems = Utils.getSetting('showUnequippedItems') + this.showUnpreparedSpells = Utils.getSetting('showUnpreparedSpells') + + this.activationSubcategoryIds = [ + 'actions', + 'bonus-actions', + 'crew-actions', + 'lair-actions', + 'legendary-actions', + 'reactions', + 'other-actions' + ] - // Set settings variables - this.abbreviateSkills = Utils.getSetting('abbreviateSkills') - this.displaySpellInfo = Utils.getSetting('displaySpellInfo') - this.showItemsWithoutActivationCosts = Utils.getSetting('showItemsWithoutActivationCosts') - this.showUnchargedItems = Utils.getSetting('showUnchargedItems') - this.showUnequippedItems = Utils.getSetting('showUnequippedItems') - this.showUnpreparedSpells = Utils.getSetting('showUnpreparedSpells') - - // Set subcategory variables - this.subcategoryIds = subcategoryIds - - this.activationSubcategoryIds = subcategoryIds.filter((subcategoryId) => - subcategoryId === 'actions' || - subcategoryId === 'bonus-actions' || - subcategoryId === 'crew-actions' || - subcategoryId === 'lair-actions' || - subcategoryId === 'legendary-actions' || - subcategoryId === 'reactions' || - subcategoryId === 'other-actions' - ) - - this.effectSubcategoryIds = subcategoryIds.filter((subcategoryId) => - subcategoryId === 'passive-effects' || - subcategoryId === 'temporary-effects' - ) - - this.featureSubcategoryIds = subcategoryIds.filter((subcategoryId) => - subcategoryId === 'active-features' || - subcategoryId === 'passive-features' || - subcategoryId === 'background-features' || - subcategoryId === 'class-features' || - subcategoryId === 'feats' || - subcategoryId === 'monster-features' || - subcategoryId === 'race-features' || - subcategoryId === 'artificer-infusions' || - subcategoryId === 'channel-divinity' || - subcategoryId === 'defensive-tactics' || - subcategoryId === 'eldritch-invocations' || - subcategoryId === 'elemental-disciplines' || - subcategoryId === 'fighting-styles' || - subcategoryId === 'hunters-prey' || - subcategoryId === 'ki-abilities' || - subcategoryId === 'maneuvers' || - subcategoryId === 'metamagic-options' || - subcategoryId === 'multiattacks' || - subcategoryId === 'pact-boons' || - subcategoryId === 'psionic-powers' || - subcategoryId === 'runes' || - subcategoryId === 'superior-hunters-defense' - ) - - this.spellSubcategoryIds = subcategoryIds.filter((subcategoryId) => - subcategoryId === 'cantrips' || - subcategoryId === '1st-level-spells' || - subcategoryId === '2nd-level-spells' || - subcategoryId === '3rd-level-spells' || - subcategoryId === '4th-level-spells' || - subcategoryId === '5th-level-spells' || - subcategoryId === '6th-level-spells' || - subcategoryId === '7th-level-spells' || - subcategoryId === '8th-level-spells' || - subcategoryId === '9th-level-spells' || - subcategoryId === 'at-will-spells' || - subcategoryId === 'innate-spells' || - subcategoryId === 'pact-spells' - ) - - // Add subcategory ids for activation types - if (this.activationSubcategoryIds.length > 0) { this.featureSubcategoryIds = [ 'active-features', - 'passive-features' + 'passive-features', + 'background-features', + 'class-features', + 'feats', + 'monster-features', + 'race-features', + 'artificer-infusions', + 'channel-divinity', + 'defensive-tactics', + 'eldritch-invocations', + 'elemental-disciplines', + 'fighting-styles', + 'hunters-prey', + 'ki-abilities', + 'maneuvers', + 'metamagic-options', + 'multiattacks', + 'pact-boons', + 'psionic-powers', + 'runes', + 'superior-hunters-defense' ] + this.spellSubcategoryIds = [ 'cantrips', '1st-level-spells', @@ -152,1183 +111,1156 @@ export class ActionHandler extends CoreActionHandler { 'innate-spells', 'pact-spells' ] - } - if (this.actorType === 'character' || this.actorType === 'npc') { - this.inventorySubcategoryIds = subcategoryIds.filter((subcategoryId) => - subcategoryId === 'equipped' || - subcategoryId === 'consumables' || - subcategoryId === 'containers' || - subcategoryId === 'equipment' || - subcategoryId === 'loot' || - subcategoryId === 'tools' || - subcategoryId === 'weapons' || - subcategoryId === 'unequipped' - ) - - // Add subcategory ids for activation types - if (this.activationSubcategoryIds.length > 0) { + if (this.actorType === 'character' || this.actorType === 'npc') { this.inventorySubcategoryIds = [ + 'equipped', 'consumables', 'containers', 'equipment', 'loot', 'tools', - 'weapons' + 'weapons', + 'unequipped' ] - } - this._buildCharacterActions() - } - if (this.actorType === 'vehicle') { - this.inventorySubcategoryIds = this.subcategoryIds.filter((subcategoryId) => - subcategoryId === 'consumables' || - subcategoryId === 'equipment' || - subcategoryId === 'tools' || - subcategoryId === 'weapons' - ) - - // Add subcategory ids for activation types - if (this.activationSubcategoryIds.length > 0) { + this._buildCharacterActions() + } + if (this.actorType === 'vehicle') { this.inventorySubcategoryIds = [ 'consumables', 'equipment', 'tools', 'weapons' ] - } - this._buildVehicleActions() - } - if (!this.actor) { - this._buildMultipleTokenActions() + this._buildVehicleActions() + } + if (!this.actor) { + this._buildMultipleTokenActions() + } } - } - - /** - * Build Character Actionss - * @private - * @returns {object} - */ - async _buildCharacterActions () { - this._buildAbilities('ability', 'abilities') - this._buildAbilities('abilityCheck', 'checks') - this._buildAbilities('abilitySave', 'saves') - this._buildCombat() - this._buildConditions() - this._buildEffects() - this._buildFeatures() - this._buildInventory() - this._buildRests() - this._buildSkills() - this._buildSpells() - this._buildUtility() - } - /** - * Build Vehicle Actions - * @private - * @returns {object} - */ - async _buildVehicleActions () { - this._buildAbilities('ability', 'abilities') - this._buildAbilities('abilityCheck', 'checks') - this._buildAbilities('abilitySave', 'saves') - this._buildCombat() - this._buildConditions() - this._buildEffects() - this._buildFeatures() - this._buildInventory() - this._buildUtility() - } + /** + * Build Character Actions + * @private + * @returns {object} + */ + async _buildCharacterActions () { + this._buildAbilities('ability', 'abilities') + this._buildAbilities('check', 'checks') + this._buildAbilities('save', 'saves') + this._buildCombat() + this._buildConditions() + this._buildEffects() + this._buildFeatures() + this._buildInventory() + this._buildRests() + this._buildSkills() + this._buildSpells() + this._buildUtility() + } - /** - * Build Multiple Token Actions - * @private - * @returns {object} - */ - async _buildMultipleTokenActions () { - this._buildAbilities('ability', 'abilities') - this._buildAbilities('abilityCheck', 'checks') - this._buildAbilities('abilitySave', 'saves') - this._buildCombat() - this._buildConditions() - this._buildRests() - this._buildSkills() - this._buildUtility() - } + /** + * Build Vehicle Actions + * @private + * @returns {object} + */ + async _buildVehicleActions () { + this._buildAbilities('ability', 'abilities') + this._buildAbilities('check', 'checks') + this._buildAbilities('save', 'saves') + this._buildCombat() + this._buildConditions() + this._buildEffects() + this._buildFeatures() + this._buildInventory() + this._buildUtility() + } - /** - * Build Abilities - * @private - * @param {string} actionType - * @param {string} subcategoryId - */ - _buildAbilities (actionType, subcategoryId) { - // Exit if no subcategory exists - if (!this.subcategoryIds.includes(subcategoryId)) return + /** + * Build Multiple Token Actions + * @private + * @returns {object} + */ + async _buildMultipleTokenActions () { + this._buildAbilities('ability', 'abilities') + this._buildAbilities('check', 'checks') + this._buildAbilities('save', 'saves') + this._buildCombat() + this._buildConditions() + this._buildRests() + this._buildSkills() + this._buildUtility() + } + /** + * Build Abilities + * @private + * @param {string} actionType + * @param {string} subcategoryId + */ + _buildAbilities (actionType, subcategoryId) { // Get abilities - const abilities = (this.actorId === 'multi') ? game.dnd5e.config.abilities : this.actor.system.abilities - - // Exit if no abilities exist - if (abilities.length === 0) return - - // Get actions - const actions = Object.entries(abilities) - .filter((ability) => abilities[ability[0]].value !== 0) - .map((ability) => { - const id = ability[0] - const abbreviatedName = id.charAt(0).toUpperCase() + id.slice(1) - const name = this.abbreviateSkills ? abbreviatedName : game.dnd5e.config.abilities[id] - const encodedValue = [actionType, this.actorId, this.tokenId, id].join(this.delimiter) - const icon = (subcategoryId !== 'checks') ? this._getProficiencyIcon(abilities[id].proficient) : '' - return { - id, - name, - encodedValue, - icon, - selected: true - } - }) + const abilities = (!this.actor) ? game.dnd5e.config.abilities : this.actor.system.abilities + + // Exit if no abilities exist + if (abilities.length === 0) return + + // Get actions + const actions = Object.entries(abilities) + .filter((ability) => abilities[ability[0]].value !== 0) + .map((ability) => { + const abilityId = ability[0] + const id = `${actionType}-${ability[0]}` + const abbreviatedName = abilityId.charAt(0).toUpperCase() + abilityId.slice(1) + const name = this.abbreviateSkills ? abbreviatedName : game.dnd5e.config.abilities[abilityId] + // Localise + const actionTypeName = `${coreModule.api.Utils.i18n(ACTION_TYPE[actionType])}: ` ?? '' + const listName = `${actionTypeName}${game.dnd5e.config.abilities[abilityId]}` + const encodedValue = [actionType, abilityId].join(this.delimiter) + const icon = (subcategoryId !== 'checks') ? this._getProficiencyIcon(abilities[abilityId].proficient) : '' + return { + id, + name, + encodedValue, + icon, + listName + } + }) - // Create subcategory data - const subcategoryData = { id: subcategoryId, type: 'system' } + // Create subcategory data + const subcategoryData = { id: subcategoryId, type: 'system' } - // Add actions to action list - this.addActionsToActionList(actions, subcategoryData) - } + // Add actions to action list + this.addActionsToActionList(actions, subcategoryData) + } - async buildActivations (items, subcategoryData, actionType = 'item') { + async buildActivations (items, subcategoryData, actionType = 'item') { // Create map of items according to activation type - const activationItems = new Map() - - // Create subcategory mappings - const subcategoryMappings = { - actions: 'action', - 'bonus-actions': 'bonus', - 'crew-actions': 'crew', - 'lair-actions': 'lair', - 'legendary-actions': 'legendary', - reactions: 'reaction', - 'other-actions': 'other' - } + const activationItems = new Map() + + // Create subcategory mappings + const subcategoryMappings = { + actions: 'action', + 'bonus-actions': 'bonus', + 'crew-actions': 'crew', + 'lair-actions': 'lair', + 'legendary-actions': 'legendary', + reactions: 'reaction', + 'other-actions': 'other' + } - const activationTypes = ['action', 'bonus', 'crew', 'lair', 'legendary', 'reaction'] + const activationTypes = ['action', 'bonus', 'crew', 'lair', 'legendary', 'reaction'] - // Loop through items - for (const [key, value] of items) { - const activationType = value.system?.activation?.type - const activationTypeOther = (activationTypes.includes(activationType)) ? activationType : 'other' - if (!activationItems.has(activationTypeOther)) activationItems.set(activationTypeOther, new Map()) - activationItems.get(activationTypeOther).set(key, value) - } + // Loop through items + for (const [key, value] of items) { + const activationType = value.system?.activation?.type + const activationTypeOther = (activationTypes.includes(activationType)) ? activationType : 'other' + if (!activationItems.has(activationTypeOther)) activationItems.set(activationTypeOther, new Map()) + activationItems.get(activationTypeOther).set(key, value) + } - // Loop through action subcategory ids - for (const subcategoryId of this.activationSubcategoryIds) { - const activationType = subcategoryMappings[subcategoryId] + // Loop through action subcategory ids + for (const subcategoryId of this.activationSubcategoryIds) { + const activationType = subcategoryMappings[subcategoryId] - // Skip if no items exist - if (!activationItems.has(activationType)) continue + // Skip if no items exist + if (!activationItems.has(activationType)) continue - // Clone and add to subcategory data - const subcategoryDataClone = { ...subcategoryData, id: `${subcategoryId}+${subcategoryData.id}`, type: 'system-derived' } + // Clone and add to subcategory data + const subcategoryDataClone = { ...subcategoryData, id: `${subcategoryId}+${subcategoryData.id}`, type: 'system-derived' } - // Create parent subcategory data - const parentSubcategoryData = { id: subcategoryId, type: 'system' } + // Create parent subcategory data + const parentSubcategoryData = { id: subcategoryId, type: 'system' } - // Add subcategory to action list - await this.addSubcategoryToActionList(parentSubcategoryData, subcategoryDataClone) + // Add subcategory to action list + await this.addSubcategoryToActionList(parentSubcategoryData, subcategoryDataClone) - // Add spell slot info to subcategory - this.addSubcategoryInfo(subcategoryData) + // Add spell slot info to subcategory + this.addSubcategoryInfo(subcategoryData) - // Build actions - this._buildActions(activationItems.get(activationType), subcategoryDataClone, actionType) + // Build actions + this._buildActions(activationItems.get(activationType), subcategoryDataClone, actionType) + } } - } - /** - * Build Combat - * @private - */ - _buildCombat () { - // Exit if no subcategory exists - if (!this.subcategoryIds.includes('combat')) return - - const actionType = 'utility' - - // Set combat types - const combatTypes = { - initiative: { id: 'initiative', name: CoreUtils.i18n('tokenActionHud.dnd5e.rollInitiative') }, - endTurn: { id: 'endTurn', name: CoreUtils.i18n('tokenActionHud.endTurn') } - } + /** + * Build Combat + * @private + */ + _buildCombat () { + const actionType = 'utility' + + // Set combat types + const combatTypes = { + initiative: { id: 'initiative', name: coreModule.api.Utils.i18n('tokenActionHud.dnd5e.rollInitiative') }, + endTurn: { id: 'endTurn', name: coreModule.api.Utils.i18n('tokenActionHud.endTurn') } + } - // Delete endTurn for multiple tokens - if (game.combat?.current?.tokenId !== this.tokenId) delete combatTypes.endTurn + // Delete endTurn for multiple tokens + if (game.combat?.current?.tokenId !== this.token?.id) delete combatTypes.endTurn + + // Get actions + const actions = Object.entries(combatTypes).map((combatType) => { + const id = combatType[1].id + const name = combatType[1].name + const actionTypeName = `${coreModule.api.Utils.i18n(ACTION_TYPE[actionType])}: ` ?? '' + const listName = `${actionTypeName}${name}` + const encodedValue = [actionType, id].join(this.delimiter) + const info1 = {} + let cssClass = '' + if (combatType[0] === 'initiative' && game.combat) { + const tokenIds = canvas.tokens.controlled.map((token) => token.id) + const combatants = game.combat.combatants.filter((combatant) => tokenIds.includes(combatant.tokenId)) + + // Get initiative for single token + if (combatants.length === 1) { + const currentInitiative = combatants[0].initiative + info1.class = 'tah-spotlight' + info1.text = currentInitiative + } - // Get actions - const actions = Object.entries(combatTypes).map((combatType) => { - const id = combatType[1].id - const name = combatType[1].name - const encodedValue = [actionType, this.actorId, this.tokenId, id].join(this.delimiter) - const info1 = {} - let cssClass = '' - if (combatType[0] === 'initiative' && game.combat) { - const tokenIds = canvas.tokens.controlled.map((token) => token.id) - const combatants = game.combat.combatants.filter((combatant) => tokenIds.includes(combatant.tokenId)) - - // Get initiative for single token - if (combatants.length === 1) { - const currentInitiative = combatants[0].initiative - info1.class = 'tah-spotlight' - info1.text = currentInitiative + const active = combatants.length > 0 && (combatants.every((combatant) => combatant?.initiative)) ? ' active' : '' + cssClass = `toggle${active}` } + return { + id, + name, + encodedValue, + info1, + cssClass, + listName + } + }) - const active = combatants.length > 0 && (combatants.every((combatant) => combatant?.initiative)) ? ' active' : '' - cssClass = `toggle${active}` - } - return { - id, - name, - encodedValue, - info1, - cssClass, - selected: true - } - }) - - // Create subcategory data - const subcategoryData = { id: 'combat', type: 'system' } + // Create subcategory data + const subcategoryData = { id: 'combat', type: 'system' } - // Add actions to action list - this.addActionsToActionList(actions, subcategoryData) - } + // Add actions to action list + this.addActionsToActionList(actions, subcategoryData) + } - /** - * Build Conditions - * @private - */ - _buildConditions () { - // Exit if the no subcategory or token exists - if (!this.subcategoryIds.includes('conditions')) return - if (!this.token) return - - const actionType = 'condition' - - // Get conditions - const conditions = CONFIG.statusEffects.filter((condition) => condition.id !== '') - - // Exit if no conditions exist - if (conditions.length === 0) return - - // Get actions - const actions = conditions.map((condition) => { - const id = condition.id - const name = CoreUtils.i18n(condition.label) - const encodedValue = [actionType, this.actorId, this.tokenId, id].join(this.delimiter) - const active = this.actors.every((actor) => { - const effects = actor.effects - return effects - .map((effect) => effect.flags?.core?.statusId) - .some((statusId) => statusId === id) + /** + * Build Conditions + * @private + */ + _buildConditions () { + if (!this.token && this.tokens.length === 0) return + + const actionType = 'condition' + + // Get conditions + const conditions = CONFIG.statusEffects.filter((condition) => condition.id !== '') + + // Exit if no conditions exist + if (conditions.length === 0) return + + // Get actions + const actions = conditions.map((condition) => { + const id = condition.id + const name = coreModule.api.Utils.i18n(condition.label) + const actionTypeName = `${coreModule.api.Utils.i18n(ACTION_TYPE[actionType])}: ` ?? '' + const listName = `${actionTypeName}${name}` + const encodedValue = [actionType, id].join(this.delimiter) + const active = this.actors.every((actor) => { + const effects = actor.effects + return effects + .map((effect) => effect.flags?.core?.statusId) + .some((statusId) => statusId === id) + }) + ? ' active' + : '' + const cssClass = `toggle${active}` + const img = coreModule.api.Utils.getImage(condition) + return { + id, + name, + encodedValue, + img, + cssClass, + listName + } }) - ? ' active' - : '' - const cssClass = `toggle${active}` - const img = CoreUtils.getImage(condition) - return { - id, - name, - encodedValue, - img, - cssClass, - selected: true - } - }) - - // Create subcategory data - const subcategoryData = { id: 'conditions', type: 'system' } - - // Add actions to action list - this.addActionsToActionList(actions, subcategoryData) - } - /** - * Build Effects - * @private - */ - _buildEffects () { - // Exit if no subcategories exist - if (!this.effectSubcategoryIds) return + // Create subcategory data + const subcategoryData = { id: 'conditions', type: 'system' } - const actionType = 'effect' + // Add actions to action list + this.addActionsToActionList(actions, subcategoryData) + } - // Get effects - const effects = this.actor.effects + /** + * Build Effects + * @private + */ + _buildEffects () { + const actionType = 'effect' + + // Get effects + const effects = this.actor.effects + + // Exit if no effects exist + if (effects.size === 0) return + + // Map passive and temporary effects to new maps + const passiveEffects = new Map() + const temporaryEffects = new Map() + + // Iterate effects and add to a map based on the isTemporary value + for (const effect of effects) { + const key = effect.id + const isTemporary = effect.isTemporary + if (isTemporary) { + temporaryEffects.set(key, effect) + } else { + passiveEffects.set(key, effect) + } + } - // Exit if no effects exist - if (effects.size === 0) return + // Build passive effects + this._buildActions(passiveEffects, { id: 'passive-effects', type: 'system' }, actionType) - // Map passive and temporary effects to new maps - const passiveEffects = new Map() - const temporaryEffects = new Map() + // Build temporary effects + this._buildActions(temporaryEffects, { id: 'temporary-effects', type: 'system' }, actionType) + } - // Iterate effects and add to a map based on the isTemporary value - for (const effect of effects) { - const key = effect.id - const isTemporary = effect.isTemporary - if (isTemporary) { - temporaryEffects.set(key, effect) - } else { - passiveEffects.set(key, effect) + /** + * Build Features + * @private + */ + _buildFeatures () { + const actionType = 'feature' + + // Get feats + const feats = new Map() + for (const [key, value] of this.items) { + const type = value.type + if (type === 'feat') feats.set(key, value) } - } - // Build passive effects - if (this.effectSubcategoryIds.includes('passive-effects')) { - const subcategoryData = { id: 'passive-effects', type: 'system' } - this._buildActions(passiveEffects, subcategoryData, actionType) - } + // Early exit if no feats exist + if (feats.size === 0) return - // Build temporary effects - if (this.effectSubcategoryIds.includes('temporary-effects')) { - const subcategoryData = { id: 'temporary-effects', type: 'system' } - this._buildActions(temporaryEffects, subcategoryData, actionType) - } - } + // Map active and passive features to new maps + const featuresMap = new Map() - /** - * Build Features - * @private - */ - _buildFeatures () { - // Exit if no subcategories exist - if (!this.featureSubcategoryIds) return - - const actionType = 'feature' - - // Get feats - const feats = new Map() - for (const [key, value] of this.items) { - const type = value.type - if (type === 'feat') feats.set(key, value) - } + const featureTypes = [ + { type: 'background', subcategoryId: 'background-features' }, + { type: 'class', subcategoryId: 'class-features' }, + { type: 'monster', subcategoryId: 'monster-features' }, + { type: 'race', subcategoryId: 'race-features' }, + { type: 'feats', subcategoryId: 'feats' } + ] - // Early exit if no feats exist - if (feats.size === 0) return - - // Map active and passive features to new maps - const featuresMap = new Map() - - const featureTypes = [ - { type: 'background', subcategoryId: 'background-features' }, - { type: 'class', subcategoryId: 'class-features' }, - { type: 'monster', subcategoryId: 'monster-features' }, - { type: 'race', subcategoryId: 'race-features' }, - { type: 'feats', subcategoryId: 'feats' } - ] - - const classFeatureTypes = [ - { type: 'artificerInfusion', subcategoryId: 'artificer-infusions' }, - { type: 'channelDivinity', subcategoryId: 'channel-divinity' }, - { type: 'defensiveTactic', subcategoryId: 'defensive-tactics' }, - { type: 'eldritchInvocation', subcategoryId: 'eldritch-invocations' }, - { type: 'elementalDiscipline', subcategoryId: 'elemental-disciplines' }, - { type: 'fightingStyle', subcategoryId: 'fighting-styles' }, - { type: 'huntersPrey', subcategoryId: 'hunters-prey' }, - { type: 'ki', subcategoryId: 'ki-abilities' }, - { type: 'maneuver', subcategoryId: 'maneuvers' }, - { type: 'metamagic', subcategoryId: 'metamagic-options' }, - { type: 'multiattack', subcategoryId: 'multiattacks' }, - { type: 'pact', subcategoryId: 'pact-boons' }, - { type: 'psionicPower', subcategoryId: 'psionic-powers' }, - { type: 'rune', subcategoryId: 'runes' }, - { type: 'superiorHuntersDefense', subcategoryId: 'superior-hunters-defense' } - ] - - for (const [key, value] of feats) { - const activationType = value.system.activation?.type - const type = value.system.type.value - const subType = value.system.type?.subtype - const excludedActivationTypes = ['', 'lair', 'legendary'] - if (activationType && !excludedActivationTypes.includes(activationType)) { - if (!featuresMap.has('active-features')) featuresMap.set('active-features', new Map()) - featuresMap.get('active-features').set(key, value) - } - if (!activationType || activationType === '') { - if (!featuresMap.has('passive-features')) featuresMap.set('passive-features', new Map()) - featuresMap.get('passive-features').set(key, value) - } - for (const featureType of featureTypes) { - const subcategoryId = featureType.subcategoryId - if (featureType.type === type) { - if (!featuresMap.has(subcategoryId)) featuresMap.set(subcategoryId, new Map()) - featuresMap.get(subcategoryId).set(key, value) + const classFeatureTypes = [ + { type: 'artificerInfusion', subcategoryId: 'artificer-infusions' }, + { type: 'channelDivinity', subcategoryId: 'channel-divinity' }, + { type: 'defensiveTactic', subcategoryId: 'defensive-tactics' }, + { type: 'eldritchInvocation', subcategoryId: 'eldritch-invocations' }, + { type: 'elementalDiscipline', subcategoryId: 'elemental-disciplines' }, + { type: 'fightingStyle', subcategoryId: 'fighting-styles' }, + { type: 'huntersPrey', subcategoryId: 'hunters-prey' }, + { type: 'ki', subcategoryId: 'ki-abilities' }, + { type: 'maneuver', subcategoryId: 'maneuvers' }, + { type: 'metamagic', subcategoryId: 'metamagic-options' }, + { type: 'multiattack', subcategoryId: 'multiattacks' }, + { type: 'pact', subcategoryId: 'pact-boons' }, + { type: 'psionicPower', subcategoryId: 'psionic-powers' }, + { type: 'rune', subcategoryId: 'runes' }, + { type: 'superiorHuntersDefense', subcategoryId: 'superior-hunters-defense' } + ] + + for (const [key, value] of feats) { + const activationType = value.system.activation?.type + const type = value.system.type.value + const subType = value.system.type?.subtype + const excludedActivationTypes = ['', 'lair', 'legendary'] + if (activationType && !excludedActivationTypes.includes(activationType)) { + if (!featuresMap.has('active-features')) featuresMap.set('active-features', new Map()) + featuresMap.get('active-features').set(key, value) } - } - for (const featureType of classFeatureTypes) { - const subcategoryId = featureType.subcategoryId - if (subType && featureType.type === subType) { - if (!featuresMap.has(subcategoryId)) featuresMap.set(subcategoryId, new Map()) - featuresMap.get(subcategoryId).set(key, value) + if (!activationType || activationType === '') { + if (!featuresMap.has('passive-features')) featuresMap.set('passive-features', new Map()) + featuresMap.get('passive-features').set(key, value) + } + for (const featureType of featureTypes) { + const subcategoryId = featureType.subcategoryId + if (featureType.type === type) { + if (!featuresMap.has(subcategoryId)) featuresMap.set(subcategoryId, new Map()) + featuresMap.get(subcategoryId).set(key, value) + } + } + for (const featureType of classFeatureTypes) { + const subcategoryId = featureType.subcategoryId + if (subType && featureType.type === subType) { + if (!featuresMap.has(subcategoryId)) featuresMap.set(subcategoryId, new Map()) + featuresMap.get(subcategoryId).set(key, value) + } } } - } - // Create subcategory name mappings - const subcategoryNameMappings = { - 'active-features': CoreUtils.i18n('tokenActionHud.dnd5e.activeFeatures'), - 'passive-features': CoreUtils.i18n('tokenActionHud.dnd5e.passiveFeatures') - } + // Create subcategory name mappings + const subcategoryNameMappings = { + 'active-features': coreModule.api.Utils.i18n('tokenActionHud.dnd5e.activeFeatures'), + 'passive-features': coreModule.api.Utils.i18n('tokenActionHud.dnd5e.passiveFeatures') + } - // Loop through inventory subcateogry ids - for (const subcategoryId of this.featureSubcategoryIds) { - if (!featuresMap.has(subcategoryId)) continue + // Loop through inventory subcateogry ids + for (const subcategoryId of this.featureSubcategoryIds) { + if (!featuresMap.has(subcategoryId)) continue - // Create subcategory data - const subcategoryData = { - id: subcategoryId, - name: subcategoryNameMappings[subcategoryId] ?? '', - type: 'system' - } + // Create subcategory data + const subcategoryData = { + id: subcategoryId, + name: subcategoryNameMappings[subcategoryId] ?? '', + type: 'system' + } - const features = featuresMap.get(subcategoryId) + const features = featuresMap.get(subcategoryId) - // Build actions - this._buildActions(features, subcategoryData, actionType) + // Build actions + this._buildActions(features, subcategoryData, actionType) - // Build activations - if (subcategoryNameMappings[subcategoryId]) this.buildActivations(features, subcategoryData, actionType) + // Build activations + if (subcategoryNameMappings[subcategoryId]) this.buildActivations(features, subcategoryData, actionType) + } } - } - /** - * Build Inventory - * @private - */ - _buildInventory () { - // Exit if no subcategories exist - if (!this.inventorySubcategoryIds) return + /** + * Build Inventory + * @private + */ + _buildInventory () { // Exit early if no items exist - if (this.items.size === 0) return + if (this.items.size === 0) return - const inventoryMap = new Map() + const inventoryMap = new Map() - for (const [key, value] of this.items) { + for (const [key, value] of this.items) { // Set variables - const equipped = value.system.equipped - const hasQuantity = value.system?.quantity > 0 - const isActiveItem = this._isActiveItem(value) - const isUsableItem = this._isUsableItem(value) - const isEquippedItem = this._isEquippedItem(value) - const type = value.type - - // Set items into maps - if (hasQuantity && isActiveItem) { - if (equipped) { - if (!inventoryMap.has('equipped')) inventoryMap.set('equipped', new Map()) - inventoryMap.get('equipped').set(key, value) - } - if (!equipped) { - if (!inventoryMap.has('unequipped')) inventoryMap.set('unequipped', new Map()) - inventoryMap.get('unequipped').set(key, value) - } - if (isUsableItem && type === 'consumable') { - if (!inventoryMap.has('consumables')) inventoryMap.set('consumables', new Map()) - inventoryMap.get('consumables').set(key, value) - } - if (isEquippedItem) { - if (type === 'backpack') { - if (!inventoryMap.has('containers')) inventoryMap.set('containers', new Map()) - inventoryMap.get('containers').set(key, value) - } - if (type === 'equipment') { - if (!inventoryMap.has('equipment')) inventoryMap.set('equipment', new Map()) - inventoryMap.get('equipment').set(key, value) + const equipped = value.system.equipped + const hasQuantity = value.system?.quantity > 0 + const isActiveItem = this._isActiveItem(value) + const isUsableItem = this._isUsableItem(value) + const isEquippedItem = this._isEquippedItem(value) + const type = value.type + + // Set items into maps + if (hasQuantity && isActiveItem) { + if (equipped) { + if (!inventoryMap.has('equipped')) inventoryMap.set('equipped', new Map()) + inventoryMap.get('equipped').set(key, value) } - if (type === 'loot') { - if (!inventoryMap.has('loot')) inventoryMap.set('loot', new Map()) - inventoryMap.get('loot').set(key, value) + if (!equipped) { + if (!inventoryMap.has('unequipped')) inventoryMap.set('unequipped', new Map()) + inventoryMap.get('unequipped').set(key, value) } - if (type === 'tool') { - if (!inventoryMap.has('tools')) inventoryMap.set('tools', new Map()) - inventoryMap.get('tools').set(key, value) + if (isUsableItem && type === 'consumable') { + if (!inventoryMap.has('consumables')) inventoryMap.set('consumables', new Map()) + inventoryMap.get('consumables').set(key, value) } - if (type === 'weapon') { - if (!inventoryMap.has('weapons')) inventoryMap.set('weapons', new Map()) - inventoryMap.get('weapons').set(key, value) + if (isEquippedItem) { + if (type === 'backpack') { + if (!inventoryMap.has('containers')) inventoryMap.set('containers', new Map()) + inventoryMap.get('containers').set(key, value) + } + if (type === 'equipment') { + if (!inventoryMap.has('equipment')) inventoryMap.set('equipment', new Map()) + inventoryMap.get('equipment').set(key, value) + } + if (type === 'loot') { + if (!inventoryMap.has('loot')) inventoryMap.set('loot', new Map()) + inventoryMap.get('loot').set(key, value) + } + if (type === 'tool') { + if (!inventoryMap.has('tools')) inventoryMap.set('tools', new Map()) + inventoryMap.get('tools').set(key, value) + } + if (type === 'weapon') { + if (!inventoryMap.has('weapons')) inventoryMap.set('weapons', new Map()) + inventoryMap.get('weapons').set(key, value) + } } } } - } - - // Create subcategory name mappings - const subcategoryNameMappings = { - equipped: CoreUtils.i18n('DND5E.Equipped'), - unequipped: CoreUtils.i18n('DND5E.Unequipped'), - consumables: CoreUtils.i18n('ITEM.TypeConsumablePl'), - containers: CoreUtils.i18n('ITEM.TypeContainerPl'), - equipment: CoreUtils.i18n('ITEM.TypeEquipmentPl'), - loot: CoreUtils.i18n('ITEM.TypeLootPl'), - tools: CoreUtils.i18n('ITEM.TypeToolPl'), - weapons: CoreUtils.i18n('ITEM.TypeWeaponPl') - } - - // Loop through inventory subcateogry ids - for (const subcategoryId of this.inventorySubcategoryIds) { - if (!inventoryMap.has(subcategoryId)) continue - // Create subcategory data - const subcategoryData = { - id: subcategoryId, - name: subcategoryNameMappings[subcategoryId], - type: 'system' + // Create subcategory name mappings + const subcategoryNameMappings = { + equipped: coreModule.api.Utils.i18n('DND5E.Equipped'), + unequipped: coreModule.api.Utils.i18n('DND5E.Unequipped'), + consumables: coreModule.api.Utils.i18n('ITEM.TypeConsumablePl'), + containers: coreModule.api.Utils.i18n('ITEM.TypeContainerPl'), + equipment: coreModule.api.Utils.i18n('ITEM.TypeEquipmentPl'), + loot: coreModule.api.Utils.i18n('ITEM.TypeLootPl'), + tools: coreModule.api.Utils.i18n('ITEM.TypeToolPl'), + weapons: coreModule.api.Utils.i18n('ITEM.TypeWeaponPl') } - const inventory = inventoryMap.get(subcategoryId) - - // Build actions - this._buildActions(inventory, subcategoryData) + // Loop through inventory subcateogry ids + for (const subcategoryId of this.inventorySubcategoryIds) { + if (!inventoryMap.has(subcategoryId)) continue - // Build activations - if (this.activationSubcategoryIds) this.buildActivations(inventory, subcategoryData) - } - } - - /** - * Build Rests - * @private - */ - _buildRests () { - // Exit if no subcategory exists - if (!this.subcategoryIds.includes('rests')) return + // Create subcategory data + const subcategoryData = { + id: subcategoryId, + name: subcategoryNameMappings[subcategoryId], + type: 'system' + } - // Exit if every actor is not the character type - if (!this.actors.every(actor => actor.type === 'character')) return + const inventory = inventoryMap.get(subcategoryId) - const actionType = 'utility' + // Build actions + this._buildActions(inventory, subcategoryData) - // Set rest types - const restTypes = { - shortRest: { name: CoreUtils.i18n('DND5E.ShortRest') }, - longRest: { name: CoreUtils.i18n('DND5E.LongRest') } + // Build activations + if (this.activationSubcategoryIds) this.buildActivations(inventory, subcategoryData) + } } - // Get actions - const actions = Object.entries(restTypes) - .map((restType) => { - const id = restType[0] - const name = restType[1].name - const encodedValue = [actionType, this.actorId, this.tokenId, id].join(this.delimiter) - return { - id, - name, - encodedValue, - selected: true - } - }) + /** + * Build Rests + * @private + */ + _buildRests () { + // Exit if every actor is not the character type + if (!this.actors.every(actor => actor.type === 'character')) return - // Create subcategory data - const subcategoryData = { id: 'rests', type: 'system' } + const actionType = 'utility' - // Add actions to action list - this.addActionsToActionList(actions, subcategoryData) - } + // Set rest types + const restTypes = { + shortRest: { name: coreModule.api.Utils.i18n('DND5E.ShortRest') }, + longRest: { name: coreModule.api.Utils.i18n('DND5E.LongRest') } + } - /** - * Build Skills - * @private - */ - _buildSkills () { - // Exit if no subcategory exists - if (!this.subcategoryIds.includes('skills')) return - - const actionType = 'skill' - - // Get skills - const skills = (this.actorId === 'multi') ? game.dnd5e.config.skills : this.actor.system.skills - - // Exit if there are no skills - if (skills.length === 0) return - - // Get actions - const actions = Object.entries(skills) - .map((skill) => { - try { - const id = skill[0] - const abbreviatedName = id.charAt(0).toUpperCase() + id.slice(1) - const name = this.abbreviateSkills ? abbreviatedName : game.dnd5e.config.skills[id].label - const encodedValue = [actionType, this.actorId, this.tokenId, id].join(this.delimiter) - const icon = this._getProficiencyIcon(skills[id].value) + // Get actions + const actions = Object.entries(restTypes) + .map((restType) => { + const id = restType[0] + const name = restType[1].name + const actionTypeName = `${coreModule.api.Utils.i18n(ACTION_TYPE[actionType])}: ` ?? '' + const listName = `${actionTypeName}${name}` + const encodedValue = [actionType, id].join(this.delimiter) return { id, name, encodedValue, - icon + listName } - } catch (error) { - Logger.error(skill) - return null - } - }) - .filter((skill) => !!skill) + }) - // Create subcategory data - const subcategoryData = { id: 'skills', type: 'system' } + // Create subcategory data + const subcategoryData = { id: 'rests', type: 'system' } - // Add actions to action list - this.addActionsToActionList(actions, subcategoryData) - } + // Add actions to action list + this.addActionsToActionList(actions, subcategoryData) + } - /** - * Build Spells - */ - _buildSpells () { - // Exit if no subcategories exist - if (!this.spellSubcategoryIds) return + /** + * Build Skills + * @private + */ + _buildSkills () { + const actionType = 'skill' + + // Get skills + const skills = (!this.actor) ? game.dnd5e.config.skills : this.actor.system.skills + + // Exit if there are no skills + if (skills.length === 0) return + + // Get actions + const actions = Object.entries(skills) + .map((skill) => { + try { + const id = skill[0] + const abbreviatedName = id.charAt(0).toUpperCase() + id.slice(1) + const name = this.abbreviateSkills ? abbreviatedName : game.dnd5e.config.skills[id].label + const actionTypeName = `${coreModule.api.Utils.i18n(ACTION_TYPE[actionType])}: ` ?? '' + const listName = `${actionTypeName}${game.dnd5e.config.skills[id].label}` + const encodedValue = [actionType, id].join(this.delimiter) + const icon = this._getProficiencyIcon(skills[id].value) + return { + id, + name, + encodedValue, + icon, + listName + } + } catch (error) { + coreModule.api.Logger.error(skill) + return null + } + }) + .filter((skill) => !!skill) - const actionType = 'spell' + // Create subcategory data + const subcategoryData = { id: 'skills', type: 'system' } - const spellsMap = new Map() + // Add actions to action list + this.addActionsToActionList(actions, subcategoryData) + } - // Loop through items - for (const [key, value] of this.items) { - const type = value.type - if (type === 'spell') { - const isUsableItem = this._isUsableItem(value) - const isUsableSpell = this._isUsableSpell(value) - if (isUsableItem && isUsableSpell) { - const preparationMode = value.system.preparation.mode - switch (preparationMode) { - case 'atwill': - if (!spellsMap.has('at-will-spells')) spellsMap.set('at-will-spells', new Map()) - spellsMap.get('at-will-spells').set(key, value) - break - case 'innate': - if (!spellsMap.has('innate-spells')) spellsMap.set('innate-spells', new Map()) - spellsMap.get('innate-spells').set(key, value) - break - case 'pact': - if (!spellsMap.has('pact-spells')) spellsMap.set('pact-spells', new Map()) - spellsMap.get('pact-spells').set(key, value) - break - default: - { const level = value.system.level - switch (level) { - case 0: - if (!spellsMap.has('cantrips')) spellsMap.set('cantrips', new Map()) - spellsMap.get('cantrips').set(key, value) + /** + * Build Spells + */ + _buildSpells () { + const actionType = 'spell' + + const spellsMap = new Map() + + // Loop through items + for (const [key, value] of this.items) { + const type = value.type + if (type === 'spell') { + const isUsableItem = this._isUsableItem(value) + const isUsableSpell = this._isUsableSpell(value) + if (isUsableItem && isUsableSpell) { + const preparationMode = value.system.preparation.mode + switch (preparationMode) { + case 'atwill': + if (!spellsMap.has('at-will-spells')) spellsMap.set('at-will-spells', new Map()) + spellsMap.get('at-will-spells').set(key, value) break - case 1: - if (!spellsMap.has('1st-level-spells')) spellsMap.set('1st-level-spells', new Map()) - spellsMap.get('1st-level-spells').set(key, value) + case 'innate': + if (!spellsMap.has('innate-spells')) spellsMap.set('innate-spells', new Map()) + spellsMap.get('innate-spells').set(key, value) break - case 2: - if (!spellsMap.has('2nd-level-spells')) spellsMap.set('2nd-level-spells', new Map()) - spellsMap.get('2nd-level-spells').set(key, value) - break - case 3: - if (!spellsMap.has('3rd-level-spells')) spellsMap.set('3rd-level-spells', new Map()) - spellsMap.get('3rd-level-spells').set(key, value) - break - case 4: - if (!spellsMap.has('4th-level-spells')) spellsMap.set('4th-level-spells', new Map()) - spellsMap.get('4th-level-spells').set(key, value) - break - case 5: - if (!spellsMap.has('5th-level-spells')) spellsMap.set('5th-level-spells', new Map()) - spellsMap.get('5th-level-spells').set(key, value) - break - case 6: - if (!spellsMap.has('6th-level-spells')) spellsMap.set('6th-level-spells', new Map()) - spellsMap.get('6th-level-spells').set(key, value) - break - case 7: - if (!spellsMap.has('7th-level-spells')) spellsMap.set('7th-level-spells', new Map()) - spellsMap.get('7th-level-spells').set(key, value) - break - case 8: - if (!spellsMap.has('8th-level-spells')) spellsMap.set('8th-level-spells', new Map()) - spellsMap.get('8th-level-spells').set(key, value) - break - case 9: - if (!spellsMap.has('9th-level-spells')) spellsMap.set('9th-level-spells', new Map()) - spellsMap.get('9th-level-spells').set(key, value) + case 'pact': + if (!spellsMap.has('pact-spells')) spellsMap.set('pact-spells', new Map()) + spellsMap.get('pact-spells').set(key, value) break + default: + { const level = value.system.level + switch (level) { + case 0: + if (!spellsMap.has('cantrips')) spellsMap.set('cantrips', new Map()) + spellsMap.get('cantrips').set(key, value) + break + case 1: + if (!spellsMap.has('1st-level-spells')) spellsMap.set('1st-level-spells', new Map()) + spellsMap.get('1st-level-spells').set(key, value) + break + case 2: + if (!spellsMap.has('2nd-level-spells')) spellsMap.set('2nd-level-spells', new Map()) + spellsMap.get('2nd-level-spells').set(key, value) + break + case 3: + if (!spellsMap.has('3rd-level-spells')) spellsMap.set('3rd-level-spells', new Map()) + spellsMap.get('3rd-level-spells').set(key, value) + break + case 4: + if (!spellsMap.has('4th-level-spells')) spellsMap.set('4th-level-spells', new Map()) + spellsMap.get('4th-level-spells').set(key, value) + break + case 5: + if (!spellsMap.has('5th-level-spells')) spellsMap.set('5th-level-spells', new Map()) + spellsMap.get('5th-level-spells').set(key, value) + break + case 6: + if (!spellsMap.has('6th-level-spells')) spellsMap.set('6th-level-spells', new Map()) + spellsMap.get('6th-level-spells').set(key, value) + break + case 7: + if (!spellsMap.has('7th-level-spells')) spellsMap.set('7th-level-spells', new Map()) + spellsMap.get('7th-level-spells').set(key, value) + break + case 8: + if (!spellsMap.has('8th-level-spells')) spellsMap.set('8th-level-spells', new Map()) + spellsMap.get('8th-level-spells').set(key, value) + break + case 9: + if (!spellsMap.has('9th-level-spells')) spellsMap.set('9th-level-spells', new Map()) + spellsMap.get('9th-level-spells').set(key, value) + break + } + } } - } } } } - } - // Reverse sort spell slots by level - const systemSpells = Object.entries(this.actor.system.spells).reverse() - - // Set spell slot availability - let pactSlot = null - const spellSlots = [] - let spellSlotAvailable = this.showUnchargedItems - let pactSlotAvailable = this.showUnchargedItems - for (const [key, value] of systemSpells) { - const hasValue = value.value > 0 - const hasMax = value.max > 0 - const hasLevel = value.level > 0 - if (key === 'pact') { - if (!pactSlotAvailable && hasValue && hasMax && hasLevel) pactSlotAvailable = true - if (!hasLevel) pactSlotAvailable = false - value.slotAvailable = pactSlotAvailable - pactSlot = [key, value] - } - if (key.startsWith('spell') && key !== 'spell0') { - if (!spellSlotAvailable && hasValue && hasMax) spellSlotAvailable = true - value.slotAvailable = spellSlotAvailable - spellSlots.push([key, value]) - } else { - if (hasValue) { - value.slotsAvailable = true - spellSlots.push(key, value) + // Reverse sort spell slots by level + const systemSpells = Object.entries(this.actor.system.spells).reverse() + + // Set spell slot availability + let pactSlot = null + const spellSlots = [] + let spellSlotAvailable = this.showUnchargedItems + let pactSlotAvailable = this.showUnchargedItems + for (const [key, value] of systemSpells) { + const hasValue = value.value > 0 + const hasMax = value.max > 0 + const hasLevel = value.level > 0 + if (key === 'pact') { + if (!pactSlotAvailable && hasValue && hasMax && hasLevel) pactSlotAvailable = true + if (!hasLevel) pactSlotAvailable = false + value.slotAvailable = pactSlotAvailable + pactSlot = [key, value] + } + if (key.startsWith('spell') && key !== 'spell0') { + if (!spellSlotAvailable && hasValue && hasMax) spellSlotAvailable = true + value.slotAvailable = spellSlotAvailable + spellSlots.push([key, value]) + } else { + if (hasValue) { + value.slotsAvailable = true + spellSlots.push(key, value) + } } } - } - // Set equivalent spell slot where pact slot is available - if (pactSlot[1].slotAvailable) { - const pactSpellEquivalent = spellSlots.findIndex(spell => spell[0] === 'spell' + pactSlot[1].level) - spellSlots[pactSpellEquivalent][1].slotsAvailable = true - } + // Set equivalent spell slot where pact slot is available + if (pactSlot[1].slotAvailable) { + const pactSpellEquivalent = spellSlots.findIndex(spell => spell[0] === 'spell' + pactSlot[1].level) + spellSlots[pactSpellEquivalent][1].slotsAvailable = true + } - const subcategoryMappings = { - '1st-level-spells': { spellMode: 1, name: CoreUtils.i18n('tokenActionHud.dnd5e.1stLevelSpells') }, - '2nd-level-spells': { spellMode: 2, name: CoreUtils.i18n('tokenActionHud.dnd5e.2ndLevelSpells') }, - '3rd-level-spells': { spellMode: 3, name: CoreUtils.i18n('tokenActionHud.dnd5e.3rdLevelSpells') }, - '4th-level-spells': { spellMode: 4, name: CoreUtils.i18n('tokenActionHud.dnd5e.4thLevelSpells') }, - '5th-level-spells': { spellMode: 5, name: CoreUtils.i18n('tokenActionHud.dnd5e.5thLevelSpells') }, - '6th-level-spells': { spellMode: 6, name: CoreUtils.i18n('tokenActionHud.dnd5e.6thLevelSpells') }, - '7th-level-spells': { spellMode: 7, name: CoreUtils.i18n('tokenActionHud.dnd5e.7thLevelSpells') }, - '8th-level-spells': { spellMode: 8, name: CoreUtils.i18n('tokenActionHud.dnd5e.8thLevelSpells') }, - '9th-level-spells': { spellMode: 9, name: CoreUtils.i18n('tokenActionHud.dnd5e.9thLevelSpells') }, - 'at-will-spells': { spellMode: 'atwill', name: CoreUtils.i18n('tokenActionHud.dnd5e.atWillSpells') }, - cantrips: { spellMode: 0, name: CoreUtils.i18n('tokenActionHud.dnd5e.cantrips') }, - 'innate-spells': { spellMode: 'innate', name: CoreUtils.i18n('tokenActionHud.dnd5e.innateSpells') }, - 'pact-spells': { spellMode: 'pact', name: CoreUtils.i18n('tokenActionHud.dnd5e.pactSpells') } - } + const subcategoryMappings = { + '1st-level-spells': { spellMode: 1, name: coreModule.api.Utils.i18n('tokenActionHud.dnd5e.1stLevelSpells') }, + '2nd-level-spells': { spellMode: 2, name: coreModule.api.Utils.i18n('tokenActionHud.dnd5e.2ndLevelSpells') }, + '3rd-level-spells': { spellMode: 3, name: coreModule.api.Utils.i18n('tokenActionHud.dnd5e.3rdLevelSpells') }, + '4th-level-spells': { spellMode: 4, name: coreModule.api.Utils.i18n('tokenActionHud.dnd5e.4thLevelSpells') }, + '5th-level-spells': { spellMode: 5, name: coreModule.api.Utils.i18n('tokenActionHud.dnd5e.5thLevelSpells') }, + '6th-level-spells': { spellMode: 6, name: coreModule.api.Utils.i18n('tokenActionHud.dnd5e.6thLevelSpells') }, + '7th-level-spells': { spellMode: 7, name: coreModule.api.Utils.i18n('tokenActionHud.dnd5e.7thLevelSpells') }, + '8th-level-spells': { spellMode: 8, name: coreModule.api.Utils.i18n('tokenActionHud.dnd5e.8thLevelSpells') }, + '9th-level-spells': { spellMode: 9, name: coreModule.api.Utils.i18n('tokenActionHud.dnd5e.9thLevelSpells') }, + 'at-will-spells': { spellMode: 'atwill', name: coreModule.api.Utils.i18n('tokenActionHud.dnd5e.atWillSpells') }, + cantrips: { spellMode: 0, name: coreModule.api.Utils.i18n('tokenActionHud.dnd5e.cantrips') }, + 'innate-spells': { spellMode: 'innate', name: coreModule.api.Utils.i18n('tokenActionHud.dnd5e.innateSpells') }, + 'pact-spells': { spellMode: 'pact', name: coreModule.api.Utils.i18n('tokenActionHud.dnd5e.pactSpells') } + } - const spellSlotModes = ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'pact'] + const spellSlotModes = ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'pact'] - for (const subcategoryId of this.spellSubcategoryIds) { - const spellMode = subcategoryMappings[subcategoryId].spellMode - const subcategoryName = subcategoryMappings[subcategoryId].name + for (const subcategoryId of this.spellSubcategoryIds) { + const spellMode = subcategoryMappings[subcategoryId].spellMode + const subcategoryName = subcategoryMappings[subcategoryId].name - // Skip if no spells exist - if (!spellsMap.has(subcategoryId)) continue + // Skip if no spells exist + if (!spellsMap.has(subcategoryId)) continue - const levelInfo = (spellMode === 'pact') ? pactSlot[1] : spellSlots.find(spellSlot => spellSlot[0] === `spell${spellMode}`)?.[1] - const slots = levelInfo?.value - const max = levelInfo?.max - const slotsAvailable = levelInfo?.slotAvailable + const levelInfo = (spellMode === 'pact') ? pactSlot[1] : spellSlots.find(spellSlot => spellSlot[0] === `spell${spellMode}`)?.[1] + const slots = levelInfo?.value + const max = levelInfo?.max + const slotsAvailable = levelInfo?.slotAvailable - // Skip if spells require spell slots and none are available - if (!slotsAvailable && spellSlotModes.includes(spellMode)) continue + // Skip if spells require spell slots and none are available + if (!slotsAvailable && spellSlotModes.includes(spellMode)) continue - // Create subcategory data= - const subcategoryInfo = {} - subcategoryInfo.info1 = { class: 'tah-spotlight', text: (max >= 0) ? `${slots}/${max}` : '' } - const subcategoryData = { - id: subcategoryId, - name: subcategoryName, - type: 'system', - info: subcategoryInfo - } + // Create subcategory data= + const subcategoryInfo = {} + subcategoryInfo.info1 = { class: 'tah-spotlight', text: (max >= 0) ? `${slots}/${max}` : '' } + const subcategoryData = { + id: subcategoryId, + name: subcategoryName, + type: 'system', + info: subcategoryInfo + } - // Add spell slot info to subcategory - this.addSubcategoryInfo(subcategoryData) + // Add spell slot info to subcategory + this.addSubcategoryInfo(subcategoryData) - const spells = spellsMap.get(subcategoryId) + const spells = spellsMap.get(subcategoryId) - // Build actions - this._buildActions(spells, subcategoryData, actionType) + // Build actions + this._buildActions(spells, subcategoryData, actionType) - // Build activations - if (this.activationSubcategoryIds) { this.buildActivations(spells, subcategoryData, actionType) } + // Build activations + if (this.activationSubcategoryIds) { this.buildActivations(spells, subcategoryData, actionType) } + } } - } - - /** - * Build Utility - * @private - */ - _buildUtility () { - // Exit if no subcategory exists - if (!this.subcategoryIds.includes('utility')) return + /** + * Build Utility + * @private + */ + _buildUtility () { // Exit if every actor is not the character type - if (!this.actors.every((actor) => actor.type === 'character')) return + if (!this.actors.every((actor) => actor.type === 'character')) return - const actionType = 'utility' + const actionType = 'utility' - // Set utility types - const utilityTypes = { - deathSave: { name: CoreUtils.i18n('DND5E.DeathSave') }, - inspiration: { name: CoreUtils.i18n('DND5E.Inspiration') } - } - - // Delete 'deathSave' for multiple tokens - if (this.actorId === 'multi' || this.actor.system.attributes.hp.value > 0) delete utilityTypes.deathSave + // Set utility types + const utilityTypes = { + deathSave: { name: coreModule.api.Utils.i18n('DND5E.DeathSave') }, + inspiration: { name: coreModule.api.Utils.i18n('DND5E.Inspiration') } + } - // Get actions - const actions = Object.entries(utilityTypes) - .map((utilityType) => { - const id = utilityType[0] - const name = utilityType[1].name - const encodedValue = [actionType, this.actorId, this.tokenId, id].join(this.delimiter) - let cssClass = '' - if (utilityType[0] === 'inspiration') { - const active = this.actors.every((actor) => actor.system.attributes?.inspiration) - ? ' active' - : '' - cssClass = `toggle${active}` - } - return { - id, - name, - encodedValue, - cssClass, - selected: true - } - }) + // Delete 'deathSave' for multiple tokens + if (!this.actor || this.actor.system.attributes.hp.value > 0) delete utilityTypes.deathSave + + // Get actions + const actions = Object.entries(utilityTypes) + .map((utilityType) => { + const id = utilityType[0] + const name = utilityType[1].name + const actionTypeName = `${coreModule.api.Utils.i18n(ACTION_TYPE[actionType])}: ` ?? '' + const listName = `${actionTypeName}${name}` + const encodedValue = [actionType, id].join(this.delimiter) + let cssClass = '' + if (utilityType[0] === 'inspiration') { + const active = this.actors.every((actor) => actor.system.attributes?.inspiration) + ? ' active' + : '' + cssClass = `toggle${active}` + } + return { + id, + name, + encodedValue, + cssClass, + listName + } + }) - // Crreate subcategory data - const subcategoryData = { id: 'utility', type: 'system' } + // Crreate subcategory data + const subcategoryData = { id: 'utility', type: 'system' } - // Add actions to action list - this.addActionsToActionList(actions, subcategoryData) - } + // Add actions to action list + this.addActionsToActionList(actions, subcategoryData) + } - /** - * Get Actions - * @private - * @param {object} items - * @param {object} subcategoryData - * @param {string} actionType - */ - _buildActions (items, subcategoryData, actionType = 'item') { + /** + * Get Actions + * @private + * @param {object} items + * @param {object} subcategoryData + * @param {string} actionType + */ + _buildActions (items, subcategoryData, actionType = 'item') { // Exit if there are no items - if (items.size === 0) return + if (items.size === 0) return - // Exit if there is no subcategoryId - const subcategoryId = (typeof subcategoryData === 'string' ? subcategoryData : subcategoryData?.id) - if (!subcategoryId) return + // Exit if there is no subcategoryId + const subcategoryId = (typeof subcategoryData === 'string' ? subcategoryData : subcategoryData?.id) + if (!subcategoryId) return - // Get actions - const actions = [...items].map(item => this._getAction(actionType, item[1])) + // Get actions + const actions = [...items].map(item => this._getAction(actionType, item[1])) - // Add actions to action list - this.addActionsToActionList(actions, subcategoryData) - } + // Add actions to action list + this.addActionsToActionList(actions, subcategoryData) + } - /** - * Get Action - * @private - * @param {string} actionType - * @param {object} entity - * @returns {object} - */ - _getAction (actionType, entity) { - const id = entity.id ?? entity._id - let name = entity?.name ?? entity?.label - if ( - entity?.system?.recharge && + /** + * Get Action + * @private + * @param {string} actionType + * @param {object} entity + * @returns {object} + */ + _getAction (actionType, entity) { + const id = entity.id ?? entity._id + let name = entity?.name ?? entity?.label + if ( + entity?.system?.recharge && !entity?.system?.recharge?.charged && entity?.system?.recharge?.value - ) { - name += ` (${CoreUtils.i18n('DND5E.Recharge')})` + ) { + name += ` (${coreModule.api.Utils.i18n('DND5E.Recharge')})` + } + const actionTypeName = `${coreModule.api.Utils.i18n(ACTION_TYPE[actionType])}: ` ?? '' + const listName = `${actionTypeName}${name}` + let cssClass = '' + if (Object.hasOwn(entity, 'disabled')) { + const active = (!entity.disabled) ? ' active' : '' + cssClass = `toggle${active}` + } + const encodedValue = [actionType, id].join(this.delimiter) + const img = coreModule.api.Utils.getImage(entity) + const icon1 = this._getActivationTypeIcon(entity?.system?.activation?.type) + let icon2 = null + let info = null + if (entity.type === 'spell') { + icon2 = this._getPreparedIcon(entity) + if (this.displaySpellInfo) info = this._getSpellInfo(entity) + } else { + info = this._getItemInfo(entity) + } + const info1 = info?.info1 + const info2 = info?.info2 + const info3 = info?.info3 + return { + id, + name, + encodedValue, + cssClass, + img, + icon1, + icon2, + info1, + info2, + info3, + listName + } } - let cssClass = '' - if (Object.hasOwn(entity, 'disabled')) { - const active = (!entity.disabled) ? ' active' : '' - cssClass = `toggle${active}` + + /** + * Is Active Item + * @param {object} item + * @returns {boolean} + */ + _isActiveItem (item) { + if (this.showItemsWithoutActivationCosts) return true + const activationTypes = Object.keys(game.dnd5e.config.abilityActivationTypes).filter((at) => at !== 'none') + const activation = item.system.activation + const activationType = activation?.type + if (activation && activationTypes.includes(activationType)) return true + return false } - const encodedValue = [actionType, this.actorId, this.tokenId, id].join(this.delimiter) - const img = CoreUtils.getImage(entity) - const icon1 = this._getActivationTypeIcon(entity?.system?.activation?.type) - let icon2 = null - let info = null - if (entity.type === 'spell') { - icon2 = this._getPreparedIcon(entity) - if (this.displaySpellInfo) info = this._getSpellInfo(entity) - } else { - info = this._getItemInfo(entity) + + /** + * Is Equipped Item + * @private + * @param {object} item + * @returns {boolean} + */ + _isEquippedItem (item) { + const type = item.type + const excludedTypes = ['consumable', 'spell', 'feat'] + if (this.showUnequippedItems && !excludedTypes.includes(type)) return true + const equipped = item.system.equipped + if (equipped && type !== 'consumable') return true + return false } - const info1 = info?.info1 - const info2 = info?.info2 - const info3 = info?.info3 - return { - id, - name, - encodedValue, - cssClass, - img, - icon1, - icon2, - info1, - info2, - info3, - selected: true + + /** + * Is Usable Item + * @private + * @param {object} item The item + * @returns {boolean} + */ + _isUsableItem (item) { + if (this.showUnchargedItems) return true + const uses = item.system.uses + if (!uses) return false + return true } - } - /** - * Is Active Item - * @param {object} item - * @returns {boolean} - */ - _isActiveItem (item) { - if (this.showItemsWithoutActivationCosts) return true - const activationTypes = Object.keys(game.dnd5e.config.abilityActivationTypes).filter((at) => at !== 'none') - const activation = item.system.activation - const activationType = activation?.type - if (activation && activationTypes.includes(activationType)) return true - return false - } + /** + * Is Usable Spell + * @param {object} spell The spell + * @returns {boolean} + */ + _isUsableSpell (spell) { + if (this.actorType !== 'character' && this.showUnequippedItems) return true + const prepared = spell.system.preparation.prepared + if (this.showUnpreparedSpells) return true + // Set variables + const level = spell.system.level + const preparationModes = Object.keys(game.dnd5e.config.spellPreparationModes) + .filter(preparationMode => preparationMode !== 'prepared') + const preparationMode = spell.system.preparation.mode + + // Return true if spell is a cantrip, has a preparation mode other than 'prepared' or is prepared + if (level === 0 || preparationModes.includes(preparationMode) || prepared) return true + return false + } - /** - * Is Equipped Item - * @private - * @param {object} item - * @returns {boolean} - */ - _isEquippedItem (item) { - const type = item.type - const excludedTypes = ['consumable', 'spell', 'feat'] - if (this.showUnequippedItems && !excludedTypes.includes(type)) return true - const equipped = item.system.equipped - if (equipped && type !== 'consumable') return true - return false - } + /** + * Get Item Info + * @private + * @param {object} item + * @returns {object} + */ + _getItemInfo (item) { + const quantityData = this._getQuantityData(item) + const usesData = this._getUsesData(item) + const consumeData = this._getConsumeData(item) - /** - * Is Usable Item - * @private - * @param {object} item The item - * @returns {boolean} - */ - _isUsableItem (item) { - if (this.showUnchargedItems) return true - const uses = item.system.uses - if (!uses) return false - return true - } + return { + info1: { text: quantityData }, + info2: { text: usesData }, + info3: { text: consumeData } + } + } - /** - * Is Usable Spell - * @param {object} spell The spell - * @returns {boolean} - */ - _isUsableSpell (spell) { - if (this.actorType !== 'character' && this.showUnequippedItems) return true - const prepared = spell.system.preparation.prepared - if (this.showUnpreparedSpells) return true - // Set variables - const level = spell.system.level - const preparationModes = Object.keys(game.dnd5e.config.spellPreparationModes) - .filter(preparationMode => preparationMode !== 'prepared') - const preparationMode = spell.system.preparation.mode - - // Return true if spell is a cantrip, has a preparation mode other than 'prepared' or is prepared - if (level === 0 || preparationModes.includes(preparationMode) || prepared) return true - return false - } + /** + * Add Spell Info + * @param {object} spell + */ + _getSpellInfo (spell) { + const components = spell.system.components - /** - * Get Item Info - * @private - * @param {object} item - * @returns {object} - */ - _getItemInfo (item) { - const quantityData = this._getQuantityData(item) - const usesData = this._getUsesData(item) - const consumeData = this._getConsumeData(item) - - return { - info1: { text: quantityData }, - info2: { text: usesData }, - info3: { text: consumeData } - } - } + const componentsArray = [] + const info1 = {} + const info2 = {} + const info3 = {} - /** - * Add Spell Info - * @param {object} spell - */ - _getSpellInfo (spell) { - const components = spell.system.components - - const componentsArray = [] - const info1 = {} - const info2 = {} - const info3 = {} - - // Components - if (components?.vocal) componentsArray.push(CoreUtils.i18n('DND5E.ComponentVerbal')) - if (components?.somatic) componentsArray.push(CoreUtils.i18n('DND5E.ComponentSomatic')) - if (components?.material) componentsArray.push(CoreUtils.i18n('DND5E.ComponentMaterial')) - - if (componentsArray.length) { - info1.title = componentsArray.join(', ') - info1.text = componentsArray.map(component => component.charAt(0).toUpperCase()).join('') - } + // Components + if (components?.vocal) componentsArray.push(coreModule.api.Utils.i18n('DND5E.ComponentVerbal')) + if (components?.somatic) componentsArray.push(coreModule.api.Utils.i18n('DND5E.ComponentSomatic')) + if (components?.material) componentsArray.push(coreModule.api.Utils.i18n('DND5E.ComponentMaterial')) - // Concentration - if (components?.concentration) { - const title = CoreUtils.i18n('DND5E.Concentration') - info2.title = title - info2.text = title.charAt(0).toUpperCase() - } + if (componentsArray.length) { + info1.title = componentsArray.join(', ') + info1.text = componentsArray.map(component => component.charAt(0).toUpperCase()).join('') + } + + // Concentration + if (components?.concentration) { + const title = coreModule.api.Utils.i18n('DND5E.Concentration') + info2.title = title + info2.text = title.charAt(0).toUpperCase() + } - // Ritual - if (components?.ritual) { - const title = CoreUtils.i18n('DND5E.Ritual') - info3.title = title - info3.text = title.charAt(0).toUpperCase() + // Ritual + if (components?.ritual) { + const title = coreModule.api.Utils.i18n('DND5E.Ritual') + info3.title = title + info3.text = title.charAt(0).toUpperCase() + } + + return { info1, info2, info3 } } - return { info1, info2, info3 } - } + /** + * Get Actors + * @private + * @returns {object} + */ + _getActors () { + const allowedTypes = ['character', 'npc'] + const actors = canvas.tokens.controlled.filter(token => token.actor).map((token) => token.actor) + if (actors.every((actor) => allowedTypes.includes(actor.type))) { return actors } + } - /** - * Get Actors - * @private - * @returns {object} - */ - _getActors () { - const allowedTypes = ['character', 'npc'] - const actors = canvas.tokens.controlled.map((token) => token.actor) - if (actors.every((actor) => allowedTypes.includes(actor.type))) { return actors } - } + /** + * Get Actors + * @private + * @returns {object} + */ + _getTokens () { + const allowedTypes = ['character', 'npc'] + const tokens = canvas.tokens.controlled + const actors = tokens.filter(token => token.actor).map((token) => token.actor) + if (actors.every((actor) => allowedTypes.includes(actor.type))) { return tokens } + } - /** - * Get Quantity - * @private - * @param {object} item - * @returns {string} - */ - _getQuantityData (item) { - const quantity = item?.system?.quantity ?? 0 - return (quantity > 1) ? quantity : '' - } + /** + * Get Quantity + * @private + * @param {object} item + * @returns {string} + */ + _getQuantityData (item) { + const quantity = item?.system?.quantity ?? 0 + return (quantity > 1) ? quantity : '' + } - /** - * Get Uses - * @private - * @param {object} item - * @returns {string} - */ - _getUsesData (item) { - const uses = item?.system?.uses - if (!uses) return '' - return (uses.value > 0 || uses.max > 0) ? `${uses.value ?? '0'}${(uses.max > 0) ? `/${uses.max}` : ''}` : '' - } + /** + * Get Uses + * @private + * @param {object} item + * @returns {string} + */ + _getUsesData (item) { + const uses = item?.system?.uses + if (!uses) return '' + return (uses.value > 0 || uses.max > 0) ? `${uses.value ?? '0'}${(uses.max > 0) ? `/${uses.max}` : ''}` : '' + } - /** - * Get Consume - * @private - * @param {object} item - * @param {object} actor - * @returns {string} - */ - _getConsumeData (item) { + /** + * Get Consume + * @private + * @param {object} item + * @param {object} actor + * @returns {string} + */ + _getConsumeData (item) { // Get consume target and type - const consumeId = item?.system?.consume?.target - const consumeType = item?.system?.consume?.type + const consumeId = item?.system?.consume?.target + const consumeType = item?.system?.consume?.type - // Return resources - if (consumeType === 'attribute') { - const parentId = consumeId.substr(0, consumeId.lastIndexOf('.')) - const target = this.actor.system[parentId] + // Return resources + if (consumeType === 'attribute') { + const parentId = consumeId.substr(0, consumeId.lastIndexOf('.')) + const target = this.actor.system[parentId] - return (target) ? `${target.value ?? '0'}${(target.max) ? `/${target.max}` : ''}` : '' - } + return (target) ? `${target.value ?? '0'}${(target.max) ? `/${target.max}` : ''}` : '' + } - const target = this.items.get(consumeId) + const target = this.items.get(consumeId) - // Return charges - if (consumeType === 'charges') { - const uses = target?.system.uses + // Return charges + if (consumeType === 'charges') { + const uses = target?.system.uses - return (uses?.value) ? `${uses.value}${(uses.max) ? `/${uses.max}` : ''}` : '' - } + return (uses?.value) ? `${uses.value}${(uses.max) ? `/${uses.max}` : ''}` : '' + } - // Return quantity - return target?.system?.quantity ?? '' - } + // Return quantity + return target?.system?.quantity ?? '' + } - /** - * Discard Slow Items - * @private - * @param {object} items - * @returns {object} - */ - _discardSlowItems (items) { + /** + * Discard Slow Items + * @private + * @param {object} items + * @returns {object} + */ + _discardSlowItems (items) { // Get setting - const showSlowActions = Utils.getSetting('showSlowActions') + const showSlowActions = Utils.getSetting('showSlowActions') - // Return all items - if (showSlowActions) return items + // Return all items + if (showSlowActions) return items - // Return filtered items - const slowActivationTypes = ['minute', 'hour', 'day'] + // Return filtered items + const slowActivationTypes = ['minute', 'hour', 'day'] - // Initialize map - const filteredItems = new Map() + // Initialize map + const filteredItems = new Map() - // Loop items and set those with fast activation types into the new map - for (const [key, value] of items.entries()) { - const activation = value.system.activation - const activationType = value.system.activation?.type - if (activation && !slowActivationTypes.includes(activationType)) filteredItems.set(key, value) - } + // Loop items and set those with fast activation types into the new map + for (const [key, value] of items.entries()) { + const activation = value.system.activation + const activationType = value.system.activation?.type + if (activation && !slowActivationTypes.includes(activationType)) filteredItems.set(key, value) + } - return filteredItems - } + return filteredItems + } - /** - * Get Proficiency Icon - * @param {string} level - * @returns {string} - */ - _getProficiencyIcon (level) { - const title = CONFIG.DND5E.proficiencyLevels[level] ?? '' - const icon = PROFICIENCY_LEVEL_ICON[level] - if (icon) return `` - } + /** + * Get Proficiency Icon + * @param {string} level + * @returns {string} + */ + _getProficiencyIcon (level) { + const title = CONFIG.DND5E.proficiencyLevels[level] ?? '' + const icon = PROFICIENCY_LEVEL_ICON[level] + if (icon) return `` + } - /** - * Get icon for the activation type - * @param {object} activationType - * @returns {string} - */ - _getActivationTypeIcon (activationType) { - const title = CONFIG.DND5E.abilityActivationTypes[activationType] ?? '' - const icon = ACTIVATION_TYPE_ICON[activationType] - if (icon) return `` - } + /** + * Get icon for the activation type + * @param {object} activationType + * @returns {string} + */ + _getActivationTypeIcon (activationType) { + const title = CONFIG.DND5E.abilityActivationTypes[activationType] ?? '' + const icon = ACTIVATION_TYPE_ICON[activationType] + if (icon) return `` + } - /** - * Get icon for a prepared spell - * @param {boolean} prepararation - * @returns - */ - _getPreparedIcon (spell) { - const level = spell.system.level - const preparationMode = spell.system.preparation.mode - const prepared = spell.system.preparation.prepared - const icon = (prepared) ? PREPARED_ICON : `${PREPARED_ICON} tah-icon-disabled` - const title = (prepared) ? CoreUtils.i18n('DND5E.SpellPrepared') : CoreUtils.i18n('DND5E.SpellUnprepared') - - // Return icon if the preparation mode is 'prepared' and the spell is not a cantrip - return (preparationMode === 'prepared' && level !== 0) ? `` : '' + /** + * Get icon for a prepared spell + * @param {boolean} prepararation + * @returns + */ + _getPreparedIcon (spell) { + const level = spell.system.level + const preparationMode = spell.system.preparation.mode + const prepared = spell.system.preparation.prepared + const icon = (prepared) ? PREPARED_ICON : `${PREPARED_ICON} tah-icon-disabled` + const title = (prepared) ? coreModule.api.Utils.i18n('DND5E.SpellPrepared') : coreModule.api.Utils.i18n('DND5E.SpellUnprepared') + + // Return icon if the preparation mode is 'prepared' and the spell is not a cantrip + return (preparationMode === 'prepared' && level !== 0) ? `` : '' + } } -} +}) diff --git a/scripts/config.js b/scripts/config.js deleted file mode 100644 index 1a47606..0000000 --- a/scripts/config.js +++ /dev/null @@ -1,31 +0,0 @@ -// For distribution -const coreModulePath = '../../token-action-hud-core/scripts/token-action-hud-core.min.js' -const coreModule = await import(coreModulePath) -export const CoreActionHandler = coreModule.ActionHandler -export const CoreActionListExtender = coreModule.ActionListExtender -export const CoreCategoryManager = coreModule.CategoryManager -export const CorePreRollHandler = coreModule.PreRollHandler -export const CoreRollHandler = coreModule.RollHandler -export const CoreSystemManager = coreModule.SystemManager -export const CoreUtils = coreModule.Utils -export const Logger = coreModule.Logger - -// For development -/* const coreModulePath = '../../token-action-hud-core/' -const coreActionHandlerFile = `${coreModulePath}scripts/action-handlers/action-handler.js` -const coreActionListExtenderFile = `${coreModulePath}scripts/action-handlers/action-list-extender.js` -const coreCategoryManagerFile = `${coreModulePath}scripts/category-manager.js` -const corePreRollHandlerFile = `${coreModulePath}scripts/roll-handlers/pre-roll-handler.js` -const coreRollHandlerFile = `${coreModulePath}scripts/roll-handlers/roll-handler.js` -const coreSystemManagerFile = `${coreModulePath}scripts/system-manager.js` -const coreUtilsFile = `${coreModulePath}scripts/utilities/utils.js` - -export const CoreActionHandler = await import(coreActionHandlerFile).then(module => module.ActionHandler) -export const CoreActionListExtender = await import(coreActionListExtenderFile).then(module => module.ActionListExtender) -export const CoreCategoryManager = await import(coreCategoryManagerFile).then(module => module.CategoryManager) -export const CorePreRollHandler = await import(corePreRollHandlerFile).then(module => module.PreRollHandler) -export const CoreRollHandler = await import(coreRollHandlerFile).then(module => module.RollHandler) -export const CoreSystemManager = await import(coreSystemManagerFile).then(module => module.SystemManager) -const coreUtilsModule = await import(coreUtilsFile) -export const CoreUtils = coreUtilsModule.Utils -export const Logger = coreUtilsModule.Logger */ diff --git a/scripts/constants.js b/scripts/constants.js index 6e58e6e..d5f849f 100644 --- a/scripts/constants.js +++ b/scripts/constants.js @@ -15,7 +15,23 @@ export const CORE_MODULE = { /** * Core module version required by the system module */ -export const REQUIRED_CORE_MODULE_VERSION = '1.2' +export const REQUIRED_CORE_MODULE_VERSION = '1.3' + +/** + * Action type + */ +export const ACTION_TYPE = { + ability: 'DND5E.Ability', + check: 'tokenActionHud.dnd5e.check', + condition: 'tokenActionHud.dnd5e.condition', + effect: 'DND5E.Effect', + feature: 'ITEM.TypeFeat', + item: 'tokenActionHud.dnd5e.item', + save: 'DND5E.ActionSave', + skill: 'tokenActionHud.dnd5e.skill', + spell: 'ITEM.TypeSpell', + utility: 'DND5E.ActionUtil' +} /** * Activation type icons @@ -55,3 +71,67 @@ export const PROFICIENCY_LEVEL_ICON = { 1: 'fas fa-check', 2: 'fas fa-check-double' } + +export const SUBCATEGORY = { + _1stLevelSpells: { id: '1st-level-spells', name: 'tokenActionHud.dnd5e.1stLevelSpells', type: 'system', hasDerivedSubcategories: false }, + _2ndLevelSpells: { id: '2nd-level-spells', name: 'tokenActionHud.dnd5e.2ndLevelSpells', type: 'system', hasDerivedSubcategories: false }, + _3rdLevelSpells: { id: '3rd-level-spells', name: 'tokenActionHud.dnd5e.3rdLevelSpells', type: 'system', hasDerivedSubcategories: false }, + _4thLevelSpells: { id: '4th-level-spells', name: 'tokenActionHud.dnd5e.4thLevelSpells', type: 'system', hasDerivedSubcategories: false }, + _5thLevelSpells: { id: '5th-level-spells', name: 'tokenActionHud.dnd5e.5thLevelSpells', type: 'system', hasDerivedSubcategories: false }, + _6thLevelSpells: { id: '6th-level-spells', name: 'tokenActionHud.dnd5e.6thLevelSpells', type: 'system', hasDerivedSubcategories: false }, + _7thLevelSpells: { id: '7th-level-spells', name: 'tokenActionHud.dnd5e.7thLevelSpells', type: 'system', hasDerivedSubcategories: false }, + _8thLevelSpells: { id: '8th-level-spells', name: 'tokenActionHud.dnd5e.8thLevelSpells', type: 'system', hasDerivedSubcategories: false }, + _9thLevelSpells: { id: '9th-level-spells', name: 'tokenActionHud.dnd5e.9thLevelSpells', type: 'system', hasDerivedSubcategories: false }, + abilities: { id: 'abilities', name: 'tokenActionHud.dnd5e.abilities', type: 'system', hasDerivedSubcategories: false }, + actions: { id: 'actions', name: 'DND5E.ActionPl', type: 'system', hasDerivedSubcategories: true }, + activeFeatures: { id: 'active-features', name: 'tokenActionHud.dnd5e.activeFeatures', type: 'system', hasDerivedSubcategories: false }, + artificerInfusions: { id: 'artificer-infusions', name: 'tokenActionHud.dnd5e.artificerInfusions', type: 'system', hasDerivedSubcategories: false }, + atWillSpells: { id: 'at-will-spells', name: 'tokenActionHud.dnd5e.atWillSpells', type: 'system', hasDerivedSubcategories: false }, + backgroundFeatures: { id: 'background-features', name: 'tokenActionHud.dnd5e.backgroundFeatures', type: 'system', hasDerivedSubcategories: false }, + bonusActions: { id: 'bonus-actions', name: 'tokenActionHud.dnd5e.bonusActions', type: 'system', hasDerivedSubcategories: true }, + cantrips: { id: 'cantrips', name: 'tokenActionHud.dnd5e.cantrips', type: 'system', hasDerivedSubcategories: false }, + channelDivinity: { id: 'channel-divinity', name: 'tokenActionHud.dnd5e.channelDivinity', type: 'system', hasDerivedSubcategories: false }, + checks: { id: 'checks', name: 'tokenActionHud.dnd5e.checks', type: 'system', hasDerivedSubcategories: false }, + classFeatures: { id: 'class-features', name: 'tokenActionHud.dnd5e.classFeatures', type: 'system', hasDerivedSubcategories: false }, + combat: { id: 'combat', name: 'tokenActionHud.combat', type: 'system', hasDerivedSubcategories: false }, + conditions: { id: 'conditions', name: 'tokenActionHud.dnd5e.conditions', type: 'system', hasDerivedSubcategories: false }, + consumables: { id: 'consumables', name: 'ITEM.TypeConsumablePl', type: 'system', hasDerivedSubcategories: false }, + containers: { id: 'containers', name: 'ITEM.TypeContainerPl', type: 'system', hasDerivedSubcategories: false }, + crewActions: { id: 'crew-actions', name: 'tokenActionHud.dnd5e.crewActions', type: 'system', hasDerivedSubcategories: true }, + defensiveTactics: { id: 'defensive-tactics', name: 'tokenActionHud.dnd5e.defensiveTactics', type: 'system', hasDerivedSubcategories: false }, + eldritchInvocations: { id: 'eldritch-invocations', name: 'tokenActionHud.dnd5e.eldritchInvocations', type: 'system', hasDerivedSubcategories: false }, + elementalDisciplines: { id: 'elemental-disciplines', name: 'tokenActionHud.dnd5e.elementalDisciplines', type: 'system', hasDerivedSubcategories: false }, + equipment: { id: 'equipment', name: 'ITEM.TypeEquipmentPl', type: 'system', hasDerivedSubcategories: false }, + equipped: { id: 'equipped', name: 'DND5E.Equipped', type: 'system', hasDerivedSubcategories: false }, + feats: { id: 'feats', name: 'tokenActionHud.dnd5e.feats', type: 'system', hasDerivedSubcategories: false }, + fightingStyles: { id: 'fighting-styles', name: 'tokenActionHud.dnd5e.fightingStyles', type: 'system', hasDerivedSubcategories: false }, + huntersPrey: { id: 'hunters-prey', name: 'tokenActionHud.dnd5e.huntersPrey', type: 'system', hasDerivedSubcategories: false }, + innateSpells: { id: 'innate-spells', name: 'tokenActionHud.dnd5e.innateSpells', type: 'system', hasDerivedSubcategories: false }, + kiAbilities: { id: 'ki-abilities', name: 'tokenActionHud.dnd5e.kiAbilities', type: 'system', hasDerivedSubcategories: false }, + lairActions: { id: 'lair-actions', name: 'tokenActionHud.dnd5e.lairActions', type: 'system', hasDerivedSubcategories: true }, + legendaryActions: { id: 'legendary-actions', name: 'tokenActionHud.dnd5e.legendaryActions', type: 'system', hasDerivedSubcategories: true }, + loot: { id: 'loot', name: 'ITEM.TypeLootPl', type: 'system', hasDerivedSubcategories: false }, + maneuvers: { id: 'maneuvers', name: 'tokenActionHud.dnd5e.maneuvers', type: 'system', hasDerivedSubcategories: false }, + metamagicOptions: { id: 'metamagic-options', name: 'tokenActionHud.dnd5e.metamagicOptions', type: 'system', hasDerivedSubcategories: false }, + monsterFeatures: { id: 'monster-features', name: 'tokenActionHud.dnd5e.monsterFeatures', type: 'system', hasDerivedSubcategories: false }, + multiattacks: { id: 'multiattacks', name: 'tokenActionHud.dnd5e.multiattacks', type: 'system', hasDerivedSubcategories: false }, + otherActions: { id: 'other-actions', name: 'tokenActionHud.dnd5e.otherActions', type: 'system', hasDerivedSubcategories: true }, + pactBoons: { id: 'pact-boons', name: 'tokenActionHud.dnd5e.pactBoons', type: 'system', hasDerivedSubcategories: false }, + pactSpells: { id: 'pact-spells', name: 'tokenActionHud.dnd5e.pactSpells', type: 'system', hasDerivedSubcategories: false }, + passiveEffects: { id: 'passive-effects', name: 'DND5E.EffectPassive', type: 'system', hasDerivedSubcategories: false }, + passiveFeatures: { id: 'passive-features', name: 'tokenActionHud.dnd5e.passiveFeatures', type: 'system', hasDerivedSubcategories: false }, + psionicPowers: { id: 'psionic-powers', name: 'tokenActionHud.dnd5e.psionicPowers', type: 'system', hasDerivedSubcategories: false }, + raceFeatures: { id: 'race-features', name: 'tokenActionHud.dnd5e.raceFeatures', type: 'system', hasDerivedSubcategories: false }, + reactions: { id: 'reactions', name: 'DND5E.ReactionPl', type: 'system', hasDerivedSubcategories: true }, + rests: { id: 'rests', name: 'tokenActionHud.dnd5e.rests', type: 'system', hasDerivedSubcategories: false }, + runes: { id: 'runes', name: 'tokenActionHud.dnd5e.runes', type: 'system', hasDerivedSubcategories: false }, + saves: { id: 'saves', name: 'DND5E.ClassSaves', type: 'system', hasDerivedSubcategories: false }, + skills: { id: 'skills', name: 'tokenActionHud.dnd5e.skills', type: 'system', hasDerivedSubcategories: false }, + superiorHuntersDefense: { id: 'superior-hunters-defense', name: 'tokenActionHud.dnd5e.superiorHuntersDefense', type: 'system', hasDerivedSubcategories: false }, + temporaryEffects: { id: 'temporary-effects', name: 'DND5E.EffectTemporary', type: 'system', hasDerivedSubcategories: false }, + token: { id: 'token', name: 'tokenActionHud.token', type: 'system', hasDerivedSubcategories: false }, + tools: { id: 'tools', name: 'ITEM.TypeToolPl', type: 'system', hasDerivedSubcategories: false }, + unequipped: { id: 'unequipped', name: 'DND5E.Unequipped', type: 'system', hasDerivedSubcategories: false }, + utility: { id: 'utility', name: 'tokenActionHud.utility', type: 'system', hasDerivedSubcategories: false }, + weapons: { id: 'weapons', name: 'ITEM.TypeWeaponPl', type: 'system', hasDerivedSubcategories: false } +} diff --git a/scripts/defaults.js b/scripts/defaults.js index 41c7271..a606d20 100644 --- a/scripts/defaults.js +++ b/scripts/defaults.js @@ -1,333 +1,99 @@ +import { SUBCATEGORY } from './constants.js' + /** * Default categories and subcategories */ export let DEFAULTS = null -Hooks.on('i18nInit', async () => { +Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => { + const subcategories = SUBCATEGORY + Object.values(subcategories).forEach(subcategory => { + subcategory.name = coreModule.api.Utils.i18n(subcategory.name) + subcategory.listName = `Subcategory: ${coreModule.api.Utils.i18n(subcategory.name)}` + }) + const subcategoriesArray = Object.values(subcategories) DEFAULTS = { categories: [ { nestId: 'inventory', id: 'inventory', - name: game.i18n.localize('DND5E.Inventory'), + name: coreModule.api.Utils.i18n('DND5E.Inventory'), subcategories: [ - { - nestId: 'inventory_weapons', - id: 'weapons', - name: game.i18n.localize('ITEM.TypeWeaponPl'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'inventory_equipment', - id: 'equipment', - name: game.i18n.localize('ITEM.TypeEquipmentPl'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'inventory_consumables', - id: 'consumables', - name: game.i18n.localize('ITEM.TypeConsumablePl'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'inventory_tools', - id: 'tools', - name: game.i18n.localize('ITEM.TypeToolPl'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'inventory_containers', - id: 'containers', - name: game.i18n.localize('ITEM.TypeContainerPl'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'inventory_loot', - id: 'loot', - name: game.i18n.localize('ITEM.TypeLootPl'), - type: 'system', - hasDerivedSubcategories: false - } + { ...subcategories.weapons, nestId: 'inventory_weapons' }, + { ...subcategories.equipment, nestId: 'inventory_equipment' }, + { ...subcategories.consumables, nestId: 'inventory_consumables' }, + { ...subcategories.tools, nestId: 'inventory_tools' }, + { ...subcategories.containers, nestId: 'inventory_containers' }, + { ...subcategories.loot, nestId: 'inventory_loot' } ] }, { nestId: 'features', id: 'features', - name: game.i18n.localize('DND5E.Features'), + name: coreModule.api.Utils.i18n('DND5E.Features'), subcategories: [ - { - nestId: 'features_active-features', - id: 'active-features', - name: game.i18n.localize('tokenActionHud.dnd5e.activeFeatures'), - type: 'system', - hasDerivedSubcategories: false - }, - { - id: 'passive-features', - nestId: 'features_passive-features', - name: game.i18n.localize('tokenActionHud.dnd5e.passiveFeatures'), - type: 'system', - hasDerivedSubcategories: false - } + { ...subcategories.activeFeatures, nestId: 'features_active-features' }, + { ...subcategories.passiveFeatures, nestId: 'features_passive-features' } ] }, { nestId: 'spells', id: 'spells', - name: game.i18n.localize('ITEM.TypeSpellPl'), + name: coreModule.api.Utils.i18n('ITEM.TypeSpellPl'), subcategories: [ - { - nestId: 'spells_at-will-spells', - id: 'at-will-spells', - name: game.i18n.localize('tokenActionHud.dnd5e.atWillSpells'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'spells_innate-spells', - id: 'innate-spells', - name: game.i18n.localize('tokenActionHud.dnd5e.innateSpells'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'spells_pact-spells', - id: 'pact-spells', - name: game.i18n.localize('tokenActionHud.dnd5e.pactSpells'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'spells_cantrips', - id: 'cantrips', - name: game.i18n.localize('tokenActionHud.dnd5e.cantrips'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'spells_1st-level-spells', - id: '1st-level-spells', - name: game.i18n.localize('tokenActionHud.dnd5e.1stLevelSpells'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'spells_2nd-level-spells', - id: '2nd-level-spells', - name: game.i18n.localize('tokenActionHud.dnd5e.2ndLevelSpells'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'spells_3rd-level-spells', - id: '3rd-level-spells', - name: game.i18n.localize('tokenActionHud.dnd5e.3rdLevelSpells'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'spells_4th-level-spells', - id: '4th-level-spells', - name: game.i18n.localize('tokenActionHud.dnd5e.4thLevelSpells'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'spells_5th-level-spells', - id: '5th-level-spells', - name: game.i18n.localize('tokenActionHud.dnd5e.5thLevelSpells'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'spells_6th-level-spells', - id: '6th-level-spells', - name: game.i18n.localize('tokenActionHud.dnd5e.6thLevelSpells'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'spells_7th-level-spells', - id: '7th-level-spells', - name: game.i18n.localize('tokenActionHud.dnd5e.7thLevelSpells'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'spells_8th-level-spells', - id: '8th-level-spells', - name: game.i18n.localize('tokenActionHud.dnd5e.8thLevelSpells'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'spells_9th-level-spells', - id: '9th-level-spells', - name: game.i18n.localize('tokenActionHud.dnd5e.9thLevelSpells'), - type: 'system', - hasDerivedSubcategories: false - } + { ...subcategories.atWillSpells, nestId: 'spells_at-will-spells' }, + { ...subcategories.innateSpells, nestId: 'spells_innate-spells' }, + { ...subcategories.pactSpells, nestId: 'spells_pact-spells' }, + { ...subcategories.cantrips, nestId: 'spells_cantrips' }, + { ...subcategories._1stLevelSpells, nestId: 'spells_1st-level-spells' }, + { ...subcategories._2ndLevelSpells, nestId: 'spells_2nd-level-spells' }, + { ...subcategories._3rdLevelSpells, nestId: 'spells_3rd-level-spells' }, + { ...subcategories._4thLevelSpells, nestId: 'spells_4th-level-spells' }, + { ...subcategories._5thLevelSpells, nestId: 'spells_5th-level-spells' }, + { ...subcategories._6thLevelSpells, nestId: 'spells_6th-level-spells' }, + { ...subcategories._7thLevelSpells, nestId: 'spells_7th-level-spells' }, + { ...subcategories._8thLevelSpells, nestId: 'spells_8th-level-spells' }, + { ...subcategories._9thLevelSpells, nestId: 'spells_9th-level-spells' } ] }, { nestId: 'attributes', id: 'attributes', - name: game.i18n.localize('DND5E.Attributes'), + name: coreModule.api.Utils.i18n('DND5E.Attributes'), subcategories: [ - { - nestId: 'attributes_abilities', - id: 'abilities', - name: game.i18n.localize('tokenActionHud.dnd5e.abilities'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'attributes_skills', - id: 'skills', - name: game.i18n.localize('tokenActionHud.dnd5e.skills'), - type: 'system', - hasDerivedSubcategories: false - } + { ...subcategories.abilities, nestId: 'attributes_abilities' }, + { ...subcategories.skills, nestId: 'attributes_skills' } ] }, { nestId: 'effects', id: 'effects', - name: game.i18n.localize('DND5E.Effects'), + name: coreModule.api.Utils.i18n('DND5E.Effects'), subcategories: [ - { - nestId: 'effects_temporary-effects', - id: 'temporary-effects', - name: game.i18n.localize('DND5E.EffectTemporary'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'effects_passive-effects', - id: 'passive-effects', - name: game.i18n.localize('DND5E.EffectPassive'), - type: 'system', - hasDerivedSubcategories: false - } + { ...subcategories.temporaryEffects, nestId: 'effects_temporary-effects' }, + { ...subcategories.passiveEffects, nestId: 'effects_passive-effects' } ] }, { nestId: 'conditions', id: 'conditions', - name: game.i18n.localize('tokenActionHud.dnd5e.conditions'), + name: coreModule.api.Utils.i18n('tokenActionHud.dnd5e.conditions'), subcategories: [ - { - nestId: 'conditions_conditions', - id: 'conditions', - name: game.i18n.localize('tokenActionHud.dnd5e.conditions'), - type: 'system', - hasDerivedSubcategories: false - } + { ...subcategories.conditions, nestId: 'conditions_conditions' } ] }, { nestId: 'utility', id: 'utility', - name: game.i18n.localize('tokenActionHud.utility'), + name: coreModule.api.Utils.i18n('tokenActionHud.utility'), subcategories: [ - { - nestId: 'utility_combat', - id: 'combat', - name: game.i18n.localize('tokenActionHud.combat'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'utility_token', - id: 'token', - name: game.i18n.localize('tokenActionHud.token'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'utility_rests', - id: 'rests', - name: game.i18n.localize('tokenActionHud.dnd5e.rests'), - type: 'system', - hasDerivedSubcategories: false - }, - { - nestId: 'utility_utility', - id: 'utility', - name: game.i18n.localize('tokenActionHud.utility'), - type: 'system', - hasDerivedSubcategories: false - } + { ...subcategories.combat, nestId: 'utility_combat' }, + { ...subcategories.token, nestId: 'utility_token' }, + { ...subcategories.rests, nestId: 'utility_rests' }, + { ...subcategories.utility, nestId: 'utility_utility' } ] } ], - subcategories: [ - { id: '1st-level-spells', name: game.i18n.localize('tokenActionHud.dnd5e.1stLevelSpells'), type: 'system', hasDerivedSubcategories: false }, - { id: '2nd-level-spells', name: game.i18n.localize('tokenActionHud.dnd5e.2ndLevelSpells'), type: 'system', hasDerivedSubcategories: false }, - { id: '3rd-level-spells', name: game.i18n.localize('tokenActionHud.dnd5e.3rdLevelSpells'), type: 'system', hasDerivedSubcategories: false }, - { id: '4th-level-spells', name: game.i18n.localize('tokenActionHud.dnd5e.4thLevelSpells'), type: 'system', hasDerivedSubcategories: false }, - { id: '5th-level-spells', name: game.i18n.localize('tokenActionHud.dnd5e.5thLevelSpells'), type: 'system', hasDerivedSubcategories: false }, - { id: '6th-level-spells', name: game.i18n.localize('tokenActionHud.dnd5e.6thLevelSpells'), type: 'system', hasDerivedSubcategories: false }, - { id: '7th-level-spells', name: game.i18n.localize('tokenActionHud.dnd5e.7thLevelSpells'), type: 'system', hasDerivedSubcategories: false }, - { id: '8th-level-spells', name: game.i18n.localize('tokenActionHud.dnd5e.8thLevelSpells'), type: 'system', hasDerivedSubcategories: false }, - { id: '9th-level-spells', name: game.i18n.localize('tokenActionHud.dnd5e.9thLevelSpells'), type: 'system', hasDerivedSubcategories: false }, - { id: 'abilities', name: game.i18n.localize('tokenActionHud.dnd5e.abilities'), type: 'system', hasDerivedSubcategories: false }, - { id: 'actions', name: game.i18n.localize('DND5E.ActionPl'), type: 'system', hasDerivedSubcategories: true }, - { id: 'active-features', name: game.i18n.localize('tokenActionHud.dnd5e.activeFeatures'), type: 'system', hasDerivedSubcategories: false }, - { id: 'artificer-infusions', name: game.i18n.localize('tokenActionHud.dnd5e.artificerInfusions'), type: 'system', hasDerivedSubcategories: false }, - { id: 'at-will-spells', name: game.i18n.localize('tokenActionHud.dnd5e.atWillSpells'), type: 'system', hasDerivedSubcategories: false }, - { id: 'background-features', name: game.i18n.localize('tokenActionHud.dnd5e.backgroundFeatures'), type: 'system', hasDerivedSubcategories: false }, - { id: 'bonus-actions', name: game.i18n.localize('tokenActionHud.dnd5e.bonusActions'), type: 'system', hasDerivedSubcategories: true }, - { id: 'cantrips', name: game.i18n.localize('tokenActionHud.dnd5e.cantrips'), type: 'system', hasDerivedSubcategories: false }, - { id: 'channel-divinity', name: game.i18n.localize('tokenActionHud.dnd5e.channelDivinity'), type: 'system', hasDerivedSubcategories: false }, - { id: 'checks', name: game.i18n.localize('tokenActionHud.dnd5e.checks'), type: 'system', hasDerivedSubcategories: false }, - { id: 'class-features', name: game.i18n.localize('tokenActionHud.dnd5e.classFeatures'), type: 'system', hasDerivedSubcategories: false }, - { id: 'combat', name: game.i18n.localize('tokenActionHud.combat'), type: 'system', hasDerivedSubcategories: false }, - { id: 'conditions', name: game.i18n.localize('tokenActionHud.dnd5e.conditions'), type: 'system', hasDerivedSubcategories: false }, - { id: 'consumables', name: game.i18n.localize('ITEM.TypeConsumablePl'), type: 'system', hasDerivedSubcategories: false }, - { id: 'containers', name: game.i18n.localize('ITEM.TypeContainerPl'), type: 'system', hasDerivedSubcategories: false }, - { id: 'crew-actions', name: game.i18n.localize('tokenActionHud.dnd5e.crewActions'), type: 'system', hasDerivedSubcategories: true }, - { id: 'defensive-tactics', name: game.i18n.localize('tokenActionHud.dnd5e.defensiveTactics'), type: 'system', hasDerivedSubcategories: false }, - { id: 'eldritch-invocations', name: game.i18n.localize('tokenActionHud.dnd5e.eldritchInvocations'), type: 'system', hasDerivedSubcategories: false }, - { id: 'elemental-disciplines', name: game.i18n.localize('tokenActionHud.dnd5e.elementalDisciplines'), type: 'system', hasDerivedSubcategories: false }, - { id: 'equipment', name: game.i18n.localize('ITEM.TypeEquipmentPl'), type: 'system', hasDerivedSubcategories: false }, - { id: 'equipped', name: game.i18n.localize('DND5E.Equipped'), type: 'system', hasDerivedSubcategories: false }, - { id: 'feats', name: game.i18n.localize('tokenActionHud.dnd5e.feats'), type: 'system', hasDerivedSubcategories: false }, - { id: 'fighting-styles', name: game.i18n.localize('tokenActionHud.dnd5e.fightingStyles'), type: 'system', hasDerivedSubcategories: false }, - { id: 'hunters-prey', name: game.i18n.localize('tokenActionHud.dnd5e.huntersPrey'), type: 'system', hasDerivedSubcategories: false }, - { id: 'innate-spells', name: game.i18n.localize('tokenActionHud.dnd5e.innateSpells'), type: 'system', hasDerivedSubcategories: false }, - { id: 'ki-abilities', name: game.i18n.localize('tokenActionHud.dnd5e.kiAbilities'), type: 'system', hasDerivedSubcategories: false }, - { id: 'lair-actions', name: game.i18n.localize('tokenActionHud.dnd5e.lairActions'), type: 'system', hasDerivedSubcategories: true }, - { id: 'legendary-actions', name: game.i18n.localize('tokenActionHud.dnd5e.legendaryActions'), type: 'system', hasDerivedSubcategories: true }, - { id: 'loot', name: game.i18n.localize('ITEM.TypeLootPl'), type: 'system', hasDerivedSubcategories: false }, - { id: 'maneuvers', name: game.i18n.localize('tokenActionHud.dnd5e.maneuvers'), type: 'system', hasDerivedSubcategories: false }, - { id: 'metamagic-options', name: game.i18n.localize('tokenActionHud.dnd5e.metamagicOptions'), type: 'system', hasDerivedSubcategories: false }, - { id: 'monster-features', name: game.i18n.localize('tokenActionHud.dnd5e.monsterFeatures'), type: 'system', hasDerivedSubcategories: false }, - { id: 'multiattacks', name: game.i18n.localize('tokenActionHud.dnd5e.multiattacks'), type: 'system', hasDerivedSubcategories: false }, - { id: 'other-actions', name: game.i18n.localize('tokenActionHud.dnd5e.otherActions'), type: 'system', hasDerivedSubcategories: true }, - { id: 'pact-boons', name: game.i18n.localize('tokenActionHud.dnd5e.pactBoons'), type: 'system', hasDerivedSubcategories: false }, - { id: 'pact-spells', name: game.i18n.localize('tokenActionHud.dnd5e.pactSpells'), type: 'system', hasDerivedSubcategories: false }, - { id: 'passive-effects', name: game.i18n.localize('DND5E.EffectPassive'), type: 'system', hasDerivedSubcategories: false }, - { id: 'passive-features', name: game.i18n.localize('tokenActionHud.dnd5e.passiveFeatures'), type: 'system', hasDerivedSubcategories: false }, - { id: 'psionic-powers', name: game.i18n.localize('tokenActionHud.dnd5e.psionicPowers'), type: 'system', hasDerivedSubcategories: false }, - { id: 'race-features', name: game.i18n.localize('tokenActionHud.dnd5e.raceFeatures'), type: 'system', hasDerivedSubcategories: false }, - { id: 'reactions', name: game.i18n.localize('DND5E.ReactionPl'), type: 'system', hasDerivedSubcategories: true }, - { id: 'rests', name: game.i18n.localize('tokenActionHud.dnd5e.rests'), type: 'system', hasDerivedSubcategories: false }, - { id: 'runes', name: game.i18n.localize('tokenActionHud.dnd5e.runes'), type: 'system', hasDerivedSubcategories: false }, - { id: 'saves', name: game.i18n.localize('DND5E.ClassSaves'), type: 'system', hasDerivedSubcategories: false }, - { id: 'skills', name: game.i18n.localize('tokenActionHud.dnd5e.skills'), type: 'system', hasDerivedSubcategories: false }, - { id: 'superior-hunters-defense', name: game.i18n.localize('tokenActionHud.dnd5e.superiorHuntersDefense'), type: 'system', hasDerivedSubcategories: false }, - { id: 'temporary-effects', name: game.i18n.localize('DND5E.EffectTemporary'), type: 'system', hasDerivedSubcategories: false }, - { id: 'token', name: game.i18n.localize('tokenActionHud.token'), type: 'system', hasDerivedSubcategories: false }, - { id: 'tools', name: game.i18n.localize('ITEM.TypeToolPl'), type: 'system', hasDerivedSubcategories: false }, - { id: 'unequipped', name: game.i18n.localize('DND5E.Unequipped'), type: 'system', hasDerivedSubcategories: false }, - { id: 'utility', name: game.i18n.localize('tokenActionHud.utility'), type: 'system', hasDerivedSubcategories: false }, - { id: 'weapons', name: game.i18n.localize('ITEM.TypeWeaponPl'), type: 'system', hasDerivedSubcategories: false } - ] + subcategories: subcategoriesArray } }) diff --git a/scripts/init.js b/scripts/init.js index d605106..623bc74 100644 --- a/scripts/init.js +++ b/scripts/init.js @@ -1,7 +1,7 @@ import { SystemManager } from './system-manager.js' import { MODULE, REQUIRED_CORE_MODULE_VERSION } from './constants.js' -Hooks.once('ready', async () => { +Hooks.on('tokenActionHudCoreApiReady', async () => { const module = game.modules.get(MODULE.ID) module.api = { requiredCoreModuleVersion: REQUIRED_CORE_MODULE_VERSION, diff --git a/scripts/magic-items-extender.js b/scripts/magic-items-extender.js index 5666ea7..bc92f51 100644 --- a/scripts/magic-items-extender.js +++ b/scripts/magic-items-extender.js @@ -1,103 +1,102 @@ -import { CoreActionListExtender, CoreUtils } from './config.js' - -export class MagicItemActionListExtender extends CoreActionListExtender { - constructor (actionHandler) { - super(actionHandler.categoryManager) - this.actionHandler = actionHandler - this.categoryManager = actionHandler.categoryManager - } +export let MagicItemActionListExtender = null + +Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => { + MagicItemActionListExtender = class MagicItemActionListExtender extends coreModule.api.ActionListExtender { + constructor (actionHandler) { + super(actionHandler.categoryManager) + this.actionHandler = actionHandler + this.categoryManager = actionHandler.categoryManager + this.actor = null + } - /** + /** * Extend the action list - * @param {object} character The actor and/or token */ - extendActionList (character) { - const actorId = this.actionHandler.actorId - const tokenId = this.actionHandler.tokenId - if (!actorId) return + extendActionList () { + this.actor = this.actionHandler.actor + if (!this.actor) return - const actor = MagicItems.actor(actorId) + const actor = MagicItems.actor(this.actor.id) - if (!actor) return + if (!actor) return - const magicItems = actor.items ?? [] + const magicItems = actor.items ?? [] - if (magicItems.length === 0) return + if (magicItems.length === 0) return - const parentSubcategoryId = 'magic-items' - const parentSubcategoryType = 'system' - const parentSubcategoryData = { - id: parentSubcategoryId, - type: parentSubcategoryType - } - - magicItems.forEach((magicItem) => { - if (magicItem.attuned && !this._isItemAttuned(magicItem)) return - if (magicItem.equipped && !this._isItemEquipped(magicItem)) return - - const subcategoryId = `magic-items_${magicItem.id}` - const subcategoryName = magicItem.name - const subcategoryType = 'system-derived' - const subcategoryInfo1 = `${magicItem.uses}/${magicItem.charges}` - const subcategoryData = { - id: subcategoryId, - name: subcategoryName, - type: subcategoryType, - info1: subcategoryInfo1 + const parentSubcategoryId = 'magic-items' + const parentSubcategoryType = 'system' + const parentSubcategoryData = { + id: parentSubcategoryId, + type: parentSubcategoryType } - // Add subcategory to action list - this.actionHandler.addSubcategoryToActionList(parentSubcategoryData, subcategoryData) - - const actions = magicItem.ownedEntries.map((entry) => { - const effect = entry.item - const id = effect.id - const name = effect.name - const encodedValue = [ - 'magicItem', - actorId, - tokenId, - `${magicItem.id}>${id}` - ].join('|') - const img = CoreUtils.getImage(effect) - const info1 = effect.consumption - const info2 = (effect.baseLevel) ? `${CoreUtils.i18n('DND5E.AbbreviationLevel')} ${effect.baseLevel}` : '' - return { - id, - name, - encodedValue, - img, - info1, - info2, - selected: true + magicItems.forEach((magicItem) => { + if (magicItem.attuned && !this._isItemAttuned(magicItem)) return + if (magicItem.equipped && !this._isItemEquipped(magicItem)) return + + const subcategoryId = `magic-items_${magicItem.id}` + const subcategoryName = magicItem.name + const subcategoryType = 'system-derived' + const subcategoryInfo1 = `${magicItem.uses}/${magicItem.charges}` + const subcategoryData = { + id: subcategoryId, + name: subcategoryName, + type: subcategoryType, + info1: subcategoryInfo1 } - }) - // Add actions to action list - this.actionHandler.addActionsToActionList(actions, subcategoryData) - }) - } + // Add subcategory to action list + this.actionHandler.addSubcategoryToActionList(parentSubcategoryData, subcategoryData) + + const actions = magicItem.ownedEntries.map((entry) => { + const effect = entry.item + const id = effect.id + const name = effect.name + const encodedValue = [ + 'magicItem', + `${magicItem.id}>${id}` + ].join('|') + const img = coreModule.api.Utils.getImage(effect) + const info1 = effect.consumption + const info2 = (effect.baseLevel) ? `${coreModule.api.Utils.i18n('DND5E.AbbreviationLevel')} ${effect.baseLevel}` : '' + return { + id, + name, + encodedValue, + img, + info1, + info2, + selected: true + } + }) + + // Add actions to action list + this.actionHandler.addActionsToActionList(actions, subcategoryData) + }) + } - /** + /** * Whether the magic item is equipped or not * @param {object} magicItem The item * @returns {boolean} */ - _isItemEquipped (magicItem) { - return magicItem.item.system.equipped - } + _isItemEquipped (magicItem) { + return magicItem.item.system.equipped + } - /** + /** * Whether the magic items is attuned or not * @param {object} magicItem The item * @returns {boolean} */ - _isItemAttuned (magicItem) { - const attunement = magicItem.item.system.attunment - const attunementRequired = CONFIG.DND5E.attunementTypes?.REQUIRED ?? 1 + _isItemAttuned (magicItem) { + const attunement = magicItem.item.system.attunment + const attunementRequired = CONFIG.DND5E.attunementTypes?.REQUIRED ?? 1 - if (attunement === attunementRequired) return false + if (attunement === attunementRequired) return false - return true + return true + } } -} +}) diff --git a/scripts/roll-handler-obsidian.js b/scripts/roll-handler-obsidian.js index b72b690..317b2cb 100644 --- a/scripts/roll-handler-obsidian.js +++ b/scripts/roll-handler-obsidian.js @@ -5,13 +5,10 @@ export class RollHandlerObsidian extends RollHandler { * Roll Ability Test * @override * @param {object} event - * @param {string} actorId - * @param {string} tokenId * @param {string} actionId */ - _rollAbilityTest (event, actorId, tokenId, actionId) { - const actor = super.getActor(actorId, tokenId) - OBSIDIAN.Items.roll(actor, { roll: 'abl', abl: actionId }) + _rollAbilityTest (event, actionId) { + OBSIDIAN.Items.roll(super.actor, { roll: 'abl', abl: actionId }) } /** @@ -22,34 +19,27 @@ export class RollHandlerObsidian extends RollHandler { * @param {string} tokenId * @param {string} actionId */ - _rollAbilitySave (event, actorId, tokenId, actionId) { - const actor = super.getActor(actorId, tokenId) - OBSIDIAN.Items.roll(actor, { roll: 'save', save: actionId }) + _rollAbilitySave (event, actionId) { + OBSIDIAN.Items.roll(super.actor, { roll: 'save', save: actionId }) } /** * Roll Skill * @override * @param {object} event - * @param {string} actorId - * @param {string} tokenId * @param {string} actionId */ - _rollSkill (event, actorId, tokenId, actionId) { - const actor = super.getActor(actorId, tokenId) - OBSIDIAN.Items.roll(actor, { roll: 'skl', skl: actionId }) + _rollSkill (event, actionId) { + OBSIDIAN.Items.roll(super.actor, { roll: 'skl', skl: actionId }) } /** * Use Item * @override * @param {object} event - * @param {string} actorId - * @param {string} tokenId * @param {string} actionId */ - _useItem (event, actorId, tokenId, actionId) { - const actor = super.getActor(actorId, tokenId) - OBSIDIAN.Items.roll(actor, { roll: 'item', id: actionId }) + _useItem (event, actionId) { + OBSIDIAN.Items.roll(super.actor, { roll: 'item', id: actionId }) } } diff --git a/scripts/roll-handler.js b/scripts/roll-handler.js index 0fcbfe8..1f294bc 100644 --- a/scripts/roll-handler.js +++ b/scripts/roll-handler.js @@ -1,322 +1,289 @@ -// Core Module Imports -import { CoreRollHandler, CoreUtils } from './config.js' +export let RollHandler = null -export class RollHandler extends CoreRollHandler { +Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => { + RollHandler = class RollHandler extends coreModule.api.RollHandler { /** * Handle Action Event * @override * @param {object} event * @param {string} encodedValue */ - async doHandleActionEvent (event, encodedValue) { - const payload = encodedValue.split('|') + async doHandleActionEvent (event, encodedValue) { + const payload = encodedValue.split('|') - if (payload.length !== 4) { - super.throwInvalidValueErr() - } + if (payload.length !== 2) { + super.throwInvalidValueErr() + } - const actionType = payload[0] - const actorId = payload[1] - const tokenId = payload[2] - const actionId = payload[3] + const actionType = payload[0] + const actionId = payload[1] - if (actorId === 'multi' && tokenId === 'multi' && actionId !== 'toggleCombat') { - for (const token of canvas.tokens.controlled) { - const tokenActorId = token.actor?.id - const tokenTokenId = token.id - await this._handleMacros( - event, - actionType, - tokenActorId, - tokenTokenId, - actionId - ) + if (!this.actor) { + for (const token of canvas.tokens.controlled) { + const actor = token.actor + await this._handleMacros(event, actionType, actor, token, actionId) + } + } else { + await this._handleMacros(event, actionType, this.actor, this.token, actionId) } - } else { - await this._handleMacros(event, actionType, actorId, tokenId, actionId) } - } - /** - * Handle Macros - * @private - * @param {object} event - * @param {string} actionType - * @param {string} actorId - * @param {string} tokenId - * @param {string} actionId - */ - async _handleMacros (event, actionType, actorId, tokenId, actionId) { - switch (actionType) { - case 'ability': - this._rollAbility(event, actorId, tokenId, actionId) - break - case 'abilityCheck': - this._rollAbilityTest(event, actorId, tokenId, actionId) - break - case 'abilitySave': - this._rollAbilitySave(event, actorId, tokenId, actionId) - break - case 'condition': - if (!tokenId) return - await this._toggleCondition(event, tokenId, actionId) - break - case 'effect': - await this._toggleEffect(event, actorId, tokenId, actionId) - break - case 'feature': - case 'item': - case 'spell': - case 'weapon': - if (this.isRenderItem()) this.doRenderItem(actorId, tokenId, actionId) - else this._useItem(event, actorId, tokenId, actionId) - break - case 'magicItem': - this._rollMagicItem(event, actorId, tokenId, actionId) - break - case 'skill': - this._rollSkill(event, actorId, tokenId, actionId) - break - case 'utility': - await this._performUtilityMacro(event, actorId, tokenId, actionId) - break - default: - break + /** + * Handle Macros + * @private + * @param {object} event + * @param {string} actionType + * @param {object} actor + * @param {object} token + * @param {string} actionId + */ + async _handleMacros (event, actionType, actor, token, actionId) { + switch (actionType) { + case 'ability': + this._rollAbility(event, actor, actionId) + break + case 'check': + this._rollAbilityTest(event, actor, actionId) + break + case 'save': + this._rollAbilitySave(event, actor, actionId) + break + case 'condition': + if (!token) return + await this._toggleCondition(event, token, actionId) + break + case 'effect': + await this._toggleEffect(event, actor, actionId) + break + case 'feature': + case 'item': + case 'spell': + case 'weapon': + if (this.isRenderItem()) this.doRenderItem(actor, actionId) + else this._useItem(event, actor, actionId) + break + case 'magicItem': + this._rollMagicItem(actor, actionId) + break + case 'skill': + this._rollSkill(event, actor, actionId) + break + case 'utility': + await this._performUtilityMacro(event, actor, token, actionId) + break + default: + break + } } - } - - /** - * Roll Ability - * @private - * @param {object} event - * @param {string} actorId - * @param {string} tokenId - * @param {string} actionId - */ - _rollAbility (event, actorId, tokenId, actionId) { - const actor = CoreUtils.getActor(actorId, tokenId) - actor.rollAbility(actionId, { event }) - } - - /** - * Roll Ability Save - * @private - * @param {object} event - * @param {string} actorId - * @param {string} tokenId - * @param {string} actionId - */ - _rollAbilitySave (event, actorId, tokenId, actionId) { - const actor = CoreUtils.getActor(actorId, tokenId) - actor.rollAbilitySave(actionId, { event }) - } - /** - * Roll Ability Test - * @private - * @param {object} event - * @param {string} actorId - * @param {string} tokenId - * @param {string} actionId - */ - _rollAbilityTest (event, actorId, tokenId, actionId) { - const actor = CoreUtils.getActor(actorId, tokenId) - actor.rollAbilityTest(actionId, { event }) - } + /** + * Roll Ability + * @private + * @param {object} event The event + * @param {object} actor The actor + * @param {string} actionId The action id + */ + _rollAbility (event, actor, actionId) { + if (!actor) return + actor.rollAbility(actionId, { event }) + } - /** - * Roll Magic Item - * @private - * @param {object} event - * @param {string} actorId - * @param {string} tokenId - * @param {string} actionId - */ - _rollMagicItem (event, actorId, tokenId, actionId) { - const actor = CoreUtils.getActor(actorId, tokenId) - const actionParts = actionId.split('>') + /** + * Roll Ability Save + * @private + * @param {object} event The event + * @param {object} actor The actor + * @param {string} actionId The action id + */ + _rollAbilitySave (event, actor, actionId) { + if (!actor) return + actor.rollAbilitySave(actionId, { event }) + } - const itemId = actionParts[0] - const magicEffectId = actionParts[1] + /** + * Roll Ability Test + * @private + * @param {object} event The event + * @param {object} actor The actor + * @param {string} actionId The action id + */ + _rollAbilityTest (event, actor, actionId) { + if (!actor) return + actor.rollAbilityTest(actionId, { event }) + } - const magicItemActor = MagicItems.actor(actor.id) + /** + * Roll Magic Item + * @private + * @param {object} actor The actor + * @param {string} actionId The action id + */ + _rollMagicItem (actor, actionId) { + const actionParts = actionId.split('>') - // magicitems module 3.0.0 does not support Item5e#use - magicItemActor.roll(itemId, magicEffectId) + const itemId = actionParts[0] + const magicEffectId = actionParts[1] - Hooks.callAll('forceUpdateTokenActionHud') - } + const magicItemActor = MagicItems.actor(actor.id) - /** - * Roll Skill - * @private - * @param {object} event - * @param {string} actorId - * @param {string} tokenId - * @param {string} actionId - */ - _rollSkill (event, actorId, tokenId, actionId) { - const actor = CoreUtils.getActor(actorId, tokenId) - actor.rollSkill(actionId, { event }) - } + // magicitems module 3.0.0 does not support Item5e#use + magicItemActor.roll(itemId, magicEffectId) - /** - * Use Item - * @private - * @param {object} event - * @param {string} actorId - * @param {string} tokenId - * @param {string} actionId - * @returns {object} - */ - _useItem (event, actorId, tokenId, actionId) { - const actor = CoreUtils.getActor(actorId, tokenId) - const item = CoreUtils.getItem(actor, actionId) - - if (this._needsRecharge(item)) { - item.rollRecharge() - return + Hooks.callAll('forceUpdateTokenActionHud') } - return item.use({ event }) - } - - /** - * Needs Recharge - * @private - * @param {object} item - * @returns {boolean} - */ - _needsRecharge (item) { - return ( - item.system.recharge && - !item.system.recharge.charged && - item.system.recharge.value - ) - } - - /** - * Perform Utility Macro - * @param {object} event - * @param {string} actorId - * @param {string} tokenId - * @param {string} actionId - */ - async _performUtilityMacro (event, actorId, tokenId, actionId) { - const actor = CoreUtils.getActor(actorId, tokenId) - const token = CoreUtils.getToken(tokenId) + /** + * Roll Skill + * @private + * @param {object} event The event + * @param {object} actor The actor + * @param {string} actionId The action id + */ + _rollSkill (event, actor, actionId) { + if (!actor) return + actor.rollSkill(actionId, { event }) + } - switch (actionId) { - case 'deathSave': - actor.rollDeathSave({ event }) - break - case 'endTurn': - if (!token) break - if (game.combat?.current?.tokenId === tokenId) { - await game.combat?.nextTurn() + /** + * Use Item + * @private + * @param {object} event The event + * @param {object} actor The actor + * @param {string} actionId The action id + * @returns {object} The item + */ + _useItem (event, actor, actionId) { + const item = coreModule.api.Utils.getItem(actor, actionId) + + if (this._needsRecharge(item)) { + item.rollRecharge() + return } - break - case 'initiative': - await this._rollInitiative(actorId) - break - case 'inspiration': { - const update = !actor.system.attributes.inspiration - actor.update({ 'data.attributes.inspiration': update }) - break + + return item.use({ event }) } - case 'longRest': - actor.longRest() - break - case 'shortRest': - actor.shortRest() - break - case 'toggleCombat': - if (canvas.tokens.controlled.length === 0) break - await canvas.tokens.controlled[0].toggleCombat() - break - case 'toggleVisibility': - if (!token) break - token.toggleVisibility() - break + + /** + * Needs Recharge + * @private + * @param {object} item + * @returns {boolean} + */ + _needsRecharge (item) { + return ( + item.system.recharge && + !item.system.recharge.charged && + item.system.recharge.value + ) } - // Update HUD - Hooks.callAll('forceUpdateTokenActionHud') - } + /** + * Perform Utility Macro + * @param {object} event The event + * @param {object} actor The actor + * @param {object} token The token + * @param {string} actionId The action id + */ + async _performUtilityMacro (event, actor, token, actionId) { + switch (actionId) { + case 'deathSave': + actor.rollDeathSave({ event }) + break + case 'endTurn': + if (!token) break + if (game.combat?.current?.tokenId === token.id) { + await game.combat?.nextTurn() + } + break + case 'initiative': + await this._rollInitiative(actor) + break + case 'inspiration': { + const update = !actor.system.attributes.inspiration + actor.update({ 'data.attributes.inspiration': update }) + break + } + case 'longRest': + actor.longRest() + break + case 'shortRest': + actor.shortRest() + break + } - /** - * Roll Initiative - * @private - * @param {string} actorId - * @param {string} tokenId - */ - async _rollInitiative (actorId, tokenId) { - const actor = CoreUtils.getActor(actorId, tokenId) + // Update HUD + Hooks.callAll('forceUpdateTokenActionHud') + } - await actor.rollInitiative({ createCombatants: true }) + /** + * Roll Initiative + * @param {object} actor The actor + * @private + */ + async _rollInitiative (actor) { + if (!actor) return + await actor.rollInitiative({ createCombatants: true }) - Hooks.callAll('forceUpdateTokenActionHud') - } + Hooks.callAll('forceUpdateTokenActionHud') + } - /** - * Toggle Condition - * @private - * @param {object} event - * @param {string} tokenId - * @param {string} actionId - * @param {object} effect - */ - async _toggleCondition (event, tokenId, actionId, effect = null) { - const token = CoreUtils.getToken(tokenId) - if (!token) return - const isRightClick = this.isRightClick(event) - if (game.dfreds && effect?.flags?.isConvenient) { - const effectLabel = effect.label - game.dfreds.effectInterface._toggleEffect(effectLabel) - } else { - const condition = this.findCondition(actionId) - if (!condition) return + /** + * Toggle Condition + * @private + * @param {object} event The event + * @param {object} token The token + * @param {string} actionId The action id + * @param {object} effect The effect + */ + async _toggleCondition (event, token, actionId, effect = null) { + if (!token) return + const isRightClick = this.isRightClick(event) + if (game.dfreds && effect?.flags?.isConvenient) { + const effectLabel = effect.label + game.dfreds.effectInterface._toggleEffect(effectLabel) + } else { + const condition = this.findCondition(actionId) + if (!condition) return + + isRightClick + ? await token.toggleEffect(condition, { overlay: true }) + : await token.toggleEffect(condition) + } - isRightClick - ? await token.toggleEffect(condition, { overlay: true }) - : await token.toggleEffect(condition) + Hooks.callAll('forceUpdateTokenActionHud') } - Hooks.callAll('forceUpdateTokenActionHud') - } + /** + * Get Condition + * @private + * @param {string} actionId + * @returns {object} + */ + findCondition (actionId) { + return CONFIG.statusEffects.find((effect) => effect.id === actionId) + } - /** - * Get Condition - * @private - * @param {string} actionId - * @returns {object} - */ - findCondition (id) { - return CONFIG.statusEffects.find((effect) => effect.id === id) - } + /** + * Toggle Effect + * @param {object} event The event + * @param {object} actor The actor + * @param {string} actionId The action id + */ + async _toggleEffect (event, actor, actionId) { + const effects = 'find' in actor.effects.entries ? actor.effects.entries : actor.effects + const effect = effects.find(effect => effect.id === actionId) - /** - * Toggle Effect - * @param {object} event - * @param {string} actorId - * @param {string} tokenId - * @param {string} actionId - */ - async _toggleEffect (event, actorId, tokenId, actionId) { - const actor = CoreUtils.getActor(actorId, tokenId) - const effects = 'find' in actor.effects.entries ? actor.effects.entries : actor.effects - const effect = effects.find((e) => e.id === actionId) + if (!effect) return - if (!effect) return + const isRightClick = this.isRightClick(event) - const isRightClick = this.isRightClick(event) + if (isRightClick) { + await effect.delete() + } else { + await effect.update({ disabled: !effect.disabled }) + } - if (isRightClick) { - await effect.delete() - } else { - await effect.update({ disabled: !effect.disabled }) + Hooks.callAll('forceUpdateTokenActionHud') } - - Hooks.callAll('forceUpdateTokenActionHud') } -} +}) diff --git a/scripts/system-manager.js b/scripts/system-manager.js index cf0c8ab..5b2e0ca 100644 --- a/scripts/system-manager.js +++ b/scripts/system-manager.js @@ -3,66 +3,76 @@ import { ActionHandler } from './action-handler.js' import { MagicItemActionListExtender } from './magic-items-extender.js' import { RollHandler as Core } from './roll-handler.js' import { RollHandlerObsidian as Obsidian5e } from './roll-handler-obsidian.js' -import * as systemSettings from './settings.js' import { DEFAULTS } from './defaults.js' +import * as systemSettings from './settings.js' -// Core Module Imports -import { CoreSystemManager, CoreCategoryManager, CoreUtils } from './config.js' - -export class SystemManager extends CoreSystemManager { - /** @override */ - doGetCategoryManager () { - return new CoreCategoryManager() - } +export let SystemManager = null +Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => { + SystemManager = class SystemManager extends coreModule.api.SystemManager { /** @override */ - doGetActionHandler (categoryManager) { - const actionHandler = new ActionHandler(categoryManager) - if (CoreUtils.isModuleActive('magicitems')) { actionHandler.addFurtherActionHandler(new MagicItemActionListExtender(actionHandler)) } - return actionHandler - } + doGetCategoryManager () { + return new coreModule.api.CategoryManager() + } - /** @override */ - getAvailableRollHandlers () { - let coreTitle = 'Core D&D5e' + /** @override */ + doGetActionHandler (categoryManager) { + const actionHandler = new ActionHandler(categoryManager) + if (coreModule.api.Utils.isModuleActive('magicitems')) { actionHandler.addFurtherActionHandler(new MagicItemActionListExtender(actionHandler)) } + return actionHandler + } - if (CoreUtils.isModuleActive('midi-qol')) { coreTitle += ` [supports ${CoreUtils.getModuleTitle('midi-qol')}]` } + /** @override */ + getAvailableRollHandlers () { + let coreTitle = 'Core D&D5e' - const choices = { core: coreTitle } - CoreSystemManager.addHandler(choices, 'obsidian') + if (coreModule.api.Utils.isModuleActive('midi-qol')) { coreTitle += ` [supports ${coreModule.api.Utils.getModuleTitle('midi-qol')}]` } - return choices - } + const choices = { core: coreTitle } + coreModule.api.SystemManager.addHandler(choices, 'obsidian') - /** @override */ - doGetRollHandler (handlerId) { - let rollHandler - switch (handlerId) { - case 'obsidian': - rollHandler = new Obsidian5e() - break - case 'core': - default: - rollHandler = new Core() - break + return choices } - return rollHandler - } + /** @override */ + doGetRollHandler (handlerId) { + let rollHandler + switch (handlerId) { + case 'obsidian': + rollHandler = new Obsidian5e() + break + case 'core': + default: + rollHandler = new Core() + break + } - /** @override */ - doRegisterSettings (updateFunc) { - systemSettings.register(updateFunc) - } + return rollHandler + } - /** @override */ - async doRegisterDefaultFlags () { - const defaults = DEFAULTS - // If the 'Magic Items' module is active, then add a subcategory for it - if (game.modules.get('magicitems')?.active) { - defaults.subcategories.push({ id: 'magic-items', name: CoreUtils.i18n('tokenActionHud.dnd5e.magicItems'), type: 'system', hasDerivedSubcategories: true }) - defaults.subcategories.sort((a, b) => a.id.localeCompare(b.id)) + /** @override */ + doRegisterSettings (updateFunc) { + systemSettings.register(updateFunc) + } + + /** @override */ + async doRegisterDefaultFlags () { + const defaults = DEFAULTS + // If the 'Magic Items' module is active, then add a subcategory for it + if (game.modules.get('magicitems')?.active) { + const name = coreModule.api.Utils.i18n('tokenActionHud.dnd5e.magicItems') + defaults.subcategories.push( + { + id: 'magic-items', + name, + listName: `Subcategory: ${name}`, + type: 'system', + hasDerivedSubcategories: true + } + ) + defaults.subcategories.sort((a, b) => a.id.localeCompare(b.id)) + } + return defaults } - return defaults } -} +}) diff --git a/scripts/token-action-hud-dnd5e.min.js b/scripts/token-action-hud-dnd5e.min.js index e6562a4..706e317 100644 --- a/scripts/token-action-hud-dnd5e.min.js +++ b/scripts/token-action-hud-dnd5e.min.js @@ -1 +1 @@ -const e=await import("../../token-action-hud-core/scripts/token-action-hud-core.min.js"),t=e.ActionHandler,s=e.ActionListExtender,i=e.CategoryManager,n=e.PreRollHandler,a=e.RollHandler,l=e.SystemManager,o=e.Utils,c=e.Logger,d={ID:"token-action-hud-dnd5e"},r={ID:"token-action-hud-core"},u="1.2",p={bonus:"fas fa-plus",crew:"fas fa-users",day:"fas fa-hourglass-end",hour:"fas fa-hourglass-half",lair:"fas fa-home",minute:"fas fa-hourglass-start",legendary:"fas fas fa-dragon",reaction:"fas fa-bolt",special:"fas fa-star"},m="fas fa-circle-c",g="fas fa-sun",h="fas fa-circle-r",y={.5:"fas fa-adjust",1:"fas fa-check",2:"fas fa-check-double"};let b=null;Hooks.on("i18nInit",(async()=>{b={categories:[{nestId:"inventory",id:"inventory",name:game.i18n.localize("DND5E.Inventory"),subcategories:[{nestId:"inventory_weapons",id:"weapons",name:game.i18n.localize("ITEM.TypeWeaponPl"),type:"system",hasDerivedSubcategories:!1},{nestId:"inventory_equipment",id:"equipment",name:game.i18n.localize("ITEM.TypeEquipmentPl"),type:"system",hasDerivedSubcategories:!1},{nestId:"inventory_consumables",id:"consumables",name:game.i18n.localize("ITEM.TypeConsumablePl"),type:"system",hasDerivedSubcategories:!1},{nestId:"inventory_tools",id:"tools",name:game.i18n.localize("ITEM.TypeToolPl"),type:"system",hasDerivedSubcategories:!1},{nestId:"inventory_containers",id:"containers",name:game.i18n.localize("ITEM.TypeContainerPl"),type:"system",hasDerivedSubcategories:!1},{nestId:"inventory_loot",id:"loot",name:game.i18n.localize("ITEM.TypeLootPl"),type:"system",hasDerivedSubcategories:!1}]},{nestId:"features",id:"features",name:game.i18n.localize("DND5E.Features"),subcategories:[{nestId:"features_active-features",id:"active-features",name:game.i18n.localize("tokenActionHud.dnd5e.activeFeatures"),type:"system",hasDerivedSubcategories:!1},{id:"passive-features",nestId:"features_passive-features",name:game.i18n.localize("tokenActionHud.dnd5e.passiveFeatures"),type:"system",hasDerivedSubcategories:!1}]},{nestId:"spells",id:"spells",name:game.i18n.localize("ITEM.TypeSpellPl"),subcategories:[{nestId:"spells_at-will-spells",id:"at-will-spells",name:game.i18n.localize("tokenActionHud.dnd5e.atWillSpells"),type:"system",hasDerivedSubcategories:!1},{nestId:"spells_innate-spells",id:"innate-spells",name:game.i18n.localize("tokenActionHud.dnd5e.innateSpells"),type:"system",hasDerivedSubcategories:!1},{nestId:"spells_pact-spells",id:"pact-spells",name:game.i18n.localize("tokenActionHud.dnd5e.pactSpells"),type:"system",hasDerivedSubcategories:!1},{nestId:"spells_cantrips",id:"cantrips",name:game.i18n.localize("tokenActionHud.dnd5e.cantrips"),type:"system",hasDerivedSubcategories:!1},{nestId:"spells_1st-level-spells",id:"1st-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.1stLevelSpells"),type:"system",hasDerivedSubcategories:!1},{nestId:"spells_2nd-level-spells",id:"2nd-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.2ndLevelSpells"),type:"system",hasDerivedSubcategories:!1},{nestId:"spells_3rd-level-spells",id:"3rd-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.3rdLevelSpells"),type:"system",hasDerivedSubcategories:!1},{nestId:"spells_4th-level-spells",id:"4th-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.4thLevelSpells"),type:"system",hasDerivedSubcategories:!1},{nestId:"spells_5th-level-spells",id:"5th-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.5thLevelSpells"),type:"system",hasDerivedSubcategories:!1},{nestId:"spells_6th-level-spells",id:"6th-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.6thLevelSpells"),type:"system",hasDerivedSubcategories:!1},{nestId:"spells_7th-level-spells",id:"7th-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.7thLevelSpells"),type:"system",hasDerivedSubcategories:!1},{nestId:"spells_8th-level-spells",id:"8th-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.8thLevelSpells"),type:"system",hasDerivedSubcategories:!1},{nestId:"spells_9th-level-spells",id:"9th-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.9thLevelSpells"),type:"system",hasDerivedSubcategories:!1}]},{nestId:"attributes",id:"attributes",name:game.i18n.localize("DND5E.Attributes"),subcategories:[{nestId:"attributes_abilities",id:"abilities",name:game.i18n.localize("tokenActionHud.dnd5e.abilities"),type:"system",hasDerivedSubcategories:!1},{nestId:"attributes_skills",id:"skills",name:game.i18n.localize("tokenActionHud.dnd5e.skills"),type:"system",hasDerivedSubcategories:!1}]},{nestId:"effects",id:"effects",name:game.i18n.localize("DND5E.Effects"),subcategories:[{nestId:"effects_temporary-effects",id:"temporary-effects",name:game.i18n.localize("DND5E.EffectTemporary"),type:"system",hasDerivedSubcategories:!1},{nestId:"effects_passive-effects",id:"passive-effects",name:game.i18n.localize("DND5E.EffectPassive"),type:"system",hasDerivedSubcategories:!1}]},{nestId:"conditions",id:"conditions",name:game.i18n.localize("tokenActionHud.dnd5e.conditions"),subcategories:[{nestId:"conditions_conditions",id:"conditions",name:game.i18n.localize("tokenActionHud.dnd5e.conditions"),type:"system",hasDerivedSubcategories:!1}]},{nestId:"utility",id:"utility",name:game.i18n.localize("tokenActionHud.utility"),subcategories:[{nestId:"utility_combat",id:"combat",name:game.i18n.localize("tokenActionHud.combat"),type:"system",hasDerivedSubcategories:!1},{nestId:"utility_token",id:"token",name:game.i18n.localize("tokenActionHud.token"),type:"system",hasDerivedSubcategories:!1},{nestId:"utility_rests",id:"rests",name:game.i18n.localize("tokenActionHud.dnd5e.rests"),type:"system",hasDerivedSubcategories:!1},{nestId:"utility_utility",id:"utility",name:game.i18n.localize("tokenActionHud.utility"),type:"system",hasDerivedSubcategories:!1}]}],subcategories:[{id:"1st-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.1stLevelSpells"),type:"system",hasDerivedSubcategories:!1},{id:"2nd-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.2ndLevelSpells"),type:"system",hasDerivedSubcategories:!1},{id:"3rd-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.3rdLevelSpells"),type:"system",hasDerivedSubcategories:!1},{id:"4th-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.4thLevelSpells"),type:"system",hasDerivedSubcategories:!1},{id:"5th-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.5thLevelSpells"),type:"system",hasDerivedSubcategories:!1},{id:"6th-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.6thLevelSpells"),type:"system",hasDerivedSubcategories:!1},{id:"7th-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.7thLevelSpells"),type:"system",hasDerivedSubcategories:!1},{id:"8th-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.8thLevelSpells"),type:"system",hasDerivedSubcategories:!1},{id:"9th-level-spells",name:game.i18n.localize("tokenActionHud.dnd5e.9thLevelSpells"),type:"system",hasDerivedSubcategories:!1},{id:"abilities",name:game.i18n.localize("tokenActionHud.dnd5e.abilities"),type:"system",hasDerivedSubcategories:!1},{id:"actions",name:game.i18n.localize("DND5E.ActionPl"),type:"system",hasDerivedSubcategories:!0},{id:"active-features",name:game.i18n.localize("tokenActionHud.dnd5e.activeFeatures"),type:"system",hasDerivedSubcategories:!1},{id:"artificer-infusions",name:game.i18n.localize("tokenActionHud.dnd5e.artificerInfusions"),type:"system",hasDerivedSubcategories:!1},{id:"at-will-spells",name:game.i18n.localize("tokenActionHud.dnd5e.atWillSpells"),type:"system",hasDerivedSubcategories:!1},{id:"background-features",name:game.i18n.localize("tokenActionHud.dnd5e.backgroundFeatures"),type:"system",hasDerivedSubcategories:!1},{id:"bonus-actions",name:game.i18n.localize("tokenActionHud.dnd5e.bonusActions"),type:"system",hasDerivedSubcategories:!0},{id:"cantrips",name:game.i18n.localize("tokenActionHud.dnd5e.cantrips"),type:"system",hasDerivedSubcategories:!1},{id:"channel-divinity",name:game.i18n.localize("tokenActionHud.dnd5e.channelDivinity"),type:"system",hasDerivedSubcategories:!1},{id:"checks",name:game.i18n.localize("tokenActionHud.dnd5e.checks"),type:"system",hasDerivedSubcategories:!1},{id:"class-features",name:game.i18n.localize("tokenActionHud.dnd5e.classFeatures"),type:"system",hasDerivedSubcategories:!1},{id:"combat",name:game.i18n.localize("tokenActionHud.combat"),type:"system",hasDerivedSubcategories:!1},{id:"conditions",name:game.i18n.localize("tokenActionHud.dnd5e.conditions"),type:"system",hasDerivedSubcategories:!1},{id:"consumables",name:game.i18n.localize("ITEM.TypeConsumablePl"),type:"system",hasDerivedSubcategories:!1},{id:"containers",name:game.i18n.localize("ITEM.TypeContainerPl"),type:"system",hasDerivedSubcategories:!1},{id:"crew-actions",name:game.i18n.localize("tokenActionHud.dnd5e.crewActions"),type:"system",hasDerivedSubcategories:!0},{id:"defensive-tactics",name:game.i18n.localize("tokenActionHud.dnd5e.defensiveTactics"),type:"system",hasDerivedSubcategories:!1},{id:"eldritch-invocations",name:game.i18n.localize("tokenActionHud.dnd5e.eldritchInvocations"),type:"system",hasDerivedSubcategories:!1},{id:"elemental-disciplines",name:game.i18n.localize("tokenActionHud.dnd5e.elementalDisciplines"),type:"system",hasDerivedSubcategories:!1},{id:"equipment",name:game.i18n.localize("ITEM.TypeEquipmentPl"),type:"system",hasDerivedSubcategories:!1},{id:"equipped",name:game.i18n.localize("DND5E.Equipped"),type:"system",hasDerivedSubcategories:!1},{id:"feats",name:game.i18n.localize("tokenActionHud.dnd5e.feats"),type:"system",hasDerivedSubcategories:!1},{id:"fighting-styles",name:game.i18n.localize("tokenActionHud.dnd5e.fightingStyles"),type:"system",hasDerivedSubcategories:!1},{id:"hunters-prey",name:game.i18n.localize("tokenActionHud.dnd5e.huntersPrey"),type:"system",hasDerivedSubcategories:!1},{id:"innate-spells",name:game.i18n.localize("tokenActionHud.dnd5e.innateSpells"),type:"system",hasDerivedSubcategories:!1},{id:"ki-abilities",name:game.i18n.localize("tokenActionHud.dnd5e.kiAbilities"),type:"system",hasDerivedSubcategories:!1},{id:"lair-actions",name:game.i18n.localize("tokenActionHud.dnd5e.lairActions"),type:"system",hasDerivedSubcategories:!0},{id:"legendary-actions",name:game.i18n.localize("tokenActionHud.dnd5e.legendaryActions"),type:"system",hasDerivedSubcategories:!0},{id:"loot",name:game.i18n.localize("ITEM.TypeLootPl"),type:"system",hasDerivedSubcategories:!1},{id:"maneuvers",name:game.i18n.localize("tokenActionHud.dnd5e.maneuvers"),type:"system",hasDerivedSubcategories:!1},{id:"metamagic-options",name:game.i18n.localize("tokenActionHud.dnd5e.metamagicOptions"),type:"system",hasDerivedSubcategories:!1},{id:"monster-features",name:game.i18n.localize("tokenActionHud.dnd5e.monsterFeatures"),type:"system",hasDerivedSubcategories:!1},{id:"multiattacks",name:game.i18n.localize("tokenActionHud.dnd5e.multiattacks"),type:"system",hasDerivedSubcategories:!1},{id:"other-actions",name:game.i18n.localize("tokenActionHud.dnd5e.otherActions"),type:"system",hasDerivedSubcategories:!0},{id:"pact-boons",name:game.i18n.localize("tokenActionHud.dnd5e.pactBoons"),type:"system",hasDerivedSubcategories:!1},{id:"pact-spells",name:game.i18n.localize("tokenActionHud.dnd5e.pactSpells"),type:"system",hasDerivedSubcategories:!1},{id:"passive-effects",name:game.i18n.localize("DND5E.EffectPassive"),type:"system",hasDerivedSubcategories:!1},{id:"passive-features",name:game.i18n.localize("tokenActionHud.dnd5e.passiveFeatures"),type:"system",hasDerivedSubcategories:!1},{id:"psionic-powers",name:game.i18n.localize("tokenActionHud.dnd5e.psionicPowers"),type:"system",hasDerivedSubcategories:!1},{id:"race-features",name:game.i18n.localize("tokenActionHud.dnd5e.raceFeatures"),type:"system",hasDerivedSubcategories:!1},{id:"reactions",name:game.i18n.localize("DND5E.ReactionPl"),type:"system",hasDerivedSubcategories:!0},{id:"rests",name:game.i18n.localize("tokenActionHud.dnd5e.rests"),type:"system",hasDerivedSubcategories:!1},{id:"runes",name:game.i18n.localize("tokenActionHud.dnd5e.runes"),type:"system",hasDerivedSubcategories:!1},{id:"saves",name:game.i18n.localize("DND5E.ClassSaves"),type:"system",hasDerivedSubcategories:!1},{id:"skills",name:game.i18n.localize("tokenActionHud.dnd5e.skills"),type:"system",hasDerivedSubcategories:!1},{id:"superior-hunters-defense",name:game.i18n.localize("tokenActionHud.dnd5e.superiorHuntersDefense"),type:"system",hasDerivedSubcategories:!1},{id:"temporary-effects",name:game.i18n.localize("DND5E.EffectTemporary"),type:"system",hasDerivedSubcategories:!1},{id:"token",name:game.i18n.localize("tokenActionHud.token"),type:"system",hasDerivedSubcategories:!1},{id:"tools",name:game.i18n.localize("ITEM.TypeToolPl"),type:"system",hasDerivedSubcategories:!1},{id:"unequipped",name:game.i18n.localize("DND5E.Unequipped"),type:"system",hasDerivedSubcategories:!1},{id:"utility",name:game.i18n.localize("tokenActionHud.utility"),type:"system",hasDerivedSubcategories:!1},{id:"weapons",name:game.i18n.localize("ITEM.TypeWeaponPl"),type:"system",hasDerivedSubcategories:!1}]}}));class MagicItemActionListExtender extends s{constructor(e){super(e.categoryManager),this.actionHandler=e,this.categoryManager=e.categoryManager}extendActionList(e){const t=this.actionHandler.actorId,s=this.actionHandler.tokenId;if(!t)return;const i=MagicItems.actor(t);if(!i)return;const n=i.items??[];if(0===n.length)return;const a={id:"magic-items",type:"system"};n.forEach((e=>{if(e.attuned&&!this._isItemAttuned(e))return;if(e.equipped&&!this._isItemEquipped(e))return;const i={id:`magic-items_${e.id}`,name:e.name,type:"system-derived",info1:`${e.uses}/${e.charges}`};this.actionHandler.addSubcategoryToActionList(a,i);const n=e.ownedEntries.map((i=>{const n=i.item,a=n.id;return{id:a,name:n.name,encodedValue:["magicItem",t,s,`${e.id}>${a}`].join("|"),img:o.getImage(n),info1:n.consumption,info2:n.baseLevel?`${o.i18n("DND5E.AbbreviationLevel")} ${n.baseLevel}`:"",selected:!0}}));this.actionHandler.addActionsToActionList(n,i)}))}_isItemEquipped(e){return e.item.system.equipped}_isItemAttuned(e){return e.item.system.attunment!==(CONFIG.DND5E.attunementTypes?.REQUIRED??1)}}class RollHandler extends a{async doHandleActionEvent(e,t){const s=t.split("|");4!==s.length&&super.throwInvalidValueErr();const i=s[0],n=s[1],a=s[2],l=s[3];if("multi"===n&&"multi"===a&&"toggleCombat"!==l)for(const t of canvas.tokens.controlled){const s=t.actor?.id,n=t.id;await this._handleMacros(e,i,s,n,l)}else await this._handleMacros(e,i,n,a,l)}async _handleMacros(e,t,s,i,n){switch(t){case"ability":this._rollAbility(e,s,i,n);break;case"abilityCheck":this._rollAbilityTest(e,s,i,n);break;case"abilitySave":this._rollAbilitySave(e,s,i,n);break;case"condition":if(!i)return;await this._toggleCondition(e,i,n);break;case"effect":await this._toggleEffect(e,s,i,n);break;case"feature":case"item":case"spell":case"weapon":this.isRenderItem()?this.doRenderItem(s,i,n):this._useItem(e,s,i,n);break;case"magicItem":this._rollMagicItem(e,s,i,n);break;case"skill":this._rollSkill(e,s,i,n);break;case"utility":await this._performUtilityMacro(e,s,i,n)}}_rollAbility(e,t,s,i){o.getActor(t,s).rollAbility(i,{event:e})}_rollAbilitySave(e,t,s,i){o.getActor(t,s).rollAbilitySave(i,{event:e})}_rollAbilityTest(e,t,s,i){o.getActor(t,s).rollAbilityTest(i,{event:e})}_rollMagicItem(e,t,s,i){const n=o.getActor(t,s),a=i.split(">"),l=a[0],c=a[1];MagicItems.actor(n.id).roll(l,c),Hooks.callAll("forceUpdateTokenActionHud")}_rollSkill(e,t,s,i){o.getActor(t,s).rollSkill(i,{event:e})}_useItem(e,t,s,i){const n=o.getActor(t,s),a=o.getItem(n,i);if(!this._needsRecharge(a))return a.use({event:e});a.rollRecharge()}_needsRecharge(e){return e.system.recharge&&!e.system.recharge.charged&&e.system.recharge.value}async _performUtilityMacro(e,t,s,i){const n=o.getActor(t,s),a=o.getToken(s);switch(i){case"deathSave":n.rollDeathSave({event:e});break;case"endTurn":if(!a)break;game.combat?.current?.tokenId===s&&await(game.combat?.nextTurn());break;case"initiative":await this._rollInitiative(t);break;case"inspiration":{const e=!n.system.attributes.inspiration;n.update({"data.attributes.inspiration":e});break}case"longRest":n.longRest();break;case"shortRest":n.shortRest();break;case"toggleCombat":if(0===canvas.tokens.controlled.length)break;await canvas.tokens.controlled[0].toggleCombat();break;case"toggleVisibility":if(!a)break;a.toggleVisibility()}Hooks.callAll("forceUpdateTokenActionHud")}async _rollInitiative(e,t){const s=o.getActor(e,t);await s.rollInitiative({createCombatants:!0}),Hooks.callAll("forceUpdateTokenActionHud")}async _toggleCondition(e,t,s,i=null){const n=o.getToken(t);if(!n)return;const a=this.isRightClick(e);if(game.dfreds&&i?.flags?.isConvenient){const e=i.label;game.dfreds.effectInterface._toggleEffect(e)}else{const e=this.findCondition(s);if(!e)return;a?await n.toggleEffect(e,{overlay:!0}):await n.toggleEffect(e)}Hooks.callAll("forceUpdateTokenActionHud")}findCondition(e){return CONFIG.statusEffects.find((t=>t.id===e))}async _toggleEffect(e,t,s,i){const n=o.getActor(t,s),a=("find"in n.effects.entries?n.effects.entries:n.effects).find((e=>e.id===i));if(!a)return;this.isRightClick(e)?await a.delete():await a.update({disabled:!a.disabled}),Hooks.callAll("forceUpdateTokenActionHud")}}class RollHandlerObsidian extends RollHandler{_rollAbilityTest(e,t,s,i){const n=super.getActor(t,s);OBSIDIAN.Items.roll(n,{roll:"abl",abl:i})}_rollAbilitySave(e,t,s,i){const n=super.getActor(t,s);OBSIDIAN.Items.roll(n,{roll:"save",save:i})}_rollSkill(e,t,s,i){const n=super.getActor(t,s);OBSIDIAN.Items.roll(n,{roll:"skl",skl:i})}_useItem(e,t,s,i){const n=super.getActor(t,s);OBSIDIAN.Items.roll(n,{roll:"item",id:i})}}class Utils{static getSetting(e,t=null){let s=t??null;try{s=game.settings.get(d.ID,e)}catch{c.debug(`Setting '${e}' not found`)}return s}static async setSetting(e,t){try{t=await game.settings.set(d.ID,e,t),c.debug(`Setting '${e}' set to '${t}'`)}catch{c.debug(`Setting '${e}' not found`)}}}class ActionHandler extends t{actor=null;actors=null;actorId=null;actorType=null;character=null;token=null;tokenId=null;items=null;abbreviateSkills=null;displaySpellInfo=null;showItemsWithoutActivationCosts=null;showUnchargedItems=null;showUnequippedItems=null;showUnpreparedSpells=null;subcategoryIds=null;activationSubcategoryIds=null;effectSubcategoryIds=null;featureSubcategoryIds=null;inventorySubcategoryIds=null;spellSubcategoryIds=null;featureActions=null;inventoryActions=null;spellActions=null;async buildSystemActions(e,t){if(this.actor=e?.actor,this.actorId=this.actor?.id??"multi",this.actors="multi"===this.actorId?this._getActors():[this.actor],this.actorType=this.actor?.type,this.token=e?.token,this.tokenId=this.token?.id??"multi","multi"!==this.actorId){let e=this.actor.items;e=this._discardSlowItems(e),e=this.sortItemsByName(e),this.items=e}this.abbreviateSkills=Utils.getSetting("abbreviateSkills"),this.displaySpellInfo=Utils.getSetting("displaySpellInfo"),this.showItemsWithoutActivationCosts=Utils.getSetting("showItemsWithoutActivationCosts"),this.showUnchargedItems=Utils.getSetting("showUnchargedItems"),this.showUnequippedItems=Utils.getSetting("showUnequippedItems"),this.showUnpreparedSpells=Utils.getSetting("showUnpreparedSpells"),this.subcategoryIds=t,this.activationSubcategoryIds=t.filter((e=>"actions"===e||"bonus-actions"===e||"crew-actions"===e||"lair-actions"===e||"legendary-actions"===e||"reactions"===e||"other-actions"===e)),this.effectSubcategoryIds=t.filter((e=>"passive-effects"===e||"temporary-effects"===e)),this.featureSubcategoryIds=t.filter((e=>"active-features"===e||"passive-features"===e||"background-features"===e||"class-features"===e||"feats"===e||"monster-features"===e||"race-features"===e||"artificer-infusions"===e||"channel-divinity"===e||"defensive-tactics"===e||"eldritch-invocations"===e||"elemental-disciplines"===e||"fighting-styles"===e||"hunters-prey"===e||"ki-abilities"===e||"maneuvers"===e||"metamagic-options"===e||"multiattacks"===e||"pact-boons"===e||"psionic-powers"===e||"runes"===e||"superior-hunters-defense"===e)),this.spellSubcategoryIds=t.filter((e=>"cantrips"===e||"1st-level-spells"===e||"2nd-level-spells"===e||"3rd-level-spells"===e||"4th-level-spells"===e||"5th-level-spells"===e||"6th-level-spells"===e||"7th-level-spells"===e||"8th-level-spells"===e||"9th-level-spells"===e||"at-will-spells"===e||"innate-spells"===e||"pact-spells"===e)),this.activationSubcategoryIds.length>0&&(this.featureSubcategoryIds=["active-features","passive-features"],this.spellSubcategoryIds=["cantrips","1st-level-spells","2nd-level-spells","3rd-level-spells","4th-level-spells","5th-level-spells","6th-level-spells","7th-level-spells","8th-level-spells","9th-level-spells","at-will-spells","innate-spells","pact-spells"]),"character"!==this.actorType&&"npc"!==this.actorType||(this.inventorySubcategoryIds=t.filter((e=>"equipped"===e||"consumables"===e||"containers"===e||"equipment"===e||"loot"===e||"tools"===e||"weapons"===e||"unequipped"===e)),this.activationSubcategoryIds.length>0&&(this.inventorySubcategoryIds=["consumables","containers","equipment","loot","tools","weapons"]),this._buildCharacterActions()),"vehicle"===this.actorType&&(this.inventorySubcategoryIds=this.subcategoryIds.filter((e=>"consumables"===e||"equipment"===e||"tools"===e||"weapons"===e)),this.activationSubcategoryIds.length>0&&(this.inventorySubcategoryIds=["consumables","equipment","tools","weapons"]),this._buildVehicleActions()),this.actor||this._buildMultipleTokenActions()}async _buildCharacterActions(){this._buildAbilities("ability","abilities"),this._buildAbilities("abilityCheck","checks"),this._buildAbilities("abilitySave","saves"),this._buildCombat(),this._buildConditions(),this._buildEffects(),this._buildFeatures(),this._buildInventory(),this._buildRests(),this._buildSkills(),this._buildSpells(),this._buildUtility()}async _buildVehicleActions(){this._buildAbilities("ability","abilities"),this._buildAbilities("abilityCheck","checks"),this._buildAbilities("abilitySave","saves"),this._buildCombat(),this._buildConditions(),this._buildEffects(),this._buildFeatures(),this._buildInventory(),this._buildUtility()}async _buildMultipleTokenActions(){this._buildAbilities("ability","abilities"),this._buildAbilities("abilityCheck","checks"),this._buildAbilities("abilitySave","saves"),this._buildCombat(),this._buildConditions(),this._buildRests(),this._buildSkills(),this._buildUtility()}_buildAbilities(e,t){if(!this.subcategoryIds.includes(t))return;const s="multi"===this.actorId?game.dnd5e.config.abilities:this.actor.system.abilities;if(0===s.length)return;const i=Object.entries(s).filter((e=>0!==s[e[0]].value)).map((i=>{const n=i[0],a=n.charAt(0).toUpperCase()+n.slice(1),l=this.abbreviateSkills?a:game.dnd5e.config.abilities[n],o=[e,this.actorId,this.tokenId,n].join(this.delimiter);return{id:n,name:l,encodedValue:o,icon:"checks"!==t?this._getProficiencyIcon(s[n].proficient):"",selected:!0}})),n={id:t,type:"system"};this.addActionsToActionList(i,n)}async buildActivations(e,t,s="item"){const i=new Map,n={actions:"action","bonus-actions":"bonus","crew-actions":"crew","lair-actions":"lair","legendary-actions":"legendary",reactions:"reaction","other-actions":"other"},a=["action","bonus","crew","lair","legendary","reaction"];for(const[t,s]of e){const e=s.system?.activation?.type,n=a.includes(e)?e:"other";i.has(n)||i.set(n,new Map),i.get(n).set(t,s)}for(const e of this.activationSubcategoryIds){const a=n[e];if(!i.has(a))continue;const l={...t,id:`${e}+${t.id}`,type:"system-derived"},o={id:e,type:"system"};await this.addSubcategoryToActionList(o,l),this.addSubcategoryInfo(t),this._buildActions(i.get(a),l,s)}}_buildCombat(){if(!this.subcategoryIds.includes("combat"))return;const e="utility",t={initiative:{id:"initiative",name:o.i18n("tokenActionHud.dnd5e.rollInitiative")},endTurn:{id:"endTurn",name:o.i18n("tokenActionHud.endTurn")}};game.combat?.current?.tokenId!==this.tokenId&&delete t.endTurn;const s=Object.entries(t).map((t=>{const s=t[1].id,i=t[1].name,n=[e,this.actorId,this.tokenId,s].join(this.delimiter),a={};let l="";if("initiative"===t[0]&&game.combat){const e=canvas.tokens.controlled.map((e=>e.id)),t=game.combat.combatants.filter((t=>e.includes(t.tokenId)));if(1===t.length){const e=t[0].initiative;a.class="tah-spotlight",a.text=e}l=`toggle${t.length>0&&t.every((e=>e?.initiative))?" active":""}`}return{id:s,name:i,encodedValue:n,info1:a,cssClass:l,selected:!0}}));this.addActionsToActionList(s,{id:"combat",type:"system"})}_buildConditions(){if(!this.subcategoryIds.includes("conditions"))return;if(!this.token)return;const e="condition",t=CONFIG.statusEffects.filter((e=>""!==e.id));if(0===t.length)return;const s=t.map((t=>{const s=t.id,i=o.i18n(t.label),n=[e,this.actorId,this.tokenId,s].join(this.delimiter),a=`toggle${this.actors.every((e=>e.effects.map((e=>e.flags?.core?.statusId)).some((e=>e===s))))?" active":""}`,l=o.getImage(t);return{id:s,name:i,encodedValue:n,img:l,cssClass:a,selected:!0}}));this.addActionsToActionList(s,{id:"conditions",type:"system"})}_buildEffects(){if(!this.effectSubcategoryIds)return;const e="effect",t=this.actor.effects;if(0===t.size)return;const s=new Map,i=new Map;for(const e of t){const t=e.id;e.isTemporary?i.set(t,e):s.set(t,e)}if(this.effectSubcategoryIds.includes("passive-effects")){const t={id:"passive-effects",type:"system"};this._buildActions(s,t,e)}if(this.effectSubcategoryIds.includes("temporary-effects")){const t={id:"temporary-effects",type:"system"};this._buildActions(i,t,e)}}_buildFeatures(){if(!this.featureSubcategoryIds)return;const e="feature",t=new Map;for(const[e,s]of this.items){"feat"===s.type&&t.set(e,s)}if(0===t.size)return;const s=new Map,i=[{type:"background",subcategoryId:"background-features"},{type:"class",subcategoryId:"class-features"},{type:"monster",subcategoryId:"monster-features"},{type:"race",subcategoryId:"race-features"},{type:"feats",subcategoryId:"feats"}],n=[{type:"artificerInfusion",subcategoryId:"artificer-infusions"},{type:"channelDivinity",subcategoryId:"channel-divinity"},{type:"defensiveTactic",subcategoryId:"defensive-tactics"},{type:"eldritchInvocation",subcategoryId:"eldritch-invocations"},{type:"elementalDiscipline",subcategoryId:"elemental-disciplines"},{type:"fightingStyle",subcategoryId:"fighting-styles"},{type:"huntersPrey",subcategoryId:"hunters-prey"},{type:"ki",subcategoryId:"ki-abilities"},{type:"maneuver",subcategoryId:"maneuvers"},{type:"metamagic",subcategoryId:"metamagic-options"},{type:"multiattack",subcategoryId:"multiattacks"},{type:"pact",subcategoryId:"pact-boons"},{type:"psionicPower",subcategoryId:"psionic-powers"},{type:"rune",subcategoryId:"runes"},{type:"superiorHuntersDefense",subcategoryId:"superior-hunters-defense"}];for(const[e,a]of t){const t=a.system.activation?.type,l=a.system.type.value,o=a.system.type?.subtype,c=["","lair","legendary"];t&&!c.includes(t)&&(s.has("active-features")||s.set("active-features",new Map),s.get("active-features").set(e,a)),t&&""!==t||(s.has("passive-features")||s.set("passive-features",new Map),s.get("passive-features").set(e,a));for(const t of i){const i=t.subcategoryId;t.type===l&&(s.has(i)||s.set(i,new Map),s.get(i).set(e,a))}for(const t of n){const i=t.subcategoryId;o&&t.type===o&&(s.has(i)||s.set(i,new Map),s.get(i).set(e,a))}}const a={"active-features":o.i18n("tokenActionHud.dnd5e.activeFeatures"),"passive-features":o.i18n("tokenActionHud.dnd5e.passiveFeatures")};for(const t of this.featureSubcategoryIds){if(!s.has(t))continue;const i={id:t,name:a[t]??"",type:"system"},n=s.get(t);this._buildActions(n,i,e),a[t]&&this.buildActivations(n,i,e)}}_buildInventory(){if(!this.inventorySubcategoryIds)return;if(0===this.items.size)return;const e=new Map;for(const[t,s]of this.items){const i=s.system.equipped,n=s.system?.quantity>0,a=this._isActiveItem(s),l=this._isUsableItem(s),o=this._isEquippedItem(s),c=s.type;n&&a&&(i&&(e.has("equipped")||e.set("equipped",new Map),e.get("equipped").set(t,s)),i||(e.has("unequipped")||e.set("unequipped",new Map),e.get("unequipped").set(t,s)),l&&"consumable"===c&&(e.has("consumables")||e.set("consumables",new Map),e.get("consumables").set(t,s)),o&&("backpack"===c&&(e.has("containers")||e.set("containers",new Map),e.get("containers").set(t,s)),"equipment"===c&&(e.has("equipment")||e.set("equipment",new Map),e.get("equipment").set(t,s)),"loot"===c&&(e.has("loot")||e.set("loot",new Map),e.get("loot").set(t,s)),"tool"===c&&(e.has("tools")||e.set("tools",new Map),e.get("tools").set(t,s)),"weapon"===c&&(e.has("weapons")||e.set("weapons",new Map),e.get("weapons").set(t,s))))}const t={equipped:o.i18n("DND5E.Equipped"),unequipped:o.i18n("DND5E.Unequipped"),consumables:o.i18n("ITEM.TypeConsumablePl"),containers:o.i18n("ITEM.TypeContainerPl"),equipment:o.i18n("ITEM.TypeEquipmentPl"),loot:o.i18n("ITEM.TypeLootPl"),tools:o.i18n("ITEM.TypeToolPl"),weapons:o.i18n("ITEM.TypeWeaponPl")};for(const s of this.inventorySubcategoryIds){if(!e.has(s))continue;const i={id:s,name:t[s],type:"system"},n=e.get(s);this._buildActions(n,i),this.activationSubcategoryIds&&this.buildActivations(n,i)}}_buildRests(){if(!this.subcategoryIds.includes("rests"))return;if(!this.actors.every((e=>"character"===e.type)))return;const e="utility",t={shortRest:{name:o.i18n("DND5E.ShortRest")},longRest:{name:o.i18n("DND5E.LongRest")}},s=Object.entries(t).map((t=>{const s=t[0],i=t[1].name,n=[e,this.actorId,this.tokenId,s].join(this.delimiter);return{id:s,name:i,encodedValue:n,selected:!0}}));this.addActionsToActionList(s,{id:"rests",type:"system"})}_buildSkills(){if(!this.subcategoryIds.includes("skills"))return;const e="skill",t="multi"===this.actorId?game.dnd5e.config.skills:this.actor.system.skills;if(0===t.length)return;const s=Object.entries(t).map((s=>{try{const i=s[0],n=i.charAt(0).toUpperCase()+i.slice(1),a=this.abbreviateSkills?n:game.dnd5e.config.skills[i].label,l=[e,this.actorId,this.tokenId,i].join(this.delimiter);return{id:i,name:a,encodedValue:l,icon:this._getProficiencyIcon(t[i].value)}}catch(e){return c.error(s),null}})).filter((e=>!!e));this.addActionsToActionList(s,{id:"skills",type:"system"})}_buildSpells(){if(!this.spellSubcategoryIds)return;const e="spell",t=new Map;for(const[e,s]of this.items){if("spell"===s.type){const i=this._isUsableItem(s),n=this._isUsableSpell(s);if(i&&n){switch(s.system.preparation.mode){case"atwill":t.has("at-will-spells")||t.set("at-will-spells",new Map),t.get("at-will-spells").set(e,s);break;case"innate":t.has("innate-spells")||t.set("innate-spells",new Map),t.get("innate-spells").set(e,s);break;case"pact":t.has("pact-spells")||t.set("pact-spells",new Map),t.get("pact-spells").set(e,s);break;default:switch(s.system.level){case 0:t.has("cantrips")||t.set("cantrips",new Map),t.get("cantrips").set(e,s);break;case 1:t.has("1st-level-spells")||t.set("1st-level-spells",new Map),t.get("1st-level-spells").set(e,s);break;case 2:t.has("2nd-level-spells")||t.set("2nd-level-spells",new Map),t.get("2nd-level-spells").set(e,s);break;case 3:t.has("3rd-level-spells")||t.set("3rd-level-spells",new Map),t.get("3rd-level-spells").set(e,s);break;case 4:t.has("4th-level-spells")||t.set("4th-level-spells",new Map),t.get("4th-level-spells").set(e,s);break;case 5:t.has("5th-level-spells")||t.set("5th-level-spells",new Map),t.get("5th-level-spells").set(e,s);break;case 6:t.has("6th-level-spells")||t.set("6th-level-spells",new Map),t.get("6th-level-spells").set(e,s);break;case 7:t.has("7th-level-spells")||t.set("7th-level-spells",new Map),t.get("7th-level-spells").set(e,s);break;case 8:t.has("8th-level-spells")||t.set("8th-level-spells",new Map),t.get("8th-level-spells").set(e,s);break;case 9:t.has("9th-level-spells")||t.set("9th-level-spells",new Map),t.get("9th-level-spells").set(e,s)}}}}}const s=Object.entries(this.actor.system.spells).reverse();let i=null;const n=[];let a=this.showUnchargedItems,l=this.showUnchargedItems;for(const[e,t]of s){const s=t.value>0,o=t.max>0,c=t.level>0;"pact"===e&&(!l&&s&&o&&c&&(l=!0),c||(l=!1),t.slotAvailable=l,i=[e,t]),e.startsWith("spell")&&"spell0"!==e?(!a&&s&&o&&(a=!0),t.slotAvailable=a,n.push([e,t])):s&&(t.slotsAvailable=!0,n.push(e,t))}if(i[1].slotAvailable){const e=n.findIndex((e=>e[0]==="spell"+i[1].level));n[e][1].slotsAvailable=!0}const c={"1st-level-spells":{spellMode:1,name:o.i18n("tokenActionHud.dnd5e.1stLevelSpells")},"2nd-level-spells":{spellMode:2,name:o.i18n("tokenActionHud.dnd5e.2ndLevelSpells")},"3rd-level-spells":{spellMode:3,name:o.i18n("tokenActionHud.dnd5e.3rdLevelSpells")},"4th-level-spells":{spellMode:4,name:o.i18n("tokenActionHud.dnd5e.4thLevelSpells")},"5th-level-spells":{spellMode:5,name:o.i18n("tokenActionHud.dnd5e.5thLevelSpells")},"6th-level-spells":{spellMode:6,name:o.i18n("tokenActionHud.dnd5e.6thLevelSpells")},"7th-level-spells":{spellMode:7,name:o.i18n("tokenActionHud.dnd5e.7thLevelSpells")},"8th-level-spells":{spellMode:8,name:o.i18n("tokenActionHud.dnd5e.8thLevelSpells")},"9th-level-spells":{spellMode:9,name:o.i18n("tokenActionHud.dnd5e.9thLevelSpells")},"at-will-spells":{spellMode:"atwill",name:o.i18n("tokenActionHud.dnd5e.atWillSpells")},cantrips:{spellMode:0,name:o.i18n("tokenActionHud.dnd5e.cantrips")},"innate-spells":{spellMode:"innate",name:o.i18n("tokenActionHud.dnd5e.innateSpells")},"pact-spells":{spellMode:"pact",name:o.i18n("tokenActionHud.dnd5e.pactSpells")}},d=["1","2","3","4","5","6","7","8","9","pact"];for(const s of this.spellSubcategoryIds){const a=c[s].spellMode,l=c[s].name;if(!t.has(s))continue;const o="pact"===a?i[1]:n.find((e=>e[0]===`spell${a}`))?.[1],r=o?.value,u=o?.max,p=o?.slotAvailable;if(!p&&d.includes(a))continue;const m={};m.info1={class:"tah-spotlight",text:u>=0?`${r}/${u}`:""};const g={id:s,name:l,type:"system",info:m};this.addSubcategoryInfo(g);const h=t.get(s);this._buildActions(h,g,e),this.activationSubcategoryIds&&this.buildActivations(h,g,e)}}_buildUtility(){if(!this.subcategoryIds.includes("utility"))return;if(!this.actors.every((e=>"character"===e.type)))return;const e="utility",t={deathSave:{name:o.i18n("DND5E.DeathSave")},inspiration:{name:o.i18n("DND5E.Inspiration")}};("multi"===this.actorId||this.actor.system.attributes.hp.value>0)&&delete t.deathSave;const s=Object.entries(t).map((t=>{const s=t[0],i=t[1].name,n=[e,this.actorId,this.tokenId,s].join(this.delimiter);let a="";if("inspiration"===t[0]){a=`toggle${this.actors.every((e=>e.system.attributes?.inspiration))?" active":""}`}return{id:s,name:i,encodedValue:n,cssClass:a,selected:!0}}));this.addActionsToActionList(s,{id:"utility",type:"system"})}_buildActions(e,t,s="item"){if(0===e.size)return;if(!("string"==typeof t?t:t?.id))return;const i=[...e].map((e=>this._getAction(s,e[1])));this.addActionsToActionList(i,t)}_getAction(e,t){const s=t.id??t._id;let i=t?.name??t?.label;t?.system?.recharge&&!t?.system?.recharge?.charged&&t?.system?.recharge?.value&&(i+=` (${o.i18n("DND5E.Recharge")})`);let n="";if(Object.hasOwn(t,"disabled")){n=`toggle${t.disabled?"":" active"}`}const a=[e,this.actorId,this.tokenId,s].join(this.delimiter),l=o.getImage(t),c=this._getActivationTypeIcon(t?.system?.activation?.type);let d=null,r=null;"spell"===t.type?(d=this._getPreparedIcon(t),this.displaySpellInfo&&(r=this._getSpellInfo(t))):r=this._getItemInfo(t);const u=r?.info1,p=r?.info2,m=r?.info3;return{id:s,name:i,encodedValue:a,cssClass:n,img:l,icon1:c,icon2:d,info1:u,info2:p,info3:m,selected:!0}}_isActiveItem(e){if(this.showItemsWithoutActivationCosts)return!0;const t=Object.keys(game.dnd5e.config.abilityActivationTypes).filter((e=>"none"!==e)),s=e.system.activation,i=s?.type;return!(!s||!t.includes(i))}_isEquippedItem(e){const t=e.type;if(this.showUnequippedItems&&!["consumable","spell","feat"].includes(t))return!0;return!(!e.system.equipped||"consumable"===t)}_isUsableItem(e){if(this.showUnchargedItems)return!0;return!!e.system.uses}_isUsableSpell(e){if("character"!==this.actorType&&this.showUnequippedItems)return!0;const t=e.system.preparation.prepared;if(this.showUnpreparedSpells)return!0;const s=e.system.level,i=Object.keys(game.dnd5e.config.spellPreparationModes).filter((e=>"prepared"!==e)),n=e.system.preparation.mode;return!(0!==s&&!i.includes(n)&&!t)}_getItemInfo(e){return{info1:{text:this._getQuantityData(e)},info2:{text:this._getUsesData(e)},info3:{text:this._getConsumeData(e)}}}_getSpellInfo(e){const t=e.system.components,s=[],i={},n={},a={};if(t?.vocal&&s.push(o.i18n("DND5E.ComponentVerbal")),t?.somatic&&s.push(o.i18n("DND5E.ComponentSomatic")),t?.material&&s.push(o.i18n("DND5E.ComponentMaterial")),s.length&&(i.title=s.join(", "),i.text=s.map((e=>e.charAt(0).toUpperCase())).join("")),t?.concentration){const e=o.i18n("DND5E.Concentration");n.title=e,n.text=e.charAt(0).toUpperCase()}if(t?.ritual){const e=o.i18n("DND5E.Ritual");a.title=e,a.text=e.charAt(0).toUpperCase()}return{info1:i,info2:n,info3:a}}_getActors(){const e=["character","npc"],t=canvas.tokens.controlled.map((e=>e.actor));if(t.every((t=>e.includes(t.type))))return t}_getQuantityData(e){const t=e?.system?.quantity??0;return t>1?t:""}_getUsesData(e){const t=e?.system?.uses;return t&&(t.value>0||t.max>0)?`${t.value??"0"}${t.max>0?`/${t.max}`:""}`:""}_getConsumeData(e){const t=e?.system?.consume?.target,s=e?.system?.consume?.type;if("attribute"===s){const e=t.substr(0,t.lastIndexOf(".")),s=this.actor.system[e];return s?`${s.value??"0"}${s.max?`/${s.max}`:""}`:""}const i=this.items.get(t);if("charges"===s){const e=i?.system.uses;return e?.value?`${e.value}${e.max?`/${e.max}`:""}`:""}return i?.system?.quantity??""}_discardSlowItems(e){if(Utils.getSetting("showSlowActions"))return e;const t=["minute","hour","day"],s=new Map;for(const[i,n]of e.entries()){const e=n.system.activation,a=n.system.activation?.type;e&&!t.includes(a)&&s.set(i,n)}return s}_getProficiencyIcon(e){const t=CONFIG.DND5E.proficiencyLevels[e]??"",s=y[e];if(s)return``}_getActivationTypeIcon(e){const t=CONFIG.DND5E.abilityActivationTypes[e]??"",s=p[e];if(s)return``}_getPreparedIcon(e){const t=e.system.level,s=e.system.preparation.mode,i=e.system.preparation.prepared,n=i?"fas fa-sun":"fas fa-sun tah-icon-disabled",a=i?o.i18n("DND5E.SpellPrepared"):o.i18n("DND5E.SpellUnprepared");return"prepared"===s&&0!==t?``:""}}function register(e){game.settings.register(d.ID,"abbreviateSkills",{name:game.i18n.localize("tokenActionHud.dnd5e.settings.abbreviateSkills.name"),hint:game.i18n.localize("tokenActionHud.dnd5e.settings.abbreviateSkills.hint"),scope:"client",config:!0,type:Boolean,default:!1,onChange:t=>{e(t)}}),game.settings.register(d.ID,"showSlowActions",{name:game.i18n.localize("tokenActionHud.dnd5e.settings.showSlowActions.name"),hint:game.i18n.localize("tokenActionHud.dnd5e.settings.showSlowActions.hint"),scope:"client",config:!0,type:Boolean,default:!0,onChange:t=>{e(t)}}),game.settings.register(d.ID,"displaySpellInfo",{name:game.i18n.localize("tokenActionHud.dnd5e.settings.displaySpellInfo.name"),hint:game.i18n.localize("tokenActionHud.dnd5e.settings.displaySpellInfo.hint"),scope:"client",config:!0,type:Boolean,default:!0,onChange:t=>{e(t)}}),game.settings.register(d.ID,"showUnchargedItems",{name:game.i18n.localize("tokenActionHud.dnd5e.settings.showUnchargedItems.name"),hint:game.i18n.localize("tokenActionHud.dnd5e.settings.showUnchargedItems.hint"),scope:"client",config:!0,type:Boolean,default:!1,onChange:t=>{e(t)}}),game.settings.register(d.ID,"showUnequippedItems",{name:game.i18n.localize("tokenActionHud.dnd5e.settings.showUnequippedItems.name"),hint:game.i18n.localize("tokenActionHud.dnd5e.settings.showUnequippedItems.hint"),scope:"client",config:!0,type:Boolean,default:!0,onChange:t=>{e(t)}}),game.settings.register(d.ID,"showUnpreparedSpells",{name:game.i18n.localize("tokenActionHud.dnd5e.settings.showUnpreparedSpells.name"),hint:game.i18n.localize("tokenActionHud.dnd5e.settings.showUnpreparedSpells.hint"),scope:"client",config:!0,type:Boolean,default:!0,onChange:t=>{e(t)}}),game.settings.register(d.ID,"showItemsWithoutActivationCosts",{name:game.i18n.localize("tokenActionHud.dnd5e.settings.showItemsWithoutActivationCosts.name"),hint:game.i18n.localize("tokenActionHud.dnd5e.settings.showItemsWithoutActivationCosts.hint"),scope:"client",config:!0,type:Boolean,default:!1,onChange:t=>{e(t)}})}class SystemManager extends l{doGetCategoryManager(){return new i}doGetActionHandler(e){const t=new ActionHandler(e);return o.isModuleActive("magicitems")&&t.addFurtherActionHandler(new MagicItemActionListExtender(t)),t}getAvailableRollHandlers(){let e="Core D&D5e";o.isModuleActive("midi-qol")&&(e+=` [supports ${o.getModuleTitle("midi-qol")}]`);const t={core:e};return l.addHandler(t,"obsidian"),t}doGetRollHandler(e){let t;if("obsidian"===e)t=new RollHandlerObsidian;else t=new RollHandler;return t}doRegisterSettings(e){register(e)}async doRegisterDefaultFlags(){const e=b;return game.modules.get("magicitems")?.active&&(e.subcategories.push({id:"magic-items",name:o.i18n("tokenActionHud.dnd5e.magicItems"),type:"system",hasDerivedSubcategories:!0}),e.subcategories.sort(((e,t)=>e.id.localeCompare(t.id)))),e}}Hooks.once("ready",(async()=>{const e=game.modules.get(d.ID);e.api={requiredCoreModuleVersion:"1.2",SystemManager:SystemManager},Hooks.call("tokenActionHudSystemReady",e)}));export{p as ACTIVATION_TYPE_ICON,ActionHandler,m as CONCENTRATION_ICON,r as CORE_MODULE,t as CoreActionHandler,s as CoreActionListExtender,i as CoreCategoryManager,n as CorePreRollHandler,a as CoreRollHandler,l as CoreSystemManager,o as CoreUtils,b as DEFAULTS,c as Logger,d as MODULE,MagicItemActionListExtender,g as PREPARED_ICON,y as PROFICIENCY_LEVEL_ICON,u as REQUIRED_CORE_MODULE_VERSION,h as RITUAL_ICON,RollHandler,RollHandlerObsidian,SystemManager,Utils,register}; +const e={ID:"token-action-hud-dnd5e"},t={ID:"token-action-hud-core"},s="1.3",i={ability:"DND5E.Ability",check:"tokenActionHud.dnd5e.check",condition:"tokenActionHud.dnd5e.condition",effect:"DND5E.Effect",feature:"ITEM.TypeFeat",item:"tokenActionHud.dnd5e.item",save:"DND5E.ActionSave",skill:"tokenActionHud.dnd5e.skill",spell:"ITEM.TypeSpell",utility:"DND5E.ActionUtil"},n={bonus:"fas fa-plus",crew:"fas fa-users",day:"fas fa-hourglass-end",hour:"fas fa-hourglass-half",lair:"fas fa-home",minute:"fas fa-hourglass-start",legendary:"fas fas fa-dragon",reaction:"fas fa-bolt",special:"fas fa-star"},a="fas fa-circle-c",l="fas fa-sun",o="fas fa-circle-r",c={.5:"fas fa-adjust",1:"fas fa-check",2:"fas fa-check-double"},r={_1stLevelSpells:{id:"1st-level-spells",name:"tokenActionHud.dnd5e.1stLevelSpells",type:"system",hasDerivedSubcategories:!1},_2ndLevelSpells:{id:"2nd-level-spells",name:"tokenActionHud.dnd5e.2ndLevelSpells",type:"system",hasDerivedSubcategories:!1},_3rdLevelSpells:{id:"3rd-level-spells",name:"tokenActionHud.dnd5e.3rdLevelSpells",type:"system",hasDerivedSubcategories:!1},_4thLevelSpells:{id:"4th-level-spells",name:"tokenActionHud.dnd5e.4thLevelSpells",type:"system",hasDerivedSubcategories:!1},_5thLevelSpells:{id:"5th-level-spells",name:"tokenActionHud.dnd5e.5thLevelSpells",type:"system",hasDerivedSubcategories:!1},_6thLevelSpells:{id:"6th-level-spells",name:"tokenActionHud.dnd5e.6thLevelSpells",type:"system",hasDerivedSubcategories:!1},_7thLevelSpells:{id:"7th-level-spells",name:"tokenActionHud.dnd5e.7thLevelSpells",type:"system",hasDerivedSubcategories:!1},_8thLevelSpells:{id:"8th-level-spells",name:"tokenActionHud.dnd5e.8thLevelSpells",type:"system",hasDerivedSubcategories:!1},_9thLevelSpells:{id:"9th-level-spells",name:"tokenActionHud.dnd5e.9thLevelSpells",type:"system",hasDerivedSubcategories:!1},abilities:{id:"abilities",name:"tokenActionHud.dnd5e.abilities",type:"system",hasDerivedSubcategories:!1},actions:{id:"actions",name:"DND5E.ActionPl",type:"system",hasDerivedSubcategories:!0},activeFeatures:{id:"active-features",name:"tokenActionHud.dnd5e.activeFeatures",type:"system",hasDerivedSubcategories:!1},artificerInfusions:{id:"artificer-infusions",name:"tokenActionHud.dnd5e.artificerInfusions",type:"system",hasDerivedSubcategories:!1},atWillSpells:{id:"at-will-spells",name:"tokenActionHud.dnd5e.atWillSpells",type:"system",hasDerivedSubcategories:!1},backgroundFeatures:{id:"background-features",name:"tokenActionHud.dnd5e.backgroundFeatures",type:"system",hasDerivedSubcategories:!1},bonusActions:{id:"bonus-actions",name:"tokenActionHud.dnd5e.bonusActions",type:"system",hasDerivedSubcategories:!0},cantrips:{id:"cantrips",name:"tokenActionHud.dnd5e.cantrips",type:"system",hasDerivedSubcategories:!1},channelDivinity:{id:"channel-divinity",name:"tokenActionHud.dnd5e.channelDivinity",type:"system",hasDerivedSubcategories:!1},checks:{id:"checks",name:"tokenActionHud.dnd5e.checks",type:"system",hasDerivedSubcategories:!1},classFeatures:{id:"class-features",name:"tokenActionHud.dnd5e.classFeatures",type:"system",hasDerivedSubcategories:!1},combat:{id:"combat",name:"tokenActionHud.combat",type:"system",hasDerivedSubcategories:!1},conditions:{id:"conditions",name:"tokenActionHud.dnd5e.conditions",type:"system",hasDerivedSubcategories:!1},consumables:{id:"consumables",name:"ITEM.TypeConsumablePl",type:"system",hasDerivedSubcategories:!1},containers:{id:"containers",name:"ITEM.TypeContainerPl",type:"system",hasDerivedSubcategories:!1},crewActions:{id:"crew-actions",name:"tokenActionHud.dnd5e.crewActions",type:"system",hasDerivedSubcategories:!0},defensiveTactics:{id:"defensive-tactics",name:"tokenActionHud.dnd5e.defensiveTactics",type:"system",hasDerivedSubcategories:!1},eldritchInvocations:{id:"eldritch-invocations",name:"tokenActionHud.dnd5e.eldritchInvocations",type:"system",hasDerivedSubcategories:!1},elementalDisciplines:{id:"elemental-disciplines",name:"tokenActionHud.dnd5e.elementalDisciplines",type:"system",hasDerivedSubcategories:!1},equipment:{id:"equipment",name:"ITEM.TypeEquipmentPl",type:"system",hasDerivedSubcategories:!1},equipped:{id:"equipped",name:"DND5E.Equipped",type:"system",hasDerivedSubcategories:!1},feats:{id:"feats",name:"tokenActionHud.dnd5e.feats",type:"system",hasDerivedSubcategories:!1},fightingStyles:{id:"fighting-styles",name:"tokenActionHud.dnd5e.fightingStyles",type:"system",hasDerivedSubcategories:!1},huntersPrey:{id:"hunters-prey",name:"tokenActionHud.dnd5e.huntersPrey",type:"system",hasDerivedSubcategories:!1},innateSpells:{id:"innate-spells",name:"tokenActionHud.dnd5e.innateSpells",type:"system",hasDerivedSubcategories:!1},kiAbilities:{id:"ki-abilities",name:"tokenActionHud.dnd5e.kiAbilities",type:"system",hasDerivedSubcategories:!1},lairActions:{id:"lair-actions",name:"tokenActionHud.dnd5e.lairActions",type:"system",hasDerivedSubcategories:!0},legendaryActions:{id:"legendary-actions",name:"tokenActionHud.dnd5e.legendaryActions",type:"system",hasDerivedSubcategories:!0},loot:{id:"loot",name:"ITEM.TypeLootPl",type:"system",hasDerivedSubcategories:!1},maneuvers:{id:"maneuvers",name:"tokenActionHud.dnd5e.maneuvers",type:"system",hasDerivedSubcategories:!1},metamagicOptions:{id:"metamagic-options",name:"tokenActionHud.dnd5e.metamagicOptions",type:"system",hasDerivedSubcategories:!1},monsterFeatures:{id:"monster-features",name:"tokenActionHud.dnd5e.monsterFeatures",type:"system",hasDerivedSubcategories:!1},multiattacks:{id:"multiattacks",name:"tokenActionHud.dnd5e.multiattacks",type:"system",hasDerivedSubcategories:!1},otherActions:{id:"other-actions",name:"tokenActionHud.dnd5e.otherActions",type:"system",hasDerivedSubcategories:!0},pactBoons:{id:"pact-boons",name:"tokenActionHud.dnd5e.pactBoons",type:"system",hasDerivedSubcategories:!1},pactSpells:{id:"pact-spells",name:"tokenActionHud.dnd5e.pactSpells",type:"system",hasDerivedSubcategories:!1},passiveEffects:{id:"passive-effects",name:"DND5E.EffectPassive",type:"system",hasDerivedSubcategories:!1},passiveFeatures:{id:"passive-features",name:"tokenActionHud.dnd5e.passiveFeatures",type:"system",hasDerivedSubcategories:!1},psionicPowers:{id:"psionic-powers",name:"tokenActionHud.dnd5e.psionicPowers",type:"system",hasDerivedSubcategories:!1},raceFeatures:{id:"race-features",name:"tokenActionHud.dnd5e.raceFeatures",type:"system",hasDerivedSubcategories:!1},reactions:{id:"reactions",name:"DND5E.ReactionPl",type:"system",hasDerivedSubcategories:!0},rests:{id:"rests",name:"tokenActionHud.dnd5e.rests",type:"system",hasDerivedSubcategories:!1},runes:{id:"runes",name:"tokenActionHud.dnd5e.runes",type:"system",hasDerivedSubcategories:!1},saves:{id:"saves",name:"DND5E.ClassSaves",type:"system",hasDerivedSubcategories:!1},skills:{id:"skills",name:"tokenActionHud.dnd5e.skills",type:"system",hasDerivedSubcategories:!1},superiorHuntersDefense:{id:"superior-hunters-defense",name:"tokenActionHud.dnd5e.superiorHuntersDefense",type:"system",hasDerivedSubcategories:!1},temporaryEffects:{id:"temporary-effects",name:"DND5E.EffectTemporary",type:"system",hasDerivedSubcategories:!1},token:{id:"token",name:"tokenActionHud.token",type:"system",hasDerivedSubcategories:!1},tools:{id:"tools",name:"ITEM.TypeToolPl",type:"system",hasDerivedSubcategories:!1},unequipped:{id:"unequipped",name:"DND5E.Unequipped",type:"system",hasDerivedSubcategories:!1},utility:{id:"utility",name:"tokenActionHud.utility",type:"system",hasDerivedSubcategories:!1},weapons:{id:"weapons",name:"ITEM.TypeWeaponPl",type:"system",hasDerivedSubcategories:!1}};let d=null;Hooks.once("tokenActionHudCoreApiReady",(async t=>{d=class Utils{static getSetting(s,i=null){let n=i??null;try{n=game.settings.get(e.ID,s)}catch{t.api.Logger.debug(`Setting '${s}' not found`)}return n}static async setSetting(s,i){try{i=await game.settings.set(e.ID,s,i),t.api.Logger.debug(`Setting '${s}' set to '${i}'`)}catch{t.api.Logger.debug(`Setting '${s}' not found`)}}}}));let p=null;Hooks.once("tokenActionHudCoreApiReady",(async e=>{p=class ActionHandler extends e.api.ActionHandler{actors=null;tokens=null;actorType=null;items=null;abbreviateSkills=null;displaySpellInfo=null;showItemsWithoutActivationCosts=null;showUnchargedItems=null;showUnequippedItems=null;showUnpreparedSpells=null;activationSubcategoryIds=null;featureSubcategoryIds=null;inventorySubcategoryIds=null;spellSubcategoryIds=null;featureActions=null;inventoryActions=null;spellActions=null;async buildSystemActions(e){if(this.actors=this.actor?[this.actor]:this._getActors(),this.tokens=this.token?[this.token]:this._getTokens(),this.actorType=this.actor?.type,this.actor){let e=this.actor.items;e=this._discardSlowItems(e),e=this.sortItemsByName(e),this.items=e}this.abbreviateSkills=d.getSetting("abbreviateSkills"),this.displaySpellInfo=d.getSetting("displaySpellInfo"),this.showItemsWithoutActivationCosts=d.getSetting("showItemsWithoutActivationCosts"),this.showUnchargedItems=d.getSetting("showUnchargedItems"),this.showUnequippedItems=d.getSetting("showUnequippedItems"),this.showUnpreparedSpells=d.getSetting("showUnpreparedSpells"),this.activationSubcategoryIds=["actions","bonus-actions","crew-actions","lair-actions","legendary-actions","reactions","other-actions"],this.featureSubcategoryIds=["active-features","passive-features","background-features","class-features","feats","monster-features","race-features","artificer-infusions","channel-divinity","defensive-tactics","eldritch-invocations","elemental-disciplines","fighting-styles","hunters-prey","ki-abilities","maneuvers","metamagic-options","multiattacks","pact-boons","psionic-powers","runes","superior-hunters-defense"],this.spellSubcategoryIds=["cantrips","1st-level-spells","2nd-level-spells","3rd-level-spells","4th-level-spells","5th-level-spells","6th-level-spells","7th-level-spells","8th-level-spells","9th-level-spells","at-will-spells","innate-spells","pact-spells"],"character"!==this.actorType&&"npc"!==this.actorType||(this.inventorySubcategoryIds=["equipped","consumables","containers","equipment","loot","tools","weapons","unequipped"],this._buildCharacterActions()),"vehicle"===this.actorType&&(this.inventorySubcategoryIds=["consumables","equipment","tools","weapons"],this._buildVehicleActions()),this.actor||this._buildMultipleTokenActions()}async _buildCharacterActions(){this._buildAbilities("ability","abilities"),this._buildAbilities("check","checks"),this._buildAbilities("save","saves"),this._buildCombat(),this._buildConditions(),this._buildEffects(),this._buildFeatures(),this._buildInventory(),this._buildRests(),this._buildSkills(),this._buildSpells(),this._buildUtility()}async _buildVehicleActions(){this._buildAbilities("ability","abilities"),this._buildAbilities("check","checks"),this._buildAbilities("save","saves"),this._buildCombat(),this._buildConditions(),this._buildEffects(),this._buildFeatures(),this._buildInventory(),this._buildUtility()}async _buildMultipleTokenActions(){this._buildAbilities("ability","abilities"),this._buildAbilities("check","checks"),this._buildAbilities("save","saves"),this._buildCombat(),this._buildConditions(),this._buildRests(),this._buildSkills(),this._buildUtility()}_buildAbilities(t,s){const n=this.actor?this.actor.system.abilities:game.dnd5e.config.abilities;if(0===n.length)return;const a=Object.entries(n).filter((e=>0!==n[e[0]].value)).map((a=>{const l=a[0],o=`${t}-${a[0]}`,c=l.charAt(0).toUpperCase()+l.slice(1),r=this.abbreviateSkills?c:game.dnd5e.config.abilities[l],d=`${`${e.api.Utils.i18n(i[t])}: `??""}${game.dnd5e.config.abilities[l]}`;return{id:o,name:r,encodedValue:[t,l].join(this.delimiter),icon:"checks"!==s?this._getProficiencyIcon(n[l].proficient):"",listName:d}})),l={id:s,type:"system"};this.addActionsToActionList(a,l)}async buildActivations(e,t,s="item"){const i=new Map,n={actions:"action","bonus-actions":"bonus","crew-actions":"crew","lair-actions":"lair","legendary-actions":"legendary",reactions:"reaction","other-actions":"other"},a=["action","bonus","crew","lair","legendary","reaction"];for(const[t,s]of e){const e=s.system?.activation?.type,n=a.includes(e)?e:"other";i.has(n)||i.set(n,new Map),i.get(n).set(t,s)}for(const e of this.activationSubcategoryIds){const a=n[e];if(!i.has(a))continue;const l={...t,id:`${e}+${t.id}`,type:"system-derived"},o={id:e,type:"system"};await this.addSubcategoryToActionList(o,l),this.addSubcategoryInfo(t),this._buildActions(i.get(a),l,s)}}_buildCombat(){const t="utility",s={initiative:{id:"initiative",name:e.api.Utils.i18n("tokenActionHud.dnd5e.rollInitiative")},endTurn:{id:"endTurn",name:e.api.Utils.i18n("tokenActionHud.endTurn")}};game.combat?.current?.tokenId!==this.token?.id&&delete s.endTurn;const n=Object.entries(s).map((s=>{const n=s[1].id,a=s[1].name,l=`${`${e.api.Utils.i18n(i[t])}: `??""}${a}`,o=[t,n].join(this.delimiter),c={};let r="";if("initiative"===s[0]&&game.combat){const e=canvas.tokens.controlled.map((e=>e.id)),t=game.combat.combatants.filter((t=>e.includes(t.tokenId)));if(1===t.length){const e=t[0].initiative;c.class="tah-spotlight",c.text=e}r=`toggle${t.length>0&&t.every((e=>e?.initiative))?" active":""}`}return{id:n,name:a,encodedValue:o,info1:c,cssClass:r,listName:l}}));this.addActionsToActionList(n,{id:"combat",type:"system"})}_buildConditions(){if(!this.token&&0===this.tokens.length)return;const t="condition",s=CONFIG.statusEffects.filter((e=>""!==e.id));if(0===s.length)return;const n=s.map((s=>{const n=s.id,a=e.api.Utils.i18n(s.label),l=`${`${e.api.Utils.i18n(i[t])}: `??""}${a}`,o=[t,n].join(this.delimiter),c=`toggle${this.actors.every((e=>e.effects.map((e=>e.flags?.core?.statusId)).some((e=>e===n))))?" active":""}`,r=e.api.Utils.getImage(s);return{id:n,name:a,encodedValue:o,img:r,cssClass:c,listName:l}}));this.addActionsToActionList(n,{id:"conditions",type:"system"})}_buildEffects(){const e="effect",t=this.actor.effects;if(0===t.size)return;const s=new Map,i=new Map;for(const e of t){const t=e.id;e.isTemporary?i.set(t,e):s.set(t,e)}this._buildActions(s,{id:"passive-effects",type:"system"},e),this._buildActions(i,{id:"temporary-effects",type:"system"},e)}_buildFeatures(){const t="feature",s=new Map;for(const[e,t]of this.items){"feat"===t.type&&s.set(e,t)}if(0===s.size)return;const i=new Map,n=[{type:"background",subcategoryId:"background-features"},{type:"class",subcategoryId:"class-features"},{type:"monster",subcategoryId:"monster-features"},{type:"race",subcategoryId:"race-features"},{type:"feats",subcategoryId:"feats"}],a=[{type:"artificerInfusion",subcategoryId:"artificer-infusions"},{type:"channelDivinity",subcategoryId:"channel-divinity"},{type:"defensiveTactic",subcategoryId:"defensive-tactics"},{type:"eldritchInvocation",subcategoryId:"eldritch-invocations"},{type:"elementalDiscipline",subcategoryId:"elemental-disciplines"},{type:"fightingStyle",subcategoryId:"fighting-styles"},{type:"huntersPrey",subcategoryId:"hunters-prey"},{type:"ki",subcategoryId:"ki-abilities"},{type:"maneuver",subcategoryId:"maneuvers"},{type:"metamagic",subcategoryId:"metamagic-options"},{type:"multiattack",subcategoryId:"multiattacks"},{type:"pact",subcategoryId:"pact-boons"},{type:"psionicPower",subcategoryId:"psionic-powers"},{type:"rune",subcategoryId:"runes"},{type:"superiorHuntersDefense",subcategoryId:"superior-hunters-defense"}];for(const[e,t]of s){const s=t.system.activation?.type,l=t.system.type.value,o=t.system.type?.subtype,c=["","lair","legendary"];s&&!c.includes(s)&&(i.has("active-features")||i.set("active-features",new Map),i.get("active-features").set(e,t)),s&&""!==s||(i.has("passive-features")||i.set("passive-features",new Map),i.get("passive-features").set(e,t));for(const s of n){const n=s.subcategoryId;s.type===l&&(i.has(n)||i.set(n,new Map),i.get(n).set(e,t))}for(const s of a){const n=s.subcategoryId;o&&s.type===o&&(i.has(n)||i.set(n,new Map),i.get(n).set(e,t))}}const l={"active-features":e.api.Utils.i18n("tokenActionHud.dnd5e.activeFeatures"),"passive-features":e.api.Utils.i18n("tokenActionHud.dnd5e.passiveFeatures")};for(const e of this.featureSubcategoryIds){if(!i.has(e))continue;const s={id:e,name:l[e]??"",type:"system"},n=i.get(e);this._buildActions(n,s,t),l[e]&&this.buildActivations(n,s,t)}}_buildInventory(){if(0===this.items.size)return;const t=new Map;for(const[e,s]of this.items){const i=s.system.equipped,n=s.system?.quantity>0,a=this._isActiveItem(s),l=this._isUsableItem(s),o=this._isEquippedItem(s),c=s.type;n&&a&&(i&&(t.has("equipped")||t.set("equipped",new Map),t.get("equipped").set(e,s)),i||(t.has("unequipped")||t.set("unequipped",new Map),t.get("unequipped").set(e,s)),l&&"consumable"===c&&(t.has("consumables")||t.set("consumables",new Map),t.get("consumables").set(e,s)),o&&("backpack"===c&&(t.has("containers")||t.set("containers",new Map),t.get("containers").set(e,s)),"equipment"===c&&(t.has("equipment")||t.set("equipment",new Map),t.get("equipment").set(e,s)),"loot"===c&&(t.has("loot")||t.set("loot",new Map),t.get("loot").set(e,s)),"tool"===c&&(t.has("tools")||t.set("tools",new Map),t.get("tools").set(e,s)),"weapon"===c&&(t.has("weapons")||t.set("weapons",new Map),t.get("weapons").set(e,s))))}const s={equipped:e.api.Utils.i18n("DND5E.Equipped"),unequipped:e.api.Utils.i18n("DND5E.Unequipped"),consumables:e.api.Utils.i18n("ITEM.TypeConsumablePl"),containers:e.api.Utils.i18n("ITEM.TypeContainerPl"),equipment:e.api.Utils.i18n("ITEM.TypeEquipmentPl"),loot:e.api.Utils.i18n("ITEM.TypeLootPl"),tools:e.api.Utils.i18n("ITEM.TypeToolPl"),weapons:e.api.Utils.i18n("ITEM.TypeWeaponPl")};for(const e of this.inventorySubcategoryIds){if(!t.has(e))continue;const i={id:e,name:s[e],type:"system"},n=t.get(e);this._buildActions(n,i),this.activationSubcategoryIds&&this.buildActivations(n,i)}}_buildRests(){if(!this.actors.every((e=>"character"===e.type)))return;const t="utility",s={shortRest:{name:e.api.Utils.i18n("DND5E.ShortRest")},longRest:{name:e.api.Utils.i18n("DND5E.LongRest")}},n=Object.entries(s).map((s=>{const n=s[0],a=s[1].name,l=`${`${e.api.Utils.i18n(i[t])}: `??""}${a}`,o=[t,n].join(this.delimiter);return{id:n,name:a,encodedValue:o,listName:l}}));this.addActionsToActionList(n,{id:"rests",type:"system"})}_buildSkills(){const t="skill",s=this.actor?this.actor.system.skills:game.dnd5e.config.skills;if(0===s.length)return;const n=Object.entries(s).map((n=>{try{const a=n[0],l=a.charAt(0).toUpperCase()+a.slice(1),o=this.abbreviateSkills?l:game.dnd5e.config.skills[a].label,c=`${`${e.api.Utils.i18n(i[t])}: `??""}${game.dnd5e.config.skills[a].label}`,r=[t,a].join(this.delimiter);return{id:a,name:o,encodedValue:r,icon:this._getProficiencyIcon(s[a].value),listName:c}}catch(t){return e.api.Logger.error(n),null}})).filter((e=>!!e));this.addActionsToActionList(n,{id:"skills",type:"system"})}_buildSpells(){const t="spell",s=new Map;for(const[e,t]of this.items){if("spell"===t.type){const i=this._isUsableItem(t),n=this._isUsableSpell(t);if(i&&n){switch(t.system.preparation.mode){case"atwill":s.has("at-will-spells")||s.set("at-will-spells",new Map),s.get("at-will-spells").set(e,t);break;case"innate":s.has("innate-spells")||s.set("innate-spells",new Map),s.get("innate-spells").set(e,t);break;case"pact":s.has("pact-spells")||s.set("pact-spells",new Map),s.get("pact-spells").set(e,t);break;default:switch(t.system.level){case 0:s.has("cantrips")||s.set("cantrips",new Map),s.get("cantrips").set(e,t);break;case 1:s.has("1st-level-spells")||s.set("1st-level-spells",new Map),s.get("1st-level-spells").set(e,t);break;case 2:s.has("2nd-level-spells")||s.set("2nd-level-spells",new Map),s.get("2nd-level-spells").set(e,t);break;case 3:s.has("3rd-level-spells")||s.set("3rd-level-spells",new Map),s.get("3rd-level-spells").set(e,t);break;case 4:s.has("4th-level-spells")||s.set("4th-level-spells",new Map),s.get("4th-level-spells").set(e,t);break;case 5:s.has("5th-level-spells")||s.set("5th-level-spells",new Map),s.get("5th-level-spells").set(e,t);break;case 6:s.has("6th-level-spells")||s.set("6th-level-spells",new Map),s.get("6th-level-spells").set(e,t);break;case 7:s.has("7th-level-spells")||s.set("7th-level-spells",new Map),s.get("7th-level-spells").set(e,t);break;case 8:s.has("8th-level-spells")||s.set("8th-level-spells",new Map),s.get("8th-level-spells").set(e,t);break;case 9:s.has("9th-level-spells")||s.set("9th-level-spells",new Map),s.get("9th-level-spells").set(e,t)}}}}}const i=Object.entries(this.actor.system.spells).reverse();let n=null;const a=[];let l=this.showUnchargedItems,o=this.showUnchargedItems;for(const[e,t]of i){const s=t.value>0,i=t.max>0,c=t.level>0;"pact"===e&&(!o&&s&&i&&c&&(o=!0),c||(o=!1),t.slotAvailable=o,n=[e,t]),e.startsWith("spell")&&"spell0"!==e?(!l&&s&&i&&(l=!0),t.slotAvailable=l,a.push([e,t])):s&&(t.slotsAvailable=!0,a.push(e,t))}if(n[1].slotAvailable){const e=a.findIndex((e=>e[0]==="spell"+n[1].level));a[e][1].slotsAvailable=!0}const c={"1st-level-spells":{spellMode:1,name:e.api.Utils.i18n("tokenActionHud.dnd5e.1stLevelSpells")},"2nd-level-spells":{spellMode:2,name:e.api.Utils.i18n("tokenActionHud.dnd5e.2ndLevelSpells")},"3rd-level-spells":{spellMode:3,name:e.api.Utils.i18n("tokenActionHud.dnd5e.3rdLevelSpells")},"4th-level-spells":{spellMode:4,name:e.api.Utils.i18n("tokenActionHud.dnd5e.4thLevelSpells")},"5th-level-spells":{spellMode:5,name:e.api.Utils.i18n("tokenActionHud.dnd5e.5thLevelSpells")},"6th-level-spells":{spellMode:6,name:e.api.Utils.i18n("tokenActionHud.dnd5e.6thLevelSpells")},"7th-level-spells":{spellMode:7,name:e.api.Utils.i18n("tokenActionHud.dnd5e.7thLevelSpells")},"8th-level-spells":{spellMode:8,name:e.api.Utils.i18n("tokenActionHud.dnd5e.8thLevelSpells")},"9th-level-spells":{spellMode:9,name:e.api.Utils.i18n("tokenActionHud.dnd5e.9thLevelSpells")},"at-will-spells":{spellMode:"atwill",name:e.api.Utils.i18n("tokenActionHud.dnd5e.atWillSpells")},cantrips:{spellMode:0,name:e.api.Utils.i18n("tokenActionHud.dnd5e.cantrips")},"innate-spells":{spellMode:"innate",name:e.api.Utils.i18n("tokenActionHud.dnd5e.innateSpells")},"pact-spells":{spellMode:"pact",name:e.api.Utils.i18n("tokenActionHud.dnd5e.pactSpells")}},r=["1","2","3","4","5","6","7","8","9","pact"];for(const e of this.spellSubcategoryIds){const i=c[e].spellMode,l=c[e].name;if(!s.has(e))continue;const o="pact"===i?n[1]:a.find((e=>e[0]===`spell${i}`))?.[1],d=o?.value,p=o?.max,u=o?.slotAvailable;if(!u&&r.includes(i))continue;const h={};h.info1={class:"tah-spotlight",text:p>=0?`${d}/${p}`:""};const m={id:e,name:l,type:"system",info:h};this.addSubcategoryInfo(m);const y=s.get(e);this._buildActions(y,m,t),this.activationSubcategoryIds&&this.buildActivations(y,m,t)}}_buildUtility(){if(!this.actors.every((e=>"character"===e.type)))return;const t="utility",s={deathSave:{name:e.api.Utils.i18n("DND5E.DeathSave")},inspiration:{name:e.api.Utils.i18n("DND5E.Inspiration")}};(!this.actor||this.actor.system.attributes.hp.value>0)&&delete s.deathSave;const n=Object.entries(s).map((s=>{const n=s[0],a=s[1].name,l=`${`${e.api.Utils.i18n(i[t])}: `??""}${a}`,o=[t,n].join(this.delimiter);let c="";if("inspiration"===s[0]){c=`toggle${this.actors.every((e=>e.system.attributes?.inspiration))?" active":""}`}return{id:n,name:a,encodedValue:o,cssClass:c,listName:l}}));this.addActionsToActionList(n,{id:"utility",type:"system"})}_buildActions(e,t,s="item"){if(0===e.size)return;if(!("string"==typeof t?t:t?.id))return;const i=[...e].map((e=>this._getAction(s,e[1])));this.addActionsToActionList(i,t)}_getAction(t,s){const n=s.id??s._id;let a=s?.name??s?.label;s?.system?.recharge&&!s?.system?.recharge?.charged&&s?.system?.recharge?.value&&(a+=` (${e.api.Utils.i18n("DND5E.Recharge")})`);const l=`${`${e.api.Utils.i18n(i[t])}: `??""}${a}`;let o="";if(Object.hasOwn(s,"disabled")){o=`toggle${s.disabled?"":" active"}`}const c=[t,n].join(this.delimiter),r=e.api.Utils.getImage(s),d=this._getActivationTypeIcon(s?.system?.activation?.type);let p=null,u=null;"spell"===s.type?(p=this._getPreparedIcon(s),this.displaySpellInfo&&(u=this._getSpellInfo(s))):u=this._getItemInfo(s);const h=u?.info1,m=u?.info2,y=u?.info3;return{id:n,name:a,encodedValue:c,cssClass:o,img:r,icon1:d,icon2:p,info1:h,info2:m,info3:y,listName:l}}_isActiveItem(e){if(this.showItemsWithoutActivationCosts)return!0;const t=Object.keys(game.dnd5e.config.abilityActivationTypes).filter((e=>"none"!==e)),s=e.system.activation,i=s?.type;return!(!s||!t.includes(i))}_isEquippedItem(e){const t=e.type;if(this.showUnequippedItems&&!["consumable","spell","feat"].includes(t))return!0;return!(!e.system.equipped||"consumable"===t)}_isUsableItem(e){if(this.showUnchargedItems)return!0;return!!e.system.uses}_isUsableSpell(e){if("character"!==this.actorType&&this.showUnequippedItems)return!0;const t=e.system.preparation.prepared;if(this.showUnpreparedSpells)return!0;const s=e.system.level,i=Object.keys(game.dnd5e.config.spellPreparationModes).filter((e=>"prepared"!==e)),n=e.system.preparation.mode;return!(0!==s&&!i.includes(n)&&!t)}_getItemInfo(e){return{info1:{text:this._getQuantityData(e)},info2:{text:this._getUsesData(e)},info3:{text:this._getConsumeData(e)}}}_getSpellInfo(t){const s=t.system.components,i=[],n={},a={},l={};if(s?.vocal&&i.push(e.api.Utils.i18n("DND5E.ComponentVerbal")),s?.somatic&&i.push(e.api.Utils.i18n("DND5E.ComponentSomatic")),s?.material&&i.push(e.api.Utils.i18n("DND5E.ComponentMaterial")),i.length&&(n.title=i.join(", "),n.text=i.map((e=>e.charAt(0).toUpperCase())).join("")),s?.concentration){const t=e.api.Utils.i18n("DND5E.Concentration");a.title=t,a.text=t.charAt(0).toUpperCase()}if(s?.ritual){const t=e.api.Utils.i18n("DND5E.Ritual");l.title=t,l.text=t.charAt(0).toUpperCase()}return{info1:n,info2:a,info3:l}}_getActors(){const e=["character","npc"],t=canvas.tokens.controlled.filter((e=>e.actor)).map((e=>e.actor));if(t.every((t=>e.includes(t.type))))return t}_getTokens(){const e=["character","npc"],t=canvas.tokens.controlled;if(t.filter((e=>e.actor)).map((e=>e.actor)).every((t=>e.includes(t.type))))return t}_getQuantityData(e){const t=e?.system?.quantity??0;return t>1?t:""}_getUsesData(e){const t=e?.system?.uses;return t&&(t.value>0||t.max>0)?`${t.value??"0"}${t.max>0?`/${t.max}`:""}`:""}_getConsumeData(e){const t=e?.system?.consume?.target,s=e?.system?.consume?.type;if("attribute"===s){const e=t.substr(0,t.lastIndexOf(".")),s=this.actor.system[e];return s?`${s.value??"0"}${s.max?`/${s.max}`:""}`:""}const i=this.items.get(t);if("charges"===s){const e=i?.system.uses;return e?.value?`${e.value}${e.max?`/${e.max}`:""}`:""}return i?.system?.quantity??""}_discardSlowItems(e){if(d.getSetting("showSlowActions"))return e;const t=["minute","hour","day"],s=new Map;for(const[i,n]of e.entries()){const e=n.system.activation,a=n.system.activation?.type;e&&!t.includes(a)&&s.set(i,n)}return s}_getProficiencyIcon(e){const t=CONFIG.DND5E.proficiencyLevels[e]??"",s=c[e];if(s)return``}_getActivationTypeIcon(e){const t=CONFIG.DND5E.abilityActivationTypes[e]??"",s=n[e];if(s)return``}_getPreparedIcon(t){const s=t.system.level,i=t.system.preparation.mode,n=t.system.preparation.prepared,a=n?"fas fa-sun":"fas fa-sun tah-icon-disabled",l=n?e.api.Utils.i18n("DND5E.SpellPrepared"):e.api.Utils.i18n("DND5E.SpellUnprepared");return"prepared"===i&&0!==s?``:""}}}));let u=null;Hooks.once("tokenActionHudCoreApiReady",(async e=>{u=class MagicItemActionListExtender extends e.api.ActionListExtender{constructor(e){super(e.categoryManager),this.actionHandler=e,this.categoryManager=e.categoryManager,this.actor=null}extendActionList(){if(this.actor=this.actionHandler.actor,!this.actor)return;const t=MagicItems.actor(this.actor.id);if(!t)return;const s=t.items??[];if(0===s.length)return;const i={id:"magic-items",type:"system"};s.forEach((t=>{if(t.attuned&&!this._isItemAttuned(t))return;if(t.equipped&&!this._isItemEquipped(t))return;const s={id:`magic-items_${t.id}`,name:t.name,type:"system-derived",info1:`${t.uses}/${t.charges}`};this.actionHandler.addSubcategoryToActionList(i,s);const n=t.ownedEntries.map((s=>{const i=s.item,n=i.id;return{id:n,name:i.name,encodedValue:["magicItem",`${t.id}>${n}`].join("|"),img:e.api.Utils.getImage(i),info1:i.consumption,info2:i.baseLevel?`${e.api.Utils.i18n("DND5E.AbbreviationLevel")} ${i.baseLevel}`:"",selected:!0}}));this.actionHandler.addActionsToActionList(n,s)}))}_isItemEquipped(e){return e.item.system.equipped}_isItemAttuned(e){return e.item.system.attunment!==(CONFIG.DND5E.attunementTypes?.REQUIRED??1)}}}));let h=null;Hooks.once("tokenActionHudCoreApiReady",(async e=>{const t=r;Object.values(t).forEach((t=>{t.name=e.api.Utils.i18n(t.name),t.listName=`Subcategory: ${e.api.Utils.i18n(t.name)}`}));const s=Object.values(t);h={categories:[{nestId:"inventory",id:"inventory",name:e.api.Utils.i18n("DND5E.Inventory"),subcategories:[{...t.weapons,nestId:"inventory_weapons"},{...t.equipment,nestId:"inventory_equipment"},{...t.consumables,nestId:"inventory_consumables"},{...t.tools,nestId:"inventory_tools"},{...t.containers,nestId:"inventory_containers"},{...t.loot,nestId:"inventory_loot"}]},{nestId:"features",id:"features",name:e.api.Utils.i18n("DND5E.Features"),subcategories:[{...t.activeFeatures,nestId:"features_active-features"},{...t.passiveFeatures,nestId:"features_passive-features"}]},{nestId:"spells",id:"spells",name:e.api.Utils.i18n("ITEM.TypeSpellPl"),subcategories:[{...t.atWillSpells,nestId:"spells_at-will-spells"},{...t.innateSpells,nestId:"spells_innate-spells"},{...t.pactSpells,nestId:"spells_pact-spells"},{...t.cantrips,nestId:"spells_cantrips"},{...t._1stLevelSpells,nestId:"spells_1st-level-spells"},{...t._2ndLevelSpells,nestId:"spells_2nd-level-spells"},{...t._3rdLevelSpells,nestId:"spells_3rd-level-spells"},{...t._4thLevelSpells,nestId:"spells_4th-level-spells"},{...t._5thLevelSpells,nestId:"spells_5th-level-spells"},{...t._6thLevelSpells,nestId:"spells_6th-level-spells"},{...t._7thLevelSpells,nestId:"spells_7th-level-spells"},{...t._8thLevelSpells,nestId:"spells_8th-level-spells"},{...t._9thLevelSpells,nestId:"spells_9th-level-spells"}]},{nestId:"attributes",id:"attributes",name:e.api.Utils.i18n("DND5E.Attributes"),subcategories:[{...t.abilities,nestId:"attributes_abilities"},{...t.skills,nestId:"attributes_skills"}]},{nestId:"effects",id:"effects",name:e.api.Utils.i18n("DND5E.Effects"),subcategories:[{...t.temporaryEffects,nestId:"effects_temporary-effects"},{...t.passiveEffects,nestId:"effects_passive-effects"}]},{nestId:"conditions",id:"conditions",name:e.api.Utils.i18n("tokenActionHud.dnd5e.conditions"),subcategories:[{...t.conditions,nestId:"conditions_conditions"}]},{nestId:"utility",id:"utility",name:e.api.Utils.i18n("tokenActionHud.utility"),subcategories:[{...t.combat,nestId:"utility_combat"},{...t.token,nestId:"utility_token"},{...t.rests,nestId:"utility_rests"},{...t.utility,nestId:"utility_utility"}]}],subcategories:s}}));let m=null;function register(t){game.settings.register(e.ID,"abbreviateSkills",{name:game.i18n.localize("tokenActionHud.dnd5e.settings.abbreviateSkills.name"),hint:game.i18n.localize("tokenActionHud.dnd5e.settings.abbreviateSkills.hint"),scope:"client",config:!0,type:Boolean,default:!1,onChange:e=>{t(e)}}),game.settings.register(e.ID,"showSlowActions",{name:game.i18n.localize("tokenActionHud.dnd5e.settings.showSlowActions.name"),hint:game.i18n.localize("tokenActionHud.dnd5e.settings.showSlowActions.hint"),scope:"client",config:!0,type:Boolean,default:!0,onChange:e=>{t(e)}}),game.settings.register(e.ID,"displaySpellInfo",{name:game.i18n.localize("tokenActionHud.dnd5e.settings.displaySpellInfo.name"),hint:game.i18n.localize("tokenActionHud.dnd5e.settings.displaySpellInfo.hint"),scope:"client",config:!0,type:Boolean,default:!0,onChange:e=>{t(e)}}),game.settings.register(e.ID,"showUnchargedItems",{name:game.i18n.localize("tokenActionHud.dnd5e.settings.showUnchargedItems.name"),hint:game.i18n.localize("tokenActionHud.dnd5e.settings.showUnchargedItems.hint"),scope:"client",config:!0,type:Boolean,default:!1,onChange:e=>{t(e)}}),game.settings.register(e.ID,"showUnequippedItems",{name:game.i18n.localize("tokenActionHud.dnd5e.settings.showUnequippedItems.name"),hint:game.i18n.localize("tokenActionHud.dnd5e.settings.showUnequippedItems.hint"),scope:"client",config:!0,type:Boolean,default:!0,onChange:e=>{t(e)}}),game.settings.register(e.ID,"showUnpreparedSpells",{name:game.i18n.localize("tokenActionHud.dnd5e.settings.showUnpreparedSpells.name"),hint:game.i18n.localize("tokenActionHud.dnd5e.settings.showUnpreparedSpells.hint"),scope:"client",config:!0,type:Boolean,default:!0,onChange:e=>{t(e)}}),game.settings.register(e.ID,"showItemsWithoutActivationCosts",{name:game.i18n.localize("tokenActionHud.dnd5e.settings.showItemsWithoutActivationCosts.name"),hint:game.i18n.localize("tokenActionHud.dnd5e.settings.showItemsWithoutActivationCosts.hint"),scope:"client",config:!0,type:Boolean,default:!1,onChange:e=>{t(e)}})}Hooks.once("tokenActionHudCoreApiReady",(async e=>{m=class RollHandler extends e.api.RollHandler{async doHandleActionEvent(e,t){const s=t.split("|");2!==s.length&&super.throwInvalidValueErr();const i=s[0],n=s[1];if(this.actor)await this._handleMacros(e,i,this.actor,this.token,n);else for(const t of canvas.tokens.controlled){const s=t.actor;await this._handleMacros(e,i,s,t,n)}}async _handleMacros(e,t,s,i,n){switch(t){case"ability":this._rollAbility(e,s,n);break;case"check":this._rollAbilityTest(e,s,n);break;case"save":this._rollAbilitySave(e,s,n);break;case"condition":if(!i)return;await this._toggleCondition(e,i,n);break;case"effect":await this._toggleEffect(e,s,n);break;case"feature":case"item":case"spell":case"weapon":this.isRenderItem()?this.doRenderItem(s,n):this._useItem(e,s,n);break;case"magicItem":this._rollMagicItem(s,n);break;case"skill":this._rollSkill(e,s,n);break;case"utility":await this._performUtilityMacro(e,s,i,n)}}_rollAbility(e,t,s){t&&t.rollAbility(s,{event:e})}_rollAbilitySave(e,t,s){t&&t.rollAbilitySave(s,{event:e})}_rollAbilityTest(e,t,s){t&&t.rollAbilityTest(s,{event:e})}_rollMagicItem(e,t){const s=t.split(">"),i=s[0],n=s[1];MagicItems.actor(e.id).roll(i,n),Hooks.callAll("forceUpdateTokenActionHud")}_rollSkill(e,t,s){t&&t.rollSkill(s,{event:e})}_useItem(t,s,i){const n=e.api.Utils.getItem(s,i);if(!this._needsRecharge(n))return n.use({event:t});n.rollRecharge()}_needsRecharge(e){return e.system.recharge&&!e.system.recharge.charged&&e.system.recharge.value}async _performUtilityMacro(e,t,s,i){switch(i){case"deathSave":t.rollDeathSave({event:e});break;case"endTurn":if(!s)break;game.combat?.current?.tokenId===s.id&&await(game.combat?.nextTurn());break;case"initiative":await this._rollInitiative(t);break;case"inspiration":{const e=!t.system.attributes.inspiration;t.update({"data.attributes.inspiration":e});break}case"longRest":t.longRest();break;case"shortRest":t.shortRest()}Hooks.callAll("forceUpdateTokenActionHud")}async _rollInitiative(e){e&&(await e.rollInitiative({createCombatants:!0}),Hooks.callAll("forceUpdateTokenActionHud"))}async _toggleCondition(e,t,s,i=null){if(!t)return;const n=this.isRightClick(e);if(game.dfreds&&i?.flags?.isConvenient){const e=i.label;game.dfreds.effectInterface._toggleEffect(e)}else{const e=this.findCondition(s);if(!e)return;n?await t.toggleEffect(e,{overlay:!0}):await t.toggleEffect(e)}Hooks.callAll("forceUpdateTokenActionHud")}findCondition(e){return CONFIG.statusEffects.find((t=>t.id===e))}async _toggleEffect(e,t,s){const i=("find"in t.effects.entries?t.effects.entries:t.effects).find((e=>e.id===s));if(!i)return;this.isRightClick(e)?await i.delete():await i.update({disabled:!i.disabled}),Hooks.callAll("forceUpdateTokenActionHud")}}}));class RollHandlerObsidian extends m{_rollAbilityTest(e,t){OBSIDIAN.Items.roll(super.actor,{roll:"abl",abl:t})}_rollAbilitySave(e,t){OBSIDIAN.Items.roll(super.actor,{roll:"save",save:t})}_rollSkill(e,t){OBSIDIAN.Items.roll(super.actor,{roll:"skl",skl:t})}_useItem(e,t){OBSIDIAN.Items.roll(super.actor,{roll:"item",id:t})}}let y=null;Hooks.once("tokenActionHudCoreApiReady",(async e=>{y=class SystemManager extends e.api.SystemManager{doGetCategoryManager(){return new e.api.CategoryManager}doGetActionHandler(t){const s=new p(t);return e.api.Utils.isModuleActive("magicitems")&&s.addFurtherActionHandler(new u(s)),s}getAvailableRollHandlers(){let t="Core D&D5e";e.api.Utils.isModuleActive("midi-qol")&&(t+=` [supports ${e.api.Utils.getModuleTitle("midi-qol")}]`);const s={core:t};return e.api.SystemManager.addHandler(s,"obsidian"),s}doGetRollHandler(e){let t;if("obsidian"===e)t=new RollHandlerObsidian;else t=new m;return t}doRegisterSettings(e){register(e)}async doRegisterDefaultFlags(){const t=h;if(game.modules.get("magicitems")?.active){const s=e.api.Utils.i18n("tokenActionHud.dnd5e.magicItems");t.subcategories.push({id:"magic-items",name:s,listName:`Subcategory: ${s}`,type:"system",hasDerivedSubcategories:!0}),t.subcategories.sort(((e,t)=>e.id.localeCompare(t.id)))}return t}}})),Hooks.on("tokenActionHudCoreApiReady",(async()=>{const t=game.modules.get(e.ID);t.api={requiredCoreModuleVersion:"1.3",SystemManager:y},Hooks.call("tokenActionHudSystemReady",t)}));export{i as ACTION_TYPE,n as ACTIVATION_TYPE_ICON,p as ActionHandler,a as CONCENTRATION_ICON,t as CORE_MODULE,h as DEFAULTS,e as MODULE,u as MagicItemActionListExtender,l as PREPARED_ICON,c as PROFICIENCY_LEVEL_ICON,s as REQUIRED_CORE_MODULE_VERSION,o as RITUAL_ICON,m as RollHandler,RollHandlerObsidian,r as SUBCATEGORY,y as SystemManager,d as Utils,register}; diff --git a/scripts/utils.js b/scripts/utils.js index 70da86b..07c1c32 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -1,34 +1,37 @@ import { MODULE } from './constants.js' -import { Logger } from './config.js' -export class Utils { +export let Utils = null + +Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => { + Utils = class Utils { /** * Get setting value * @param {string} key The key * @param {string=null} defaultValue The default value * @returns The setting value */ - static getSetting (key, defaultValue = null) { - let value = defaultValue ?? null - try { - value = game.settings.get(MODULE.ID, key) - } catch { - Logger.debug(`Setting '${key}' not found`) + static getSetting (key, defaultValue = null) { + let value = defaultValue ?? null + try { + value = game.settings.get(MODULE.ID, key) + } catch { + coreModule.api.Logger.debug(`Setting '${key}' not found`) + } + return value } - return value - } - /** + /** * Set setting value * @param {string} key The key * @param {string} value The value */ - static async setSetting (key, value) { - try { - value = await game.settings.set(MODULE.ID, key, value) - Logger.debug(`Setting '${key}' set to '${value}'`) - } catch { - Logger.debug(`Setting '${key}' not found`) + static async setSetting (key, value) { + try { + value = await game.settings.set(MODULE.ID, key, value) + coreModule.api.Logger.debug(`Setting '${key}' set to '${value}'`) + } catch { + coreModule.api.Logger.debug(`Setting '${key}' not found`) + } } } -} +})