diff --git a/module.json b/module.json
index a18ca53..b0f4a5b 100644
--- a/module.json
+++ b/module.json
@@ -96,8 +96,8 @@
"id": "pf2e",
"type": "system",
"compatibility": {
- "minimum": "6.5.0",
- "verified": "6.6.0"
+ "minimum": "6.6.2",
+ "verified": "6.6.2"
}
}
],
@@ -107,7 +107,7 @@
"type": "module",
"compatibility": {
"minimum": "2.0.0",
- "verified": "2.0.5",
+ "verified": "2.0.6",
"maximum": "2.0.99"
}
}
diff --git a/scripts/action-handler.js b/scripts/action-handler.js
index 5b90a60..7d145e8 100644
--- a/scripts/action-handler.js
+++ b/scripts/action-handler.js
@@ -91,7 +91,8 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
this.#buildEffects(),
this.#buildFeats(),
this.#buildHeroActions(),
- this.#buildHeroPoints(),
+ this.#buildPoints('heroPoints'),
+ this.#buildPoints('mythicPoints'),
this.#buildInitiative(),
this.#buildInventory(),
this.#buildPerceptionCheck(),
@@ -451,26 +452,47 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
/**
* Build hero points
*/
- async #buildHeroPoints () {
- const actionType = 'heroPoints'
+ async #buildPoints (actionType) {
+ let actions, groupData
+
+ const mythicEnabled = this.actor.system.resources?.mythicPoints.max ? true : false
// Create group data
- const groupData = { id: 'hero-points', type: 'system' }
+ if (actionType === 'heroPoints' && !mythicEnabled) {
+ groupData = { id: 'hero-points', type: 'system' }
- const heroPoints = this.actor.system.resources?.heroPoints
- const value = heroPoints.value
- const max = heroPoints.max
+ const heroPoints = this.actor.system.resources?.heroPoints
+ const value = heroPoints.value
+ const max = heroPoints.max
- // Get actions
- const actions = [{
- id: 'heroPoints',
- name: coreModule.api.Utils.i18n('PF2E.HeroPointsLabel'),
- encodedValue: [actionType, actionType].join(this.delimiter),
- info1: { text: (max > 0) ? `${value ?? 0}/${max}` : '' }
- }]
+ // Get actions
+ actions = [{
+ id: 'heroPoints',
+ name: coreModule.api.Utils.i18n('PF2E.Actor.Resource.HeroPoints'),
+ encodedValue: [actionType, actionType].join(this.delimiter),
+ info1: { text: (max > 0) ? `${value ?? 0}/${max}` : '' }
+ }]
+ }
+ else if (actionType === 'mythicPoints' && mythicEnabled) {
+ groupData = { id: 'mythic-points', type: 'system' }
+
+ const mythicPoints = this.actor.system.resources?.mythicPoints
+ const value = mythicPoints.value
+ const max = mythicPoints.max
+
+ // Get actions
+ actions = [{
+ id: 'mythicPoints',
+ name: coreModule.api.Utils.i18n('PF2E.Actor.Resource.MythicPoints'),
+ encodedValue: [actionType, actionType].join(this.delimiter),
+ info1: { text: (max > 0) ? `${value ?? 0}/${max}` : '' }
+ }]
+ }
// Add actions to action list
- this.addActions(actions, groupData)
+ if (actions && groupData) {
+ this.addActions(actions, groupData)
+ }
}
/**
@@ -593,9 +615,9 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
}
/**
- * Build Hero Actions
- * @private
- */
+ * Build hero actions
+ * @private
+ */
async #buildHeroActions () {
if (!game.modules.get('pf2e-hero-actions')?.active) return
@@ -2126,7 +2148,9 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
if (!description && !tagsHtml && !modifiersHtml) return name
- return `
${nameHtml}${headerTags}${description}${propertiesHtml}
`
+ const tooltipHtml = `${nameHtml}${headerTags}${description}${propertiesHtml}
`
+
+ return await TextEditor.enrichHTML(tooltipHtml, { async: true })
}
/**
diff --git a/scripts/constants.js b/scripts/constants.js
index 391609f..8091771 100644
--- a/scripts/constants.js
+++ b/scripts/constants.js
@@ -194,7 +194,8 @@ export const GROUP = {
generalFeats: { id: 'general-feats', name: 'PF2E.FeatGeneralHeader', type: 'system' },
bonusFeats: { id: 'bonus-feats', name: 'PF2E.FeatBonusHeader', type: 'system' },
spells: { id: 'spells', name: 'PF2E.Item.Spell.Plural', type: 'system' },
- heroPoints: { id: 'hero-points', name: 'PF2E.HeroPointsLabel', type: 'system' },
+ heroPoints: { id: 'hero-points', name: 'PF2E.Actor.Resource.HeroPoints', type: 'system' },
+ mythicPoints: { id: 'mythic-points', name: 'PF2E.Actor.Resource.MythicPoints', type: 'system' },
initiative: { id: 'initiative', name: 'PF2E.InitiativeLabel', type: 'system' },
perceptionCheck: { id: 'perception-check', name: 'PF2E.PerceptionLabel', type: 'system' },
coreSkills: { id: 'core-skills', name: 'PF2E.CoreSkillsHeader', type: 'system' },
diff --git a/scripts/defaults.js b/scripts/defaults.js
index a501f12..3f99047 100644
--- a/scripts/defaults.js
+++ b/scripts/defaults.js
@@ -85,6 +85,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
name: coreModule.api.Utils.i18n('tokenActionHud.pf2e.attributes'),
groups: [
{ ...groups.heroPoints, nestId: 'attributes_hero-points' },
+ { ...groups.mythicPoints, nestId: 'attributes_mythic-points' },
{ ...groups.initiative, nestId: 'attributes_initiative' },
{ ...groups.perceptionCheck, nestId: 'attributes_perception-check' },
{ ...groups.saves, nestId: 'attributes_saves' }
diff --git a/scripts/roll-handler.js b/scripts/roll-handler.js
index d38a226..ac3111e 100644
--- a/scripts/roll-handler.js
+++ b/scripts/roll-handler.js
@@ -195,12 +195,15 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
case 'feat':
this.#rollItemMacro(event, actor, actionId)
break
- case 'heroAction':
- this.#performHeroAction(actor, actionId)
- break
+ case 'heroAction':
+ this.#performHeroAction(actor, actionId)
+ break
case 'heroPoints':
await this.#adjustResources(actor, 'heroPoints', 'value')
break
+ case 'mythicPoints':
+ await this.#adjustResources(actor, 'mythicPoints', 'value')
+ break
case 'initiative':
this.#rollInitiative(actor, actionId)
break
@@ -375,7 +378,14 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
}
}
- await actor.update({ "system.resources.heroPoints.value": value });
+ switch (resource) {
+ case "heroPoints":
+ await actor.update({ "system.resources.heroPoints.value": value })
+ break
+ case "mythicPoints":
+ await actor.update({ "system.resources.mythicPoints.value": value })
+ break
+ }
Hooks.callAll('forceUpdateTokenActionHud')
}
diff --git a/scripts/token-action-hud-pf2e.min.js.map b/scripts/token-action-hud-pf2e.min.js.map
index 917bdc1..f220acc 100644
--- a/scripts/token-action-hud-pf2e.min.js.map
+++ b/scripts/token-action-hud-pf2e.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"token-action-hud-pf2e.min.js","sources":["constants.js","defaults.js","utils.js","action-handler.js","roll-handler.js","settings.js","system-manager.js","init.js"],"sourcesContent":["/**\r\n * Module-based constants\r\n */\r\nexport const MODULE = {\r\n ID: 'token-action-hud-pf2e'\r\n}\r\n\r\n/**\r\n * Core module\r\n */\r\nexport const CORE_MODULE = {\r\n ID: 'token-action-hud-core'\r\n}\r\n\r\n/**\r\n * Core module version required by the system module\r\n */\r\nexport const REQUIRED_CORE_MODULE_VERSION = '2.0'\r\n\r\n/**\r\n * Damage type icons\r\n */\r\nexport const DAMAGE_TYPE_ICONS = {\r\n axe: 'fa-solid fa-axe fa-fw',\r\n brain: 'fa-solid fa-brain fa-fw',\r\n bolt: 'fa-solid fa-bolt fa-fw',\r\n 'bow-arrow': 'fa-solid fa-bow-arrow fa-fw',\r\n droplet: 'fa-solid fa-droplet fa-fw',\r\n fire: 'fa-solid fa-fire fa-fw',\r\n ghost: 'fa-solid fa-ghost fa-fw',\r\n hammer: 'fa-solid fa-hammer fa-fw',\r\n skull: 'fa-solid fa-skull fa-fw',\r\n snowflake: 'fa-solid fa-snowflake fa-fw',\r\n spider: 'fa-solid fa-spider fa-fw',\r\n sun: 'fa-solid fa-sun fa-fw',\r\n vial: 'fa-solid fa-vial fa-fw',\r\n 'waveform-lines': 'fa-solid fa-waveform-lines fa-fw'\r\n}\r\n\r\n/**\r\n * Action icons\r\n */\r\nexport const ACTION_ICON = {\r\n 1: 'A',\r\n 2: 'D',\r\n 3: 'T',\r\n free: 'F',\r\n reaction: 'R',\r\n passive: '',\r\n A: 'A',\r\n D: 'D',\r\n T: 'T',\r\n F: 'F',\r\n R: 'R',\r\n ...DAMAGE_TYPE_ICONS\r\n}\r\n\r\n/**\r\n * Action type\r\n */\r\nexport const ACTION_TYPE = {\r\n action: 'TYPES.Item.action',\r\n attribute: 'tokenActionHud.pf2e.attribute',\r\n condition: 'TYPES.Item.condition',\r\n effect: 'TYPES.Item.effect',\r\n familiarAttack: 'PF2E.AttackLabel',\r\n feat: 'PF2E.Item.Feat.LevelLabel',\r\n heroAction: 'tokenActionHud.pf2e.heroAction',\r\n initiative: 'PF2E.InitiativeLabel',\r\n item: 'PF2E.ItemTitle',\r\n save: 'tokenActionHud.pf2e.save',\r\n skill: 'PF2E.SkillLabel',\r\n skillAction: 'tokenActionHud.pf2e.skillAction',\r\n spell: 'TYPES.Item.spell',\r\n strike: 'PF2E.WeaponStrikeLabel',\r\n strikeAuxiliaryAction: 'PF2E.WeaponStrikeLabel',\r\n toggle: 'tokenActionHud.pf2e.toggle',\r\n utility: 'tokenActionHud.utility'\r\n}\r\n\r\n/**\r\n * Carry types icons\r\n */\r\nexport const CARRY_TYPE_ICON = {\r\n held1: {\r\n icon: '',\r\n tooltip: 'PF2E.CarryType.held1'\r\n },\r\n held2: {\r\n icon: '',\r\n tooltip: 'PF2E.CarryType.held2'\r\n },\r\n dropped: {\r\n icon: '',\r\n tooltip: 'PF2E.CarryType.dropped'\r\n },\r\n stowed: {\r\n icon: '',\r\n tooltip: 'PF2E.CarryType.stowed'\r\n },\r\n worn: {\r\n icon: '',\r\n tooltip: 'PF2E.CarryType.worn'\r\n }\r\n}\r\n\r\n/**\r\n * Item types\r\n */\r\nexport const ITEM_TYPE = {\r\n armor: { groupId: 'armor' },\r\n consumable: { groupId: 'consumables' },\r\n backpack: { groupId: 'containers' },\r\n equipment: { groupId: 'equipment' },\r\n shield: { groupId: 'shields' },\r\n treasure: { groupId: 'treasure' },\r\n weapon: { groupId: 'weapons' }\r\n}\r\n\r\n/**\r\n * Modular options\r\n */\r\nexport const MODULAR_OPTION = {\r\n bludgeoning: 'PF2E.TraitBludgeoning',\r\n piercing: 'PF2E.TraitPiercing',\r\n slashing: 'PF2E.TraitSlashing'\r\n}\r\n\r\n/**\r\n * Skill abbreviations\r\n */\r\nexport const SKILL_ABBREVIATION = {\r\n acrobatics: 'acr',\r\n arcana: 'arc',\r\n athletics: 'ath',\r\n crafting: 'cra',\r\n deception: 'dec',\r\n diplomacy: 'dip',\r\n intimidation: 'itm',\r\n medicine: 'med',\r\n nature: 'nat',\r\n occultism: 'occ',\r\n performance: 'prf',\r\n perception: 'per',\r\n religion: 'rel',\r\n society: 'soc',\r\n stealth: 'ste',\r\n survival: 'sur',\r\n thievery: 'thi'\r\n}\r\n\r\n/**\r\n * Strike icons\r\n */\r\nexport const STRIKE_ICON = {\r\n melee: 'systems/pf2e/icons/mdi/sword.svg',\r\n thrown: 'systems/pf2e/icons/mdi/thrown.svg'\r\n}\r\n\r\n/**\r\n * Strike usage\r\n */\r\nexport const STRIKE_USAGE = {\r\n melee: { name: 'PF2E.WeaponRangeMelee' },\r\n ranged: { name: 'PF2E.NPCAttackRanged' },\r\n thrown: { name: 'PF2E.TraitThrown' }\r\n}\r\n\r\n/**\r\n * Groups\r\n */\r\nexport const GROUP = {\r\n attack: { id: 'attack', name: 'PF2E.AttackLabel', type: 'system' },\r\n toggles: { id: 'toggles', name: 'PF2E.TogglesLabel', type: 'system' },\r\n strikes: { id: 'strikes', name: 'PF2E.StrikesLabel', type: 'system' },\r\n actions: { id: 'actions', name: 'PF2E.ActionsActionsHeader', type: 'system' },\r\n reactions: { id: 'reactions', name: 'PF2E.ActionsReactionsHeader', type: 'system' },\r\n freeActions: { id: 'free-actions', name: 'PF2E.ActionsFreeActionsHeader', type: 'system' },\r\n passives: { id: 'passives', name: 'PF2E.NPC.PassivesLabel', type: 'system' },\r\n skillActionsGrouped: { id: 'skill-actions-grouped', name: 'tokenActionHud.pf2e.skillActions', listName: 'tokenActionHud.pf2e.skillActionsGrouped', type: 'system' },\r\n skillActionsUngrouped: { id: 'skill-actions-ungrouped', name: 'tokenActionHud.pf2e.skillActions', listName: 'tokenActionHud.pf2e.skillActionsUngrouped', type: 'system', settings: { sort: true } },\r\n weapons: { id: 'weapons', name: 'tokenActionHud.pf2e.weapons', type: 'system' },\r\n shields: { id: 'shields', name: 'tokenActionHud.pf2e.shields', type: 'system' },\r\n armor: { id: 'armor', name: 'tokenActionHud.pf2e.armor', type: 'system' },\r\n equipment: { id: 'equipment', name: 'tokenActionHud.pf2e.equipment', type: 'system' },\r\n consumables: { id: 'consumables', name: 'tokenActionHud.pf2e.consumables', type: 'system' },\r\n containers: { id: 'containers', name: 'tokenActionHud.pf2e.containers', type: 'system' },\r\n treasure: { id: 'treasure', name: 'tokenActionHud.pf2e.treasure', type: 'system' },\r\n ancestryFeatures: { id: 'ancestry-features', name: 'PF2E.FeaturesAncestryHeader', type: 'system' },\r\n classFeatures: { id: 'class-features', name: 'PF2E.FeaturesClassHeader', type: 'system' },\r\n ancestryFeats: { id: 'ancestry-feats', name: 'PF2E.FeatAncestryHeader', type: 'system' },\r\n classFeats: { id: 'class-feats', name: 'PF2E.FeatClassHeader', type: 'system' },\r\n skillFeats: { id: 'skill-feats', name: 'PF2E.FeatSkillHeader', type: 'system' },\r\n generalFeats: { id: 'general-feats', name: 'PF2E.FeatGeneralHeader', type: 'system' },\r\n bonusFeats: { id: 'bonus-feats', name: 'PF2E.FeatBonusHeader', type: 'system' },\r\n spells: { id: 'spells', name: 'PF2E.Item.Spell.Plural', type: 'system' },\r\n heroPoints: { id: 'hero-points', name: 'PF2E.HeroPointsLabel', type: 'system' },\r\n initiative: { id: 'initiative', name: 'PF2E.InitiativeLabel', type: 'system' },\r\n perceptionCheck: { id: 'perception-check', name: 'PF2E.PerceptionLabel', type: 'system' },\r\n coreSkills: { id: 'core-skills', name: 'PF2E.CoreSkillsHeader', type: 'system' },\r\n loreSkills: { id: 'lore-skills', name: 'PF2E.LoreSkillsHeader', type: 'system' },\r\n conditions: { id: 'conditions', name: 'PF2E.ConditionsLabel', type: 'system' },\r\n socialConditions: { id: 'social-conditions', name: 'tokenActionHud.pf2e.socialConditions', type: 'system' },\r\n otherConditions: { id: 'other-conditions', name: 'tokenActionHud.pf2e.otherConditions', type: 'system' },\r\n effects: { id: 'effects', name: 'PF2E.EffectsLabel', type: 'system' },\r\n combat: { id: 'combat', name: 'tokenActionHud.combat', type: 'system' },\r\n token: { id: 'token', name: 'tokenActionHud.token', type: 'system' },\r\n recoveryCheck: { id: 'recovery-check', name: 'PF2E.Check.Specific.Recovery', type: 'system' },\r\n rests: { id: 'rests', name: 'tokenActionHud.pf2e.rests', type: 'system' },\r\n saves: { id: 'saves', name: 'PF2E.SavesHeader', type: 'system' },\r\n utility: { id: 'utility', name: 'tokenActionHud.utility', type: 'system' }\r\n}\r\n\r\n/**\r\n * Skill actions\r\n */\r\nexport const SKILL_ACTION = {\r\n l5pbgrj8SSNtRGs8: { name: 'PF2E.Actions.AdministerFirstAid.Stabilize.Title', actionCost: 2, skill: 'medicine', image: 'systems/pf2e/icons/features/feats/treat-wounds.webp' },\r\n ZEWD4zcEDQwYhVT8: { name: 'PF2E.Actions.AdministerFirstAid.StopBleeding.Title', actionCost: 2, skill: 'medicine', image: 'systems/pf2e/icons/conditions/persistent-damage.webp' },\r\n '55mxH0w8UkY1o3Xv': { name: 'PF2E.Actions.Balance.Title', skill: 'acrobatics', actionCost: 1, image: 'icons/skills/movement/feet-winged-boots-brown.webp' },\r\n LXCy1iJddD95Z91s: { name: 'PF2E.Actions.Climb.Title', skill: 'athletics', actionCost: 1, image: 'icons/sundries/misc/ladder.webp' },\r\n '9RNumMausgG7adgL': { name: 'PF2E.Actions.Coerce.Title', skill: 'intimidation', actionCost: 'passive', image: 'icons/skills/social/intimidation-impressing.webp' },\r\n xcrdOOiN0l6O1sIn: { name: 'PF2E.Actions.CommandAnAnimal.Title', skill: 'nature', actionCost: 1, image: 'icons/environment/creatures/horse-white.webp' },\r\n zn0HadZeoKDALxRu: { name: 'PF2E.Actions.ConcealAnObject.Title', skill: 'stealth', actionCost: 1, image: 'systems/pf2e/icons/equipment/adventuring-gear/wax-key-blank.webp' },\r\n Tu7LIRelQsiOuo1l: { name: 'PF2E.Actions.Craft.Title', skill: 'crafting', actionCost: 'passive', image: 'icons/skills/trades/smithing-anvil-silver-red.webp' },\r\n aDsYSdRqiC6qQIOQ: { name: 'PF2E.Actions.CreateADiversion.DistractingWords.Title', skill: 'deception', actionCost: 1, image: 'icons/magic/control/mouth-smile-deception-purple.webp' },\r\n zUJ0UhuoFt5a7tiN: { name: 'PF2E.Actions.CreateADiversion.Gesture.Title', skill: 'deception', actionCost: 1, image: 'icons/skills/social/wave-halt-stop.webp' },\r\n '1JpYPlIkjyseE9JU': { name: 'PF2E.Actions.CreateADiversion.Trick.Title', skill: 'deception', actionCost: 1, image: 'systems/pf2e/icons/spells/charming-words.webp' },\r\n mNphXpAkmGsMadUv: { name: 'PF2E.Actions.CreateForgery.Title', skill: 'society', actionCost: 'passive', image: 'systems/pf2e/icons/spells/transcribe-moment.webp' },\r\n U6WjxFPn4fUqIrfl: { name: 'PF2E.Actions.DecipherWriting.Title', skill: 'arcana', actionCost: 'passive', image: 'icons/skills/trades/academics-book-study-runes.webp' },\r\n RZyfkw1DiqVy3JUC: { name: 'PF2E.Actions.DecipherWriting.Title', skill: 'occultism', actionCost: 'passive', image: 'icons/skills/trades/academics-book-study-purple.webp' },\r\n sDUERv4E88G5BRPr: { name: 'PF2E.Actions.DecipherWriting.Title', skill: 'religion', actionCost: 'passive', image: 'systems/pf2e/icons/equipment/other/spellbooks/thresholds-of-truth.webp' },\r\n YWAvvDXpdW1fYPFo: { name: 'PF2E.Actions.DecipherWriting.Title', skill: 'society', actionCost: 'passive', image: 'icons/skills/trades/academics-study-reading-book.webp' },\r\n nEwqNNWX6scLt4sc: { name: 'PF2E.Actions.Demoralize.Title', skill: 'intimidation', actionCost: 1, image: 'icons/skills/social/intimidation-impressing.webp' },\r\n T2QNEoRojMWEec4a: { name: 'PF2E.Actions.DisableDevice.Title', skill: 'thievery', actionCost: 2, image: 'systems/pf2e/icons/equipment/adventuring-gear/thieves-tools.webp' },\r\n ooiO59Ch2QaebOmc: { name: 'PF2E.Actions.Disarm.Title', skill: 'athletics', actionCost: 1, image: 'icons/skills/melee/sword-damaged-broken-glow-red.webp' },\r\n '50Q0DYL33Kalu1BH': { name: 'PF2E.Actions.Escape.Title', skill: 'acrobatics', actionCost: 1, image: 'icons/skills/movement/feet-winged-boots-glowing-yellow.webp' },\r\n lkEcQQss16SIrVxM: { name: 'PF2E.Actions.Escape.Title', skill: 'athletics', actionCost: 1, image: 'icons/skills/movement/feet-winged-boots-glowing-yellow.webp' },\r\n RjfPFjqPrNve6eeh: { name: 'PF2E.Actions.Feint.Title', skill: 'deception', actionCost: 1, image: 'icons/skills/melee/maneuver-sword-katana-yellow.webp' },\r\n yMTKMnaYSGtDz4wk: { name: 'PF2E.Actions.ForceOpen.Title', skill: 'athletics', actionCost: 1, image: 'icons/equipment/feet/boots-armored-steel.webp' },\r\n rCgGPEyXbzLFcio6: { name: 'PF2E.Actions.GatherInformation.Title', skill: 'diplomacy', actionCost: 'passive', image: 'icons/skills/social/diplomacy-handshake.webp' },\r\n i95kcGLIQKOTsnv6: { name: 'PF2E.Actions.Grapple.Title', skill: 'athletics', actionCost: 'passive', image: 'icons/skills/melee/unarmed-punch-fist.webp' },\r\n FlM3HvpnsZpCKawG: { name: 'PF2E.Actions.Hide.Title', skill: 'stealth', actionCost: 1, image: 'systems/pf2e/icons/features/classes/wild.webp' },\r\n v3dlDjFlOmT5T2gC: { name: 'PF2E.Actions.HighJump.Title', skill: 'athletics', actionCost: 2, image: 'icons/skills/movement/arrows-up-trio-red.webp' },\r\n k5nW4jGyXD0Oq9LR: { name: 'PF2E.Actions.Impersonate.Title', skill: 'deception', actionCost: 'passive', image: 'icons/equipment/head/mask-carved-scream-tan.webp' },\r\n VTg4t8kYTvXcHROq: { name: 'PF2E.Actions.Lie.Title', skill: 'deception', actionCost: 'passive', image: 'icons/magic/control/mouth-smile-deception-purple.webp' },\r\n QPsV0qi2zXm7syt6: { name: 'PF2E.Actions.LongJump.Title', skill: 'athletics', actionCost: 2, image: 'icons/skills/movement/figure-running-gray.webp' },\r\n '1Sj2Pz3VI2SFWqZw': { name: 'PF2E.Actions.MakeAnImpression.Title', skill: 'diplomacy', actionCost: 'passive', image: 'icons/environment/people/commoner.webp' },\r\n '9Ul5Op5OceT9P5SS': { name: 'PF2E.Actions.ManeuverInFlight.Title', skill: 'acrobatics', actionCost: 1, image: 'icons/commodities/biological/wing-bird-white.webp' },\r\n Gj68YCVlDjc75iCP: { name: 'PF2E.Actions.PalmAnObject.Title', skill: 'thievery', actionCost: 1, image: 'icons/sundries/gaming/playing-cards.webp' },\r\n dWcrojMk0d2WRPBq: { name: 'PF2E.Actions.Perform.Title', skill: 'performance', actionCost: 1, image: 'icons/skills/trades/music-singing-voice-blue.webp' },\r\n '8YrH37NzKRuiKFbF': { name: 'PF2E.Actions.PickALock.Title', skill: 'thievery', actionCost: 2, image: 'icons/sundries/misc/lock-bronze-reinforced.webp' },\r\n BQTA7bL264189Xla: { name: 'PF2E.Actions.Repair.Title', skill: 'crafting', actionCost: 'passive', image: 'icons/tools/smithing/anvil.webp' },\r\n tbveXG4gaIoKnsWX: { name: 'PF2E.Actions.Request.Title', skill: 'diplomacy', actionCost: 1, image: 'icons/skills/social/thumbsup-approval-like.webp' },\r\n tikhJ2b6AMh7wQU7: { name: 'PF2E.Actions.Seek.Title', skill: 'perception', actionCost: 1, image: 'icons/tools/scribal/magnifying-glass.webp' },\r\n LN67MgbGE8IHb2X0: { name: 'PF2E.Actions.SenseDirection.Title', skill: 'survival', actionCost: 'passive', image: 'icons/tools/navigation/compass-brass-blue-red.webp' },\r\n PmHt7Gb5fCrlWWTr: { name: 'PF2E.Actions.SenseMotive.Title', skill: 'perception', actionCost: 1, image: 'icons/environment/people/commoner.webp' },\r\n yNry1xMZqdWHncbV: { name: 'PF2E.Actions.Shove.Title', skill: 'athletics', actionCost: 1, image: 'systems/pf2e/icons/spells/hydraulic-push.webp' },\r\n HSTkVuv0SjTNK3Xx: { name: 'PF2E.Actions.Sneak.Title', skill: 'stealth', actionCost: 1, image: 'systems/pf2e/icons/conditions/unnoticed.webp' },\r\n UKHPveLpG7hUs4D0: { name: 'PF2E.Actions.Squeeze.Title', skill: 'acrobatics', actionCost: 'passive', image: 'icons/commodities/tech/claw-mechanical.webp' },\r\n zjovbAeuLvyuWFKd: { name: 'PF2E.Actions.Steal.Title', skill: 'thievery', actionCost: 1, image: 'icons/containers/bags/coinpouch-gold-red.webp' },\r\n mkKko3CEBCyJVQw1: { name: 'PF2E.Actions.Subsist.Title', skill: 'society', actionCost: 'passive', image: 'icons/environment/settlement/building-rubble.webp' },\r\n zkqh01BoXDVgydzo: { name: 'PF2E.Actions.Subsist.Title', skill: 'survival', actionCost: 'passive', image: 'icons/environment/wilderness/camp-improvised.webp' },\r\n TIlUkCzviYxdVk4E: { name: 'PF2E.Actions.Swim.Title', skill: 'athletics', actionCost: 1, image: 'icons/creatures/fish/fish-shark-swimming.webp' },\r\n Al5LYMMdeDcpC9Br: { name: 'PF2E.Actions.Track.Title', skill: 'survival', actionCost: 'passive', image: 'systems/pf2e/icons/conditions/observed.webp' },\r\n m4iM5r3TfvQs5Y2n: { name: 'PF2E.Actions.TreatDisease.Title', skill: 'medicine', actionCost: 'passive', image: 'icons/magic/nature/root-vine-caduceus-healing.webp' },\r\n R03LRl2RBbsm6EcF: { name: 'PF2E.Actions.TreatPoison.Title', skill: 'medicine', actionCost: 1, image: 'systems/pf2e/icons/effects/treat-poison.webp' },\r\n gRj7xUfcpUZQLrOC: { name: 'PF2E.Actions.Trip.Title', skill: 'athletics', actionCost: 1, image: 'icons/skills/wounds/bone-broken-marrow-yellow.webp' },\r\n '2qhYHkcSsTJoSwrJ': { name: 'PF2E.Actions.TumbleThrough.Title', skill: 'acrobatics', actionCost: 1, image: 'icons/skills/movement/feet-winged-sandals-tan.webp' }\r\n}\r\n\r\n/**\r\n * Skills\r\n */\r\nexport const SKILL = {\r\n acrobatics: { name: 'PF2E.SkillAcrobatics' },\r\n arcana: { name: 'PF2E.SkillArcana' },\r\n athletics: { name: 'PF2E.SkillAthletics' },\r\n crafting: { name: 'PF2E.SkillCrafting' },\r\n deception: { name: 'PF2E.SkillDeception' },\r\n diplomacy: { name: 'PF2E.SkillDiplomacy' },\r\n intimidation: { name: 'PF2E.SkillIntimidation' },\r\n lore: { name: 'PF2E.SkillLore' },\r\n medicine: { name: 'PF2E.SkillMedicine' },\r\n nature: { name: 'PF2E.SkillNature' },\r\n occultism: { name: 'PF2E.SkillOccultism' },\r\n perception: { name: 'PF2E.PerceptionLabel' },\r\n performance: { name: 'PF2E.SkillPerformance' },\r\n religion: { name: 'PF2E.SkillReligion' },\r\n society: { name: 'PF2E.SkillSociety' },\r\n stealth: { name: 'PF2E.SkillStealth' },\r\n survival: { name: 'PF2E.SkillSurvival' },\r\n thievery: { name: 'PF2E.SkillThievery' }\r\n}\r\n","import { GROUP } from './constants.js'\r\n\r\n/**\r\n * Default layout and groups\r\n */\r\nexport let DEFAULTS = null\r\n\r\nHooks.once('tokenActionHudCoreApiReady', async (coreModule) => {\r\n const groups = GROUP\r\n Object.values(groups).forEach(group => {\r\n group.name = coreModule.api.Utils.i18n(group.name)\r\n group.listName = `Group: ${coreModule.api.Utils.i18n(group.listName ?? group.name)}`\r\n })\r\n const groupsArray = Object.values(groups)\r\n DEFAULTS = {\r\n layout: [\r\n {\r\n nestId: 'attack',\r\n id: 'attack',\r\n name: coreModule.api.Utils.i18n('PF2E.AttackLabel'),\r\n groups: [\r\n { ...groups.attack, nestId: 'attack_attack' }\r\n ]\r\n },\r\n {\r\n nestId: 'strikes',\r\n id: 'strikes',\r\n name: coreModule.api.Utils.i18n('PF2E.StrikesLabel'),\r\n groups: [\r\n { ...groups.toggles, nestId: 'strikes_toggles' },\r\n { ...groups.strikes, nestId: 'strikes_strikes' }\r\n ],\r\n settings: { customWidth: 500 }\r\n },\r\n {\r\n nestId: 'actions',\r\n id: 'actions',\r\n name: coreModule.api.Utils.i18n('PF2E.ActionsActionsHeader'),\r\n groups: [\r\n { ...groups.actions, nestId: 'actions_actions' },\r\n { ...groups.reactions, nestId: 'actions_reactions' },\r\n { ...groups.freeActions, nestId: 'actions_free-actions' },\r\n { ...groups.passives, nestId: 'actions_passives' }\r\n ]\r\n },\r\n {\r\n nestId: 'inventory',\r\n id: 'inventory',\r\n name: coreModule.api.Utils.i18n('PF2E.TabInventoryLabel'),\r\n groups: [\r\n { ...groups.weapons, nestId: 'inventory_weapons' },\r\n { ...groups.shields, nestId: 'inventory_shields' },\r\n { ...groups.armor, nestId: 'inventory_armor' },\r\n { ...groups.equipment, nestId: 'inventory_equipment' },\r\n { ...groups.consumables, nestId: 'inventory_consumables' },\r\n { ...groups.containers, nestId: 'inventory_containers' },\r\n { ...groups.treasure, nestId: 'inventory_treasure' }\r\n ]\r\n },\r\n {\r\n nestId: 'feats',\r\n id: 'feats',\r\n name: coreModule.api.Utils.i18n('PF2E.Item.Feat.Plural'),\r\n groups: [\r\n { ...groups.ancestryFeatures, nestId: 'feats_ancestry-features' },\r\n { ...groups.classFeatures, nestId: 'feats_class-features' },\r\n { ...groups.ancestryFeats, nestId: 'feats_ancestry-feats' },\r\n { ...groups.classFeats, nestId: 'feats_class-feats' },\r\n { ...groups.skillFeats, nestId: 'feats_skill-feats' },\r\n { ...groups.generalFeats, nestId: 'feats_general-feats' },\r\n { ...groups.bonusFeats, nestId: 'feats_bonus-feats' }\r\n ]\r\n },\r\n {\r\n nestId: 'spells',\r\n id: 'spells',\r\n name: coreModule.api.Utils.i18n('PF2E.Item.Spell.Plural'),\r\n groups: [\r\n { ...groups.spells, nestId: 'spells_spells' }\r\n ]\r\n },\r\n {\r\n nestId: 'attributes',\r\n id: 'attributes',\r\n name: coreModule.api.Utils.i18n('tokenActionHud.pf2e.attributes'),\r\n groups: [\r\n { ...groups.heroPoints, nestId: 'attributes_hero-points' },\r\n { ...groups.initiative, nestId: 'attributes_initiative' },\r\n { ...groups.perceptionCheck, nestId: 'attributes_perception-check' },\r\n { ...groups.saves, nestId: 'attributes_saves' }\r\n ]\r\n },\r\n {\r\n nestId: 'skills',\r\n id: 'skills',\r\n name: coreModule.api.Utils.i18n('PF2E.SkillsLabel'),\r\n groups: [\r\n { ...groups.coreSkills, nestId: 'skills_core-skills' },\r\n { ...groups.loreSkills, nestId: 'skills_lore-skills' }\r\n ]\r\n },\r\n {\r\n nestId: 'effects',\r\n id: 'effects',\r\n name: coreModule.api.Utils.i18n('PF2E.EffectsLabel'),\r\n groups: [\r\n { ...groups.conditions, nestId: 'effects_conditions' },\r\n { ...groups.socialConditions, nestId: 'effects_social-conditions' },\r\n { ...groups.otherConditions, nestId: 'effects_other-conditions' },\r\n { ...groups.effects, nestId: 'effects_effects' }\r\n ]\r\n },\r\n {\r\n nestId: 'utility',\r\n id: 'utility',\r\n name: coreModule.api.Utils.i18n('tokenActionHud.utility'),\r\n groups: [\r\n { ...groups.combat, nestId: 'utility_combat' },\r\n { ...groups.token, nestId: 'utility_token' },\r\n { ...groups.recoveryCheck, nestId: 'utility_recovery-check' },\r\n { ...groups.rests, nestId: 'utility_rests' },\r\n { ...groups.utility, nestId: 'utility_utility' }\r\n ]\r\n }\r\n ],\r\n groups: groupsArray\r\n }\r\n})\r\n","import { MODULE } from './constants.js'\r\n\r\nexport let Utils = null\r\n\r\nHooks.once('tokenActionHudCoreApiReady', async (coreModule) => {\r\n Utils = class Utils {\r\n /**\r\n * Get setting value\r\n * @param {string} key The key\r\n * @param {string=null} defaultValue The default value\r\n * @returns The setting value\r\n */\r\n static getSetting (key, defaultValue = null) {\r\n let value = defaultValue ?? null\r\n try {\r\n value = game.settings.get(MODULE.ID, key)\r\n } catch {\r\n coreModule.api.Logger.debug(`Setting '${key}' not found`)\r\n }\r\n return value\r\n }\r\n\r\n /**\r\n * Set setting value\r\n * @param {string} key The key\r\n * @param {string} value The value\r\n */\r\n static async setSetting (key, value) {\r\n try {\r\n value = await game.settings.set(MODULE.ID, key, value)\r\n coreModule.api.Logger.debug(`Setting '${key}' set to '${value}'`)\r\n } catch {\r\n coreModule.api.Logger.debug(`Setting '${key}' not found`)\r\n }\r\n }\r\n }\r\n})\r\n","// System Module Imports\r\nimport { ACTION_ICON, ACTION_TYPE, CARRY_TYPE_ICON, ITEM_TYPE, MODULAR_OPTION, SKILL_ABBREVIATION, SKILL, SKILL_ACTION, STRIKE_ICON, STRIKE_USAGE, DAMAGE_TYPE_ICONS } from './constants.js'\r\nimport { Utils } from './utils.js'\r\n\r\nexport let ActionHandler = null\r\n\r\nHooks.once('tokenActionHudCoreApiReady', async (coreModule) => {\r\n ActionHandler = class ActionHandler extends coreModule.api.ActionHandler {\r\n // Initialize actor and token variables\r\n actors = null\r\n actorId = null\r\n actorType = null\r\n tokenId = null\r\n\r\n // Initialize items variable\r\n items = null\r\n\r\n // Initialize groupIds variables\r\n groupIds = null\r\n activationGroupIds = null\r\n effectGroupIds = null\r\n inventoryGroupIds = null\r\n spellGroupIds = null\r\n\r\n // Initialize action variables\r\n featureActions = null\r\n inventoryActions = null\r\n spellActions = null\r\n\r\n mapLabel = coreModule.api.Utils.i18n('PF2E.MAPAbbreviationLabel').replace(' {penalty}', '')\r\n\r\n /**\r\n * Build System Actions\r\n * @override\r\n * @param {array} groupIds\r\n */\r\n async buildSystemActions (groupIds) {\r\n // Set actor and token variables\r\n this.actors = (!this.actor) ? this.#getActors() : [this.actor]\r\n this.actorType = this.actor?.type\r\n\r\n // Exit if actor is not a known type\r\n const knownActors = ['character', 'familiar', 'hazard', 'npc']\r\n if (this.actorType && !knownActors.includes(this.actorType)) return\r\n\r\n // Set items variable\r\n if (this.actor) {\r\n let items = this.actor.items\r\n items = coreModule.api.Utils.sortItemsByName(items)\r\n this.items = items\r\n }\r\n\r\n // Set settings variables\r\n this.abbreviateSkills = Utils.getSetting('abbreviateSkills')\r\n this.addAuxiliaryActions = Utils.getSetting('addAuxiliaryActions')\r\n this.addDamageAndCritical = Utils.getSetting('addDamageAndCritical')\r\n this.addStowedItems = Utils.getSetting('addStowedItems')\r\n this.addUnequippedItems = Utils.getSetting('addUnequippedItems')\r\n this.calculateAttackPenalty = Utils.getSetting('calculateAttackPenalty')\r\n this.colorSkills = Utils.getSetting('colorSkills')\r\n this.showStrikeImages = Utils.getSetting('showStrikeImages')\r\n this.showStrikeNames = Utils.getSetting('showStrikeNames')\r\n this.showStrikeTraits = Utils.getSetting('showStrikeTraits')\r\n this.splitStrikes = Utils.getSetting('splitStrikes')\r\n\r\n // Set group variables\r\n this.groupIds = groupIds\r\n\r\n if (this.actorType === 'character') {\r\n await this.#buildCharacterActions()\r\n } else if (this.actorType === 'familiar') {\r\n await this.#buildFamiliarActions()\r\n } else if (this.actorType === 'hazard') {\r\n await this.#buildHazardActions()\r\n } else if (this.actorType === 'npc') {\r\n await this.#buildNpcActions()\r\n } else if (!this.actor) {\r\n this.#buildMultipleTokenActions()\r\n }\r\n }\r\n\r\n /**\r\n * Build character actions\r\n * @private\r\n */\r\n async #buildCharacterActions () {\r\n await Promise.all([\r\n this.#buildActions(),\r\n this.#buildCombat(),\r\n this.#buildConditions(),\r\n this.#buildEffects(),\r\n this.#buildFeats(),\r\n this.#buildHeroActions(),\r\n this.#buildHeroPoints(),\r\n this.#buildInitiative(),\r\n this.#buildInventory(),\r\n this.#buildPerceptionCheck(),\r\n this.#buildRecoveryCheck(),\r\n this.#buildRests(),\r\n this.#buildSaves(),\r\n this.#buildSkillActions(),\r\n this.#buildSkills(),\r\n this.#buildSpells(),\r\n this.#buildStrikes(),\r\n this.#buildToggles()\r\n ])\r\n // Build elemental blasts after other character actions so they are grouped together\r\n await this.#buildElementalBlasts()\r\n }\r\n\r\n /**\r\n * Build familiar actions\r\n * @private\r\n */\r\n async #buildFamiliarActions () {\r\n await Promise.all([\r\n this.#buildActions(),\r\n this.#buildAttack(),\r\n this.#buildCombat(),\r\n this.#buildConditions(),\r\n this.#buildEffects(),\r\n this.#buildInventory(),\r\n this.#buildPerceptionCheck(),\r\n this.#buildSaves(),\r\n this.#buildSkills()\r\n ])\r\n }\r\n\r\n /**\r\n * Build hazard actions\r\n * @private\r\n */\r\n async #buildHazardActions () {\r\n await Promise.all([\r\n this.#buildActions(),\r\n this.#buildCombat(),\r\n this.#buildInitiative(),\r\n this.#buildSaves(),\r\n this.#buildStrikes()\r\n ])\r\n }\r\n\r\n /**\r\n * Build NPC actions\r\n */\r\n async #buildNpcActions () {\r\n await Promise.all([\r\n this.#buildActions(),\r\n this.#buildCombat(),\r\n this.#buildConditions(),\r\n this.#buildEffects(),\r\n this.#buildFeats(),\r\n this.#buildInitiative(),\r\n this.#buildInventory(),\r\n this.#buildPerceptionCheck(),\r\n this.#buildSaves(),\r\n this.#buildSkillActions(),\r\n this.#buildSkills(),\r\n this.#buildStrikes(),\r\n this.#buildSpells(),\r\n this.#buildToggles()\r\n ])\r\n }\r\n\r\n /**\r\n * Build multiple token actions\r\n * @private\r\n * @returns {object}\r\n */\r\n async #buildMultipleTokenActions () {\r\n await Promise.all([\r\n this.#buildInitiative(),\r\n this.#buildPerceptionCheck(),\r\n this.#buildSaves(),\r\n this.#buildSkillActions(),\r\n this.#buildSkills()\r\n ])\r\n }\r\n\r\n /**\r\n * Build actions\r\n */\r\n async #buildActions () {\r\n const actionType = 'action'\r\n\r\n // Exit early if no items exist\r\n if (this.items.size === 0) return\r\n\r\n const actionTypes = ['action', 'reaction', 'free', 'passive']\r\n\r\n const actionItems = new Map([...this.items].filter(([_, itemData]) => itemData.type === 'action' || actionTypes.includes(itemData.system?.actionType?.value)))\r\n\r\n const actionsMap = new Map()\r\n\r\n for (const [key, value] of actionItems) {\r\n // Set variables\r\n const actionTypeValue = value.system.actionType?.value\r\n\r\n switch (actionTypeValue) {\r\n case 'action':\r\n actionsMap.set('actions', actionsMap.get('actions') || new Map())\r\n actionsMap.get('actions').set(key, value)\r\n break\r\n case 'reaction':\r\n actionsMap.set('reactions', actionsMap.get('reactions') || new Map())\r\n actionsMap.get('reactions').set(key, value)\r\n break\r\n case 'free':\r\n actionsMap.set('free-actions', actionsMap.get('free-actions') || new Map())\r\n actionsMap.get('free-actions').set(key, value)\r\n break\r\n case 'passive':\r\n actionsMap.set('passives', actionsMap.get('passives') || new Map())\r\n actionsMap.get('passives').set(key, value)\r\n break\r\n }\r\n }\r\n\r\n // Loop through inventory subcategory ids\r\n for (const [key, value] of actionsMap) {\r\n const groupId = key\r\n const items = value\r\n\r\n // Create group data\r\n const groupData = { id: groupId, type: 'system' }\r\n\r\n const actions = await Promise.all(\r\n [...items].map(async ([_, itemData]) => {\r\n const id = this.#getActionId(itemData)\r\n const name = this.#getActionName(itemData)\r\n const listName = this.#getActionListName(itemData, actionType)\r\n const cssClass = this.#getActionCss(itemData)\r\n const encodedValue = [actionType, id].join(this.delimiter)\r\n const icon1 = this.#getIcon1(itemData, actionType)\r\n const img = coreModule.api.Utils.getImage(itemData)\r\n const info = this.#getItemInfo(itemData)\r\n const tooltipData = await this.#getTooltipData(itemData, actionType)\r\n const tooltip = await this.#getTooltip(actionType, tooltipData)\r\n\r\n return {\r\n id,\r\n name,\r\n encodedValue,\r\n cssClass,\r\n img,\r\n icon1,\r\n info,\r\n listName,\r\n tooltip\r\n }\r\n })\r\n )\r\n\r\n // Add actions to action list\r\n this.addActions(actions, groupData)\r\n }\r\n }\r\n\r\n /**\r\n * Build attacks\r\n * @private\r\n */\r\n #buildAttack () {\r\n const actionType = 'familiarAttack'\r\n\r\n const attack = this.actor.system.attack\r\n\r\n if (attack) {\r\n const id = attack.slug\r\n const name = coreModule.api.Utils.i18n('PF2E.AttackLabel')\r\n const actionTypeName = `${coreModule.api.Utils.i18n(ACTION_TYPE[actionType])}: ` ?? ''\r\n const listName = `${actionTypeName}${name}`\r\n const encodedValue = [actionType, id].join(this.delimiter)\r\n const modifier = coreModule.api.Utils.getModifier(attack?.totalModifier)\r\n const info1 = this.actor ? { text: modifier } : ''\r\n\r\n // Get actions\r\n const actions = [{\r\n id,\r\n name,\r\n listName,\r\n encodedValue,\r\n info1\r\n }]\r\n\r\n // Create group data\r\n const groupData = { id: 'attack', type: 'system' }\r\n\r\n // Add actions to action list\r\n this.addActions(actions, groupData)\r\n }\r\n }\r\n\r\n /**\r\n * Build combat\r\n */\r\n #buildCombat () {\r\n const actionType = 'utility'\r\n\r\n // Set combat types\r\n const combatTypes = {\r\n endTurn: { id: 'endTurn', name: coreModule.api.Utils.i18n('tokenActionHud.endTurn') }\r\n }\r\n\r\n // Delete endTurn for multiple tokens\r\n if (game.combat?.current?.tokenId !== this.token?.id) delete combatTypes.endTurn\r\n\r\n // Get actions\r\n const actions = Object.entries(combatTypes).map((combatType) => {\r\n const id = combatType[1].id\r\n const name = combatType[1].name\r\n const actionTypeName = `${coreModule.api.Utils.i18n(ACTION_TYPE[actionType])}: ` ?? ''\r\n const listName = `${actionTypeName}${name}`\r\n const encodedValue = [actionType, id].join(this.delimiter)\r\n return {\r\n id,\r\n name,\r\n listName,\r\n encodedValue\r\n }\r\n })\r\n\r\n // Create group data\r\n const groupData = { id: 'combat', type: 'system' }\r\n\r\n // Add actions to action list\r\n this.addActions(actions, groupData)\r\n }\r\n\r\n /**\r\n * Build conditions\r\n * @private\r\n */\r\n async #buildConditions () {\r\n const actionType = 'condition'\r\n const limitedConditions = ['doomed', 'dying', 'wounded']\r\n\r\n // Get active conditions\r\n const activeConditions = new Map(\r\n [...this.items]\r\n .filter(item => item[1].type === actionType)\r\n .map(item => {\r\n const itemData = item[1]\r\n return [\r\n itemData.slug,\r\n itemData\r\n ]\r\n })\r\n )\r\n\r\n // Get conditions\r\n // Conditions are duplicated in the ConditionManager and the name scaled conditions is suffixed with ' 1'\r\n const conditions = [...game.pf2e.ConditionManager.conditions]\r\n .filter(([conditionId]) => !conditionId.startsWith('Compendium'))\r\n .map(([conditionId, conditionData]) => {\r\n conditionData.name = conditionData.name.replace(' 1', '')\r\n return [conditionId, conditionData]\r\n })\r\n\r\n // Build actions\r\n const actions = await Promise.all(\r\n conditions.map(async ([conditionId, conditionData]) => {\r\n const id = conditionData.slug\r\n const activeCondition = activeConditions.get(conditionId)\r\n const activeConditionId = activeCondition?.id\r\n const name = conditionData.name\r\n const actionTypeName = `${coreModule.api.Utils.i18n(ACTION_TYPE[actionType])}: ` ?? ''\r\n const listName = `${actionTypeName}${name}`\r\n const encodedValue = [actionType, id].join(this.delimiter)\r\n const img = coreModule.api.Utils.getImage(conditionData)\r\n const active = activeConditionId ? ' active' : ''\r\n const cssClass = `toggle${active}`\r\n let info1 = ''\r\n let attributeValue = ''\r\n\r\n if (activeConditionId) {\r\n if (limitedConditions.includes(activeCondition.slug)) {\r\n const attribute = this.actor.system.attributes[activeCondition.slug]\r\n attributeValue = attribute.value\r\n const max = attribute.max\r\n info1 = { text: (max > 0) ? `${attributeValue ?? 0}/${max}` : '' }\r\n } else if (activeCondition.system.value.isValued) {\r\n attributeValue = activeCondition.system.value.value\r\n info1 = { text: attributeValue }\r\n }\r\n }\r\n\r\n const tooltipName = `${name}${(attributeValue) ? ` ${attributeValue}` : ''}`\r\n const tooltipData = {\r\n name: tooltipName,\r\n description: conditionData.description\r\n }\r\n const tooltip = await this.#getTooltip(actionType, tooltipData)\r\n\r\n return {\r\n id,\r\n name,\r\n listName,\r\n encodedValue,\r\n cssClass,\r\n img,\r\n info1,\r\n tooltip\r\n }\r\n })\r\n )\r\n\r\n actions.sort((a, b) => a.name.localeCompare(b.name))\r\n\r\n // Create group data\r\n const conditionsGroupData = { id: 'conditions', type: 'system' }\r\n const actionsTemp = []\r\n\r\n // Add conditions to the group\r\n await this.addActions(\r\n actions.reduce(function (acc, current) {\r\n const filter = ['Friendly', 'Helpful', 'Hostile', 'Indifferent', 'Malevolence', 'Unfriendly']\r\n if (!filter.includes(current.name)) {\r\n acc.push(current)\r\n } else {\r\n actionsTemp.push(current)\r\n }\r\n return acc\r\n }, []),\r\n conditionsGroupData\r\n )\r\n\r\n const socialGroupData = { id: 'social-conditions', type: 'system' }\r\n const actionsOther = []\r\n\r\n // Add social conditions to the group\r\n await this.addActions(\r\n actionsTemp.reduce(function (acc, current) {\r\n const filter = ['Friendly', 'Helpful', 'Hostile', 'Indifferent', 'Unfriendly']\r\n if (filter.includes(current.name)) {\r\n acc.push(current)\r\n } else {\r\n actionsOther.push(current)\r\n }\r\n return acc\r\n }, []),\r\n socialGroupData\r\n )\r\n\r\n const otherGroupData = { id: 'other-conditions', type: 'system' }\r\n\r\n // Add other conditions to the group\r\n await this.addActions(actionsOther, otherGroupData)\r\n }\r\n\r\n /**\r\n * Build hero points\r\n */\r\n async #buildHeroPoints () {\r\n const actionType = 'heroPoints'\r\n\r\n // Create group data\r\n const groupData = { id: 'hero-points', type: 'system' }\r\n\r\n const heroPoints = this.actor.system.resources?.heroPoints\r\n const value = heroPoints.value\r\n const max = heroPoints.max\r\n\r\n // Get actions\r\n const actions = [{\r\n id: 'heroPoints',\r\n name: coreModule.api.Utils.i18n('PF2E.HeroPointsLabel'),\r\n encodedValue: [actionType, actionType].join(this.delimiter),\r\n info1: { text: (max > 0) ? `${value ?? 0}/${max}` : '' }\r\n }]\r\n\r\n // Add actions to action list\r\n this.addActions(actions, groupData)\r\n }\r\n\r\n /**\r\n * Build effects\r\n * @private\r\n */\r\n async #buildEffects () {\r\n const actionType = 'effect'\r\n\r\n // Get effects\r\n const items = new Map([...this.items]\r\n .filter(item =>\r\n item[1].type === 'effect' &&\r\n ((!(item[1].system?.unidentified ?? false) &&\r\n !(item[1].unidentified ?? false)) || game.user.isGM)))\r\n\r\n // Create group data\r\n const groupData = { id: 'effects', type: 'system' }\r\n\r\n const actions = await Promise.all(\r\n [...items].map(async ([_$, itemData]) => {\r\n const id = this.#getActionId(itemData)\r\n const name = this.#getActionName(itemData)\r\n const listName = this.#getActionListName(itemData, actionType)\r\n const cssClass = this.#getActionCss(itemData)\r\n const encodedValue = [actionType, id].join(this.delimiter)\r\n const icon1 = this.#getIcon1(itemData, actionType)\r\n const img = coreModule.api.Utils.getImage(itemData)\r\n const info = this.#getItemInfo(itemData)\r\n const tooltipData = {\r\n name,\r\n description: itemData.description\r\n }\r\n const tooltip = await this.#getTooltip(actionType, tooltipData)\r\n return {\r\n id,\r\n name,\r\n listName,\r\n encodedValue,\r\n cssClass,\r\n img,\r\n icon1,\r\n info,\r\n tooltip\r\n }\r\n })\r\n )\r\n\r\n actions.sort((a, b) => a.name.localeCompare(b.name))\r\n\r\n // Add actions to action list\r\n this.addActions(actions, groupData)\r\n }\r\n\r\n /**\r\n * Build Feats\r\n * @private\r\n */\r\n async #buildFeats () {\r\n const actionType = 'feat'\r\n const featTypes = {\r\n ancestryfeature: 'ancestry-features',\r\n classfeature: 'class-features',\r\n ancestry: 'ancestry-feats',\r\n class: 'class-feats',\r\n skill: 'skill-feats',\r\n general: 'general-feats',\r\n bonus: 'bonus-feats'\r\n }\r\n\r\n // Get feats\r\n const featsMap = new Map()\r\n\r\n for (const [key, value] of this.items) {\r\n if (value.type !== 'feat') continue\r\n // 'featType' changed to 'system.category' post pf2e 4.10+\r\n const featType = value.system?.category ?? value.featType\r\n const groupId = featTypes[featType]\r\n\r\n featsMap.set(groupId, featsMap.get(groupId) || new Map())\r\n featsMap.get(groupId).set(key, value)\r\n }\r\n\r\n for (const [key, value] of featsMap) {\r\n const groupId = key\r\n const items = value\r\n\r\n // Get group data\r\n const groupData = { id: groupId, type: 'system' }\r\n\r\n const actions = await Promise.all(\r\n [...items].map(async ([_, itemData]) => {\r\n const id = this.#getActionId(itemData)\r\n const name = this.#getActionName(itemData)\r\n const listName = this.#getActionListName(itemData, actionType)\r\n const cssClass = this.#getActionCss(itemData)\r\n const encodedValue = [actionType, id].join(this.delimiter)\r\n const icon1 = this.#getIcon1(itemData, actionType)\r\n const img = coreModule.api.Utils.getImage(itemData)\r\n const info = this.#getItemInfo(itemData)\r\n const tooltipData = await this.#getTooltipData(itemData, actionType)\r\n const tooltip = await this.#getTooltip(actionType, tooltipData)\r\n return {\r\n id,\r\n name,\r\n encodedValue,\r\n cssClass,\r\n img,\r\n icon1,\r\n info,\r\n listName,\r\n tooltip\r\n }\r\n })\r\n )\r\n\r\n // Add actions to action list\r\n this.addActions(actions, groupData)\r\n }\r\n }\r\n\r\n /**\r\n * Build Hero Actions\r\n * @private\r\n */\r\n async #buildHeroActions () {\r\n if (!game.modules.get('pf2e-hero-actions')?.active) return\r\n\r\n const actionType = 'heroAction'\r\n const actionTypeName = coreModule.api.Utils.i18n(ACTION_TYPE[actionType])\r\n const heroActions = this.actor.getFlag('pf2e-hero-actions', 'heroActions') ?? []\r\n\r\n const groupData = { id: 'hero-actions', type: 'system' }\r\n\r\n const actions = []\r\n\r\n const heroPoints = this.actor.heroPoints?.value\r\n const remainingHeroPoints = heroPoints - (heroActions?.length ?? 0)\r\n\r\n if (remainingHeroPoints > 0) {\r\n actions.push({\r\n id: 'drawHeroActions',\r\n name: game.i18n.format('pf2e-hero-actions.templates.heroActions.draw', { nb: remainingHeroPoints }),\r\n listName: `${actionTypeName}: ${game.i18n.localize('pf2e-hero-actions.templates.heroActions.draw').replace('({nb}) ', '')}`,\r\n encodedValue: [actionType, 'drawHeroActions'].join(this.delimiter)\r\n })\r\n }\r\n\r\n const heroActionActions = await Promise.all(\r\n [...heroActions].map(async (heroAction) => {\r\n const id = heroAction?.uuid\r\n const name = heroAction?.name\r\n const listName = `${actionTypeName}: ${name}`\r\n const encodedValue = [actionType, id].join(this.delimiter)\r\n const img = coreModule.api.Utils.getImage('systems/pf2e/icons/actions/Passive.webp')\r\n const uuidData = (heroAction?.uuid) ? await fromUuid(heroAction?.uuid) : null\r\n const tooltipData = {\r\n name,\r\n description: uuidData?.text?.content ?? null\r\n }\r\n const tooltip = await this.#getTooltip(actionType, tooltipData)\r\n return {\r\n id,\r\n name,\r\n encodedValue,\r\n img,\r\n listName,\r\n tooltip\r\n }\r\n })\r\n )\r\n\r\n actions.push(...heroActionActions)\r\n\r\n // Add actions to action list\r\n this.addActions(actions, groupData)\r\n }\r\n\r\n /**\r\n * Build initiative\r\n * @private\r\n */\r\n async #buildInitiative () {\r\n const actionType = 'initiative'\r\n\r\n const initiativeStatistic = this.actor?.system?.initiative?.statistic ?? null\r\n\r\n // Get actions\r\n const actions = []\r\n\r\n if (this.actorType !== 'hazard') {\r\n const initiative = this.actor ? this.actor.system.initiative : coreModule.api.Utils.i18n('PF2E.PerceptionLabel')\r\n const fullName = coreModule.api.Utils.i18n('PF2E.PerceptionLabel')\r\n const name = this.abbreviatedSkills ? SKILL_ABBREVIATION.perception ?? fullName : fullName\r\n const actionTypeName = `${coreModule.api.Utils.i18n(ACTION_TYPE[actionType])}: ` ?? ''\r\n const listName = `${actionTypeName}${name}`\r\n const encodedValue = [actionType, 'perception'].join(this.delimiter)\r\n const active = (initiativeStatistic === 'perception') ? ' active' : ''\r\n const cssClass = `toggle${active}`\r\n const modifier = coreModule.api.Utils.getModifier(initiative?.totalModifier)\r\n const info1 = this.actor ? { text: modifier } : ''\r\n const tooltipName = `${fullName}${(this.actor && modifier) ? ` ${modifier}` : ''}`\r\n const tooltipData = {\r\n name: tooltipName,\r\n modifiers: initiative?.modifiers\r\n }\r\n const tooltip = this.actor ? await this.#getTooltip(actionType, tooltipData) : null\r\n\r\n // Get actions\r\n actions.push({\r\n id: 'initiative-perception',\r\n name,\r\n listName,\r\n encodedValue,\r\n cssClass,\r\n info1,\r\n tooltip\r\n })\r\n }\r\n\r\n // Get skills\r\n const skills = (this.actor)\r\n ? Object.entries(this.actor.skills).filter(([_, skillData]) => !!skillData.label && skillData.label.length > 1)\r\n : this.#getSharedSkills()\r\n\r\n if (!skills) return\r\n\r\n const coreSkills = []\r\n const loreSkills = []\r\n\r\n for (const skill of skills) {\r\n if (!skill[1].lore) {\r\n coreSkills.push(skill)\r\n } else {\r\n loreSkills.push(skill)\r\n }\r\n }\r\n\r\n coreSkills.sort((a, b) => a[1].label.localeCompare(b[1].label))\r\n loreSkills.sort((a, b) => a[1].label.localeCompare(b[1].label))\r\n\r\n const skillActions = await Promise.all(\r\n [...coreSkills, ...loreSkills].map(async ([skillId, skillData]) => {\r\n const id = `initiative-${skillId}`\r\n const data = skillData\r\n const fullName = coreModule.api.Utils.i18n(data.label) ?? coreModule.api.Utils.i18n(CONFIG.PF2E.skillList[skillId])\r\n const name = this.abbreviatedSkills ? SKILL_ABBREVIATION[data.slug] ?? fullName : fullName\r\n const actionTypeName = `${coreModule.api.Utils.i18n(ACTION_TYPE[actionType])}: ` ?? ''\r\n const listName = `${actionTypeName}${name}`\r\n const encodedValue = [actionType, skillId].join(this.delimiter)\r\n const active = (initiativeStatistic === skillId) ? ' active' : ''\r\n const cssClass = `toggle${active}`\r\n const modifier = coreModule.api.Utils.getModifier(skillData.check?.mod)\r\n const info1 = this.actor ? { text: modifier } : ''\r\n const tooltipName = `${fullName}${(this.actor && modifier) ? ` ${modifier}` : ''}`\r\n const tooltipData = {\r\n name: tooltipName,\r\n modifiers: skillData?.modifiers\r\n }\r\n const tooltip = (this.actor) ? await this.#getTooltip(actionType, tooltipData) : null\r\n\r\n return {\r\n id,\r\n name,\r\n listName,\r\n encodedValue,\r\n cssClass,\r\n info1,\r\n tooltip\r\n }\r\n })\r\n )\r\n\r\n actions.push(...skillActions)\r\n\r\n // Create group data\r\n const groupData = { id: 'initiative', type: 'system' }\r\n\r\n // Add actions to action list\r\n this.addActions(actions, groupData)\r\n }\r\n\r\n /**\r\n * Build inventory\r\n * @private\r\n */\r\n async #buildInventory () {\r\n // Exit if no items exist\r\n if (this.items.size === 0) return\r\n\r\n const actionType = 'item'\r\n const inventoryMap = new Map()\r\n\r\n for (const [key, value] of this.items) {\r\n const hasQuantity = value.system?.quantity > 0\r\n const isEquippedItem = this.#isEquippedItem(value)\r\n const isAddItem = this.#isAddItem('nonContainer', value)\r\n const type = value.type\r\n\r\n if (hasQuantity && isAddItem) {\r\n const itemType = isEquippedItem ? 'equipped' : 'unequipped'\r\n const itemCategoryMap = inventoryMap.get(itemType) ?? new Map()\r\n itemCategoryMap.set(key, value)\r\n inventoryMap.set(itemType, itemCategoryMap)\r\n\r\n if (isEquippedItem) {\r\n const categoryTypeMap = inventoryMap.get(type) ?? new Map()\r\n categoryTypeMap.set(key, value)\r\n inventoryMap.set(type, categoryTypeMap)\r\n }\r\n }\r\n }\r\n\r\n // Loop through inventory group ids\r\n for (const [id, items] of inventoryMap) {\r\n const groupId = ITEM_TYPE[id]?.groupId\r\n\r\n if (!groupId) continue\r\n\r\n // Create group data\r\n const groupData = { id: groupId, type: 'system' }\r\n\r\n // Get actions\r\n const actions = await Promise.all(\r\n [...items].map(async ([_, itemData]) => {\r\n const id = this.#getActionId(itemData)\r\n const name = this.#getActionName(itemData)\r\n const listName = this.#getActionListName(itemData, actionType)\r\n const cssClass = this.#getActionCss(itemData)\r\n const encodedValue = [actionType, id].join(this.delimiter)\r\n const icon1 = this.#getIcon1(itemData, actionType)\r\n const icon2 = this.#getCarryTypeIcon(itemData)\r\n const img = coreModule.api.Utils.getImage(itemData)\r\n const info = this.#getItemInfo(itemData)\r\n const tooltipData = await this.#getTooltipData(itemData, actionType)\r\n const tooltip = await this.#getTooltip(actionType, tooltipData)\r\n\r\n return {\r\n id,\r\n name,\r\n encodedValue,\r\n cssClass,\r\n img,\r\n icon1,\r\n icon2,\r\n info,\r\n listName,\r\n tooltip\r\n }\r\n })\r\n )\r\n\r\n // Add actions to action list\r\n this.addActions(actions, groupData)\r\n }\r\n\r\n // Add container contents\r\n if (inventoryMap.has('backpack')) {\r\n // Create parent group data\r\n const parentGroupData = { id: 'containers', type: 'system' }\r\n\r\n const containers = inventoryMap.get('backpack')\r\n\r\n for (const [id, container] of containers) {\r\n const contents = container.contents\r\n\r\n // Skip if container has no contents\r\n if (!contents.size) continue\r\n\r\n // Create group data\r\n const groupData = {\r\n id,\r\n name: container.name,\r\n listName: `Group: ${container.name}`,\r\n type: 'system-derived'\r\n }\r\n\r\n // Add group to action list\r\n await this.addGroup(groupData, parentGroupData)\r\n\r\n const contentsMap = new Map()\r\n\r\n for (const content of contents) {\r\n const isAddItem = this.#isAddItem('container', content)\r\n\r\n if (isAddItem) {\r\n contentsMap.set(content.id, content)\r\n }\r\n }\r\n\r\n const actions = await Promise.all(\r\n [...contentsMap].map(async ([_, itemData]) => {\r\n const id = this.#getActionId(itemData)\r\n const name = this.#getActionName(itemData)\r\n const listName = this.#getActionListName(itemData, actionType)\r\n const cssClass = this.#getActionCss(itemData)\r\n const encodedValue = [actionType, id].join(this.delimiter)\r\n const icon1 = this.#getIcon1(itemData, actionType)\r\n const icon2 = this.#getCarryTypeIcon(itemData)\r\n const img = coreModule.api.Utils.getImage(itemData)\r\n const info = this.#getItemInfo(itemData)\r\n const tooltipData = await this.#getTooltipData(itemData, actionType)\r\n const tooltip = await this.#getTooltip(actionType, tooltipData)\r\n\r\n return {\r\n id,\r\n name,\r\n encodedValue,\r\n cssClass,\r\n img,\r\n icon1,\r\n icon2,\r\n info,\r\n listName,\r\n tooltip\r\n }\r\n })\r\n )\r\n\r\n // Add actions to action list\r\n this.addActions(actions, groupData)\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Build perception check\r\n * @private\r\n */\r\n async #buildPerceptionCheck () {\r\n const actionType = 'perceptionCheck'\r\n const perception = this.actor ? this.actor.system.perception : coreModule.api.Utils.i18n('PF2E.PerceptionLabel')\r\n const name = coreModule.api.Utils.i18n('PF2E.PerceptionLabel')\r\n const modifier = coreModule.api.Utils.getModifier(perception?.totalModifier)\r\n const info1 = this.actor ? { text: modifier } : ''\r\n const tooltipName = `${name}${(this.actor && modifier) ? ` ${modifier}` : ''}`\r\n const tooltipData = {\r\n name: tooltipName,\r\n modifiers: perception?.modifiers\r\n }\r\n const tooltip = await this.#getTooltip(actionType, tooltipData)\r\n\r\n // Get actions\r\n const actions = [{\r\n id: 'perception',\r\n name,\r\n encodedValue: [actionType, 'perception'].join(this.delimiter),\r\n info1,\r\n tooltip\r\n }]\r\n\r\n // Create group data\r\n const groupData = { id: 'perception-check', type: 'system' }\r\n\r\n // Add actions to action list\r\n this.addActions(actions, groupData)\r\n }\r\n\r\n /**\r\n * Build recovery check\r\n */\r\n #buildRecoveryCheck () {\r\n const actionType = 'recoveryCheck'\r\n const dyingValue = this.actor?.system.attributes?.dying\r\n\r\n if (dyingValue?.value > 0) {\r\n // Get actions\r\n const actions = [{\r\n id: actionType,\r\n name: coreModule.api.Utils.i18n('PF2E.Check.Specific.Recovery'),\r\n encodedValue: [actionType, actionType].join(this.delimiter)\r\n }]\r\n\r\n // Create group data\r\n const groupData = { id: 'recovery-check', type: 'system' }\r\n\r\n // Add actions to action list\r\n this.addActions(actions, groupData)\r\n }\r\n }\r\n\r\n /**\r\n * Build rests\r\n */\r\n #buildRests () {\r\n // Exit if multiple actors and not every actor is the character type\r\n if (!this.actor && !this.actors.every(actor => actor.type === 'character')) return\r\n\r\n const actionType = 'utility'\r\n\r\n // Get actions\r\n const actions = [\r\n {\r\n id: 'treatWounds',\r\n name: coreModule.api.Utils.i18n('PF2E.Actions.TreatWounds.Label'),\r\n encodedValue: [actionType, 'treatWounds'].join(this.delimiter)\r\n },\r\n {\r\n id: 'rest',\r\n name: coreModule.api.Utils.i18n('PF2E.Actor.Character.Rest.Label'),\r\n encodedValue: [actionType, 'rest'].join(this.delimiter)\r\n }\r\n ]\r\n\r\n // Take a Breather\r\n if (game.settings.get('pf2e', 'staminaVariant')) {\r\n actions.push({\r\n id: 'takeBreather',\r\n name: coreModule.api.Utils.i18n('tokenActionHud.pf2e.takeBreather'),\r\n encodedValue: [actionType, 'takeBreather'].join(this.delimiter)\r\n })\r\n }\r\n\r\n // Create group data\r\n const groupData = { id: 'rests', type: 'system' }\r\n\r\n // Add actions to action list\r\n this.addActions(actions, groupData)\r\n }\r\n\r\n /**\r\n * Build saves\r\n * @private\r\n */\r\n async #buildSaves () {\r\n const actionType = 'save'\r\n\r\n // Get saves\r\n const saves = this.actor ? Object.entries(this.actor.saves || []) : Object.entries(CONFIG.PF2E.saves)\r\n\r\n // Exit if no saves exist\r\n if (!saves || saves.length === 0) return\r\n\r\n // Get actions\r\n const actions = await Promise.all(\r\n saves.map(async ([id, saveData]) => {\r\n const name = saveData.label ?? (typeof saveData === 'string' ? coreModule.api.Utils.i18n(saveData) : '')\r\n const actionTypeName = `${coreModule.api.Utils.i18n(ACTION_TYPE[actionType])}: ` ?? ''\r\n const listName = `${actionTypeName}${name}`\r\n const encodedValue = [actionType, id].join(this.delimiter)\r\n const modifier = coreModule.api.Utils.getModifier(saveData.mod)\r\n const info1 = this.actor ? { text: modifier } : ''\r\n const tooltipName = `${name}${(this.actor && modifier) ? ` ${modifier}` : ''}`\r\n const tooltipData = {\r\n name: tooltipName,\r\n modifiers: saveData?.modifiers\r\n }\r\n const tooltip = this.actor ? await this.#getTooltip(actionType, tooltipData) : null\r\n\r\n return {\r\n id,\r\n name,\r\n listName,\r\n encodedValue,\r\n info1,\r\n tooltip\r\n }\r\n })\r\n )\r\n\r\n // Get group data\r\n const groupData = { id: 'saves', type: 'system' }\r\n\r\n // Add actions to action list\r\n this.addActions(actions, groupData)\r\n }\r\n\r\n /**\r\n * Build skill actions\r\n * @private\r\n */\r\n async #buildSkillActions () {\r\n const actionType = 'compendiumMacro'\r\n\r\n // Get skill actions\r\n const actionMacros = await game.packs.get('pf2e.action-macros').getIndex()\r\n\r\n if (!actionMacros.size) return\r\n\r\n const skillActionsMap = new Map()\r\n\r\n // Get actions\r\n const actions = []\r\n for (const actionMacro of actionMacros) {\r\n const skillAction = SKILL_ACTION[actionMacro._id]\r\n\r\n if (!skillAction) continue\r\n\r\n const id = actionMacro._id\r\n const actionName = coreModule.api.Utils.i18n(skillAction.name)\r\n const skillName = coreModule.api.Utils.i18n(SKILL[skillAction.skill]?.name)\r\n const name = `${actionName} - ${skillName}`\r\n const actionTypeName = `${coreModule.api.Utils.i18n(ACTION_TYPE.skillAction)}: ` ?? ''\r\n const listName = `${actionTypeName}${name}`\r\n const encodedValue = [actionType, 'pf2e.action-macros', id].join(this.delimiter)\r\n const icon1 = this.#getActionIcon(skillAction.actionCost)\r\n const img = skillAction.image\r\n const modifier = coreModule.api.Utils.getModifier(this.actor?.skills[skillAction.skill]?.check?.mod)\r\n const info1 = this.actor ? { text: modifier } : null\r\n\r\n const action = {\r\n id,\r\n name,\r\n listName,\r\n encodedValue,\r\n icon1,\r\n img,\r\n info1\r\n }\r\n\r\n actions.push(action)\r\n\r\n skillActionsMap.set(skillAction.skill, skillActionsMap.get(skillAction.skill) || new Map())\r\n skillActionsMap.get(skillAction.skill).set(actionMacro._id, { ...action, name: actionName })\r\n }\r\n\r\n // Add actions to HUD\r\n await this.addActions(actions, { id: 'skill-actions-ungrouped', type: 'system' })\r\n\r\n for (const [key, value] of Object.entries(SKILL)) {\r\n const groupId = key\r\n const groupName = coreModule.api.Utils.i18n(value.name)\r\n const skillActions = skillActionsMap.get(groupId)\r\n\r\n if (!skillActions) continue\r\n\r\n // Create group data\r\n const groupData = { id: groupId, name: groupName, type: 'system-derived' }\r\n\r\n // Add group to HUD\r\n await this.addGroup(groupData, { id: 'skill-actions-grouped', type: 'system' })\r\n\r\n // Get actions\r\n const actions = [...skillActions].map(([_, skillAction]) => {\r\n return skillAction\r\n })\r\n\r\n // Add actions to HUD\r\n await this.addActions(actions, groupData)\r\n }\r\n }\r\n\r\n /**\r\n * Build skills\r\n * @private\r\n */\r\n async #buildSkills () {\r\n const actionType = 'skill'\r\n\r\n // Get skills\r\n const skills = (this.actor)\r\n ? Object.entries(this.actor.skills).filter(skill => !!skill[1].label && skill[1].label.length > 1)\r\n : this.#getSharedSkills()\r\n\r\n if (!skills) return\r\n\r\n const coreSkills = []\r\n const loreSkills = []\r\n\r\n for (const skill of skills) {\r\n if (!skill[1].lore) {\r\n coreSkills.push(skill)\r\n } else {\r\n loreSkills.push(skill)\r\n }\r\n }\r\n\r\n coreSkills.sort((a, b) => a[1].label.localeCompare(b[1].label))\r\n loreSkills.sort((a, b) => a[1].label.localeCompare(b[1].label))\r\n\r\n const skillsMap = new Map()\r\n\r\n skillsMap.set('skills', new Map())\r\n\r\n if (coreSkills.length > 0) {\r\n skillsMap.set('core-skills', new Map())\r\n }\r\n if (loreSkills.length > 0) {\r\n skillsMap.set('lore-skills', new Map())\r\n }\r\n\r\n for (const skill of [...coreSkills, ...loreSkills]) {\r\n if (!skill[1].lore) {\r\n skillsMap.get('core-skills').set(skill[0], skill[1])\r\n } else {\r\n skillsMap.get('lore-skills').set(skill[0], skill[1])\r\n }\r\n }\r\n\r\n // Loop through inventory subcateogry ids\r\n for (const [key, value] of skillsMap) {\r\n const groupId = key\r\n const skills = value\r\n\r\n // Create group data\r\n const groupData = { id: groupId, type: 'system' }\r\n\r\n // Get actions\r\n const actions = await Promise.all(\r\n [...skills].map(async ([skillId, skillData]) => {\r\n const id = skillId\r\n const label = coreModule.api.Utils.i18n(skillData.label) ?? coreModule.api.Utils.i18n(CONFIG.PF2E.skillList[skillId])\r\n const name = this.abbreviatedSkills ? SKILL_ABBREVIATION[skillData.slug] ?? label : label\r\n const fullName = label\r\n const actionTypeName = `${coreModule.api.Utils.i18n(ACTION_TYPE[actionType])}: ` ?? ''\r\n const listName = `${actionTypeName}${name}`\r\n const encodedValue = [actionType, id].join(this.delimiter)\r\n const cssClass = (this.actor && this.colorSkills && skillData.rank > 0) ? `tah-pf2e-skill-rank-${skillData.rank}` : ''\r\n const modifier = coreModule.api.Utils.getModifier(skillData.check?.mod)\r\n const info1 = this.actor ? { text: modifier } : ''\r\n const tooltipName = `${fullName}${(this.actor && modifier) ? ` ${modifier}` : ''}`\r\n const tooltipData = {\r\n name: tooltipName,\r\n modifiers: skillData?.modifiers\r\n }\r\n const tooltip = (this.actor) ? await this.#getTooltip(actionType, tooltipData) : null\r\n\r\n return {\r\n id,\r\n name,\r\n fullName,\r\n listName,\r\n encodedValue,\r\n cssClass,\r\n info1,\r\n tooltip\r\n }\r\n })\r\n )\r\n\r\n // Add actions to action list\r\n this.addActions(actions, groupData)\r\n }\r\n }\r\n\r\n /**\r\n * Get shared skills between all actors\r\n * @returns {object}\r\n */\r\n #getSharedSkills () {\r\n if (!this.actors) return\r\n const allSkillSets = this.actors.map(actor => Object.entries(actor.skills).filter(skill => !!skill[1].label && skill[1].label.length > 1))\r\n const minSkillSetSize = Math.min(...allSkillSets.map(skillSet => skillSet.length))\r\n const smallestSkillSet = allSkillSets.find(skillSet => skillSet.length === minSkillSetSize)\r\n return smallestSkillSet.filter(smallestSkill => allSkillSets.every(skillSet => skillSet.some(skill => skill[0] === smallestSkill[0])))\r\n }\r\n\r\n /**\r\n * Build spells\r\n * @private\r\n */\r\n async #buildSpells () {\r\n const actionType = 'spell'\r\n\r\n // Create parent group data\r\n const parentGroupData = { id: 'spells', type: 'system' }\r\n\r\n const spellcastingEntries = [...this.items].filter(item => item[1].type === 'spellcastingEntry')\r\n\r\n for (const spellcastingEntry of spellcastingEntries) {\r\n const spellbookGroupId = `spells+${spellcastingEntry[1].name.slugify({ replacement: '-', strict: true })}`\r\n const spellbookGroupName = spellcastingEntry[1].name\r\n const spellbookInfo1 = this.#getSpellDcInfo(spellcastingEntry[1])\r\n\r\n // Create book group data\r\n const bookGroupData = {\r\n id: spellbookGroupId,\r\n name: spellbookGroupName,\r\n type: 'system-derived',\r\n info1: spellbookInfo1\r\n }\r\n\r\n // Add group to action list\r\n await this.addGroup(bookGroupData, parentGroupData)\r\n\r\n // Add spell slot info to group\r\n this.addGroupInfo(bookGroupData)\r\n\r\n const spellInfo = await (spellcastingEntry[1].getSpellData ? spellcastingEntry[1].getSpellData() : spellcastingEntry[1].getSheetData())\r\n const activeLevels = spellInfo.groups.filter(level => level.active.length > 0)\r\n\r\n for (const level of Object.entries(activeLevels)) {\r\n const spellLevel = level[1].id\r\n const levelGroupId = `${spellbookGroupId}+${spellLevel}`\r\n const levelGroupName = String(coreModule.api.Utils.i18n(level[1].label))\r\n\r\n // Create level group data\r\n const levelGroupData = {\r\n id: levelGroupId,\r\n name: levelGroupName,\r\n type: 'system-derived'\r\n }\r\n\r\n // Add group to action list\r\n await this.addGroup(levelGroupData, bookGroupData)\r\n\r\n await this.#addSpellSlotInfo(bookGroupData, levelGroupData, level, spellInfo)\r\n\r\n // Get available spells\r\n const activeSpells = level[1].active\r\n .filter(activeSpell => activeSpell && !activeSpell.expended)\r\n .map(spell => spell.spell)\r\n\r\n const spells = new Map(activeSpells.map(spell => [spell.id, spell]))\r\n\r\n // Get actions\r\n const actions = await Promise.all(\r\n [...spells].map(async ([_, itemData]) => {\r\n const id = this.#getActionId(itemData, actionType, spellLevel)\r\n const name = this.#getActionName(itemData)\r\n const listName = this.#getActionListName(itemData, actionType)\r\n const cssClass = this.#getActionCss(itemData)\r\n const encodedValue = this.#getActionEncodedValue(itemData, actionType, spellLevel)\r\n const icon1 = this.#getIcon1(itemData, actionType)\r\n const img = coreModule.api.Utils.getImage(itemData)\r\n const tooltipData = await this.#getTooltipData(itemData, actionType, spellLevel)\r\n const tooltip = await this.#getTooltip(actionType, tooltipData)\r\n\r\n return {\r\n id,\r\n name,\r\n listName,\r\n encodedValue,\r\n cssClass,\r\n img,\r\n icon1,\r\n tooltip\r\n }\r\n })\r\n )\r\n\r\n // Add actions to action list\r\n this.addActions(actions, levelGroupData)\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Add spell slot info\r\n * @param {object} bookGroupData The book group data\r\n * @param {object} levelGroupData The level group data\r\n * @param {number} level The level\r\n * @param {object} spellInfo The spell info\r\n */\r\n async #addSpellSlotInfo (\r\n bookGroupData,\r\n levelGroupData,\r\n level,\r\n spellInfo\r\n ) {\r\n const isCantrip = level[1].id === 'cantrips'\r\n const isFlexible = spellInfo.isFlexible\r\n const isFocusPool = spellInfo.isFocusPool\r\n const isInnate = spellInfo.isInnate\r\n const isPrepared = spellInfo.isPrepared\r\n\r\n // Exit if spells are cantrips\r\n if (!isFocusPool && (isCantrip || isInnate)) return\r\n\r\n if (!isFocusPool && (isPrepared && !isFlexible)) return\r\n\r\n const actionType = 'spellSlot'\r\n const focus = this.actor.system.resources.focus\r\n const slots = level[1].uses\r\n const spellSlot = (isFocusPool) ? 'focus' : `slot${level[1].id}`\r\n const maxSlots = (spellSlot === 'focus') ? focus?.max : slots?.max\r\n const availableSlots = (spellSlot === 'focus') ? focus?.value : slots?.value\r\n const info1 = { text: (maxSlots >= 0) ? `${availableSlots ?? 0}/${maxSlots}` : '' }\r\n\r\n levelGroupData.info = { info1 }\r\n\r\n // Add group info to the group\r\n this.addGroupInfo(levelGroupData)\r\n\r\n const actionTypeName = coreModule.api.Utils.i18n(ACTION_TYPE.spell)\r\n\r\n // Get actions\r\n const actions = [\r\n {\r\n id: `${spellInfo.id}>${spellSlot}>slotIncrease`,\r\n name: '+',\r\n listName: `${actionTypeName}: ${bookGroupData.name}: ${levelGroupData.name}: +`,\r\n encodedValue: [actionType, `${spellInfo.id}>${spellSlot}>slotIncrease`].join(this.delimiter),\r\n cssClass: 'shrink'\r\n },\r\n {\r\n id: `${spellInfo.id}>${spellSlot}>slotDecrease`,\r\n name: '-',\r\n listName: `${actionTypeName}: ${bookGroupData.name}: ${levelGroupData.name}: -`,\r\n encodedValue: [actionType, `${spellInfo.id}>${spellSlot}>slotDecrease`].join(this.delimiter),\r\n cssClass: 'shrink'\r\n }\r\n ]\r\n\r\n // Add actions to action list\r\n this.addActions(actions, levelGroupData)\r\n }\r\n\r\n /**\r\n * Build elemental blasts\r\n */\r\n async #buildElementalBlasts () {\r\n const actionType = 'elementalBlast'\r\n\r\n // Get elemental blasts\r\n const blasts = new game.pf2e.ElementalBlast(this.actor)?.configs\r\n\r\n // Exit if no strikes exist\r\n if (!blasts.length) return\r\n\r\n // Create parent group data\r\n const parentGroupData = { id: 'strikes', type: 'system' }\r\n\r\n for (const blast of blasts) {\r\n let damageTypeActions = []\r\n let strikeGroupData = null\r\n const usageData = []\r\n\r\n const strikeId = `${blast.item.id}-${blast.element}`\r\n const strikeGroupId = `strikes+${strikeId}`\r\n const strikeGroupName = (() => {\r\n let groupName = coreModule.api.Utils.i18n(blast.label)\r\n if (this.showStrikeTraits && this.showStrikeNames) {\r\n const blastTraits = blast.item.system.traits.value\r\n if (blastTraits.length > 0) {\r\n groupName += ' - '\r\n for (const trait of blastTraits) {\r\n groupName += '[' + trait + ']'\r\n }\r\n }\r\n }\r\n return groupName\r\n })()\r\n const strikeGroupListName = `${coreModule.api.Utils.i18n(ACTION_TYPE.strike)}: ${strikeGroupName} (${blast.item.id})`\r\n const image = blast.img ?? blast.item?.img\r\n const showTitle = this.showStrikeNames\r\n const tooltipData = await this.#getTooltipData(blast, actionType)\r\n const tooltip = await this.#getTooltip(actionType, tooltipData)\r\n\r\n // Create group data\r\n strikeGroupData = { id: strikeGroupId, name: strikeGroupName, listName: strikeGroupListName, type: 'system-derived', settings: { showTitle }, tooltip }\r\n if (this.showStrikeImages) {\r\n strikeGroupData.settings.image = image\r\n }\r\n\r\n // Add group to action list\r\n this.addGroup(strikeGroupData, parentGroupData)\r\n\r\n if (blast.damageTypes.length > 1) {\r\n // Get actions\r\n damageTypeActions = blast.damageTypes.map((damageType, index) => {\r\n const id = encodeURIComponent(`${blast.item.id}>${blast.element}>${damageType.value}>`)\r\n const fullName = damageType.label\r\n return {\r\n id,\r\n name: '',\r\n fullName,\r\n listName: `${strikeGroupListName}: ${fullName}`,\r\n encodedValue: ['elementalBlastDamageType', id].join(this.delimiter),\r\n cssClass: this.#getActionCss(damageType),\r\n icon1: this.#getActionIcon(damageType.icon, fullName)\r\n }\r\n })\r\n }\r\n\r\n const blastUsages = Object.entries(blast.maps) ?? []\r\n\r\n for (const [key, blastUsage] of blastUsages) {\r\n const usage = key\r\n const usageGroupId = `${strikeGroupId}+${key}`\r\n const usageGroupName = (() => {\r\n if (usage !== 'melee' && blast.range.max > 0 && blast.range.label.length > 0) {\r\n return blast.range.label\r\n } else {\r\n return coreModule.api.Utils.i18n(STRIKE_USAGE[key].name)\r\n }\r\n })()\r\n const usageGroupListName = `${strikeGroupListName}: ${usageGroupName}`\r\n const usageGroupImage = (blastUsages.length > 1)\r\n ? (usage === 'melee')\r\n ? STRIKE_ICON.melee\r\n : STRIKE_ICON.thrown\r\n : ''\r\n const usageGroupShowTitle = !((usageGroupImage || blastUsages.length <= 1))\r\n const settings = { showTitle: usageGroupShowTitle, image: usageGroupImage }\r\n\r\n const usageGroupData = {\r\n id: usageGroupId,\r\n name: usageGroupName,\r\n listName: usageGroupListName,\r\n type: 'system-derived',\r\n settings\r\n }\r\n\r\n const rolls = Object.values(blastUsage)\r\n\r\n const actions = rolls.map((roll, index) => {\r\n const id = encodeURIComponent(`${blast.item.id}>${blast.element}>${index}>` + usage)\r\n const isMap = `${roll}`.includes(this.mapLabel)\r\n let modifier\r\n if (isMap) {\r\n modifier = `${roll}`.split(' ')[0]\r\n } else {\r\n modifier = `${roll}`.replace(coreModule.api.Utils.i18n('PF2E.WeaponStrikeLabel'), '').replace(' ', '')\r\n }\r\n const name = (this.calculateAttackPenalty) ? modifier : roll\r\n return {\r\n id,\r\n name,\r\n encodedValue: [actionType, id].join(this.delimiter),\r\n listName: `${usageGroupListName}: ${name}`\r\n }\r\n })\r\n\r\n // Get Damage\r\n const damageId = encodeURIComponent(`${blast.item.id}>${blast.element}>damage>${usage}`)\r\n const damageName = coreModule.api.Utils.i18n('PF2E.DamageLabel')\r\n actions.push({\r\n id: damageId,\r\n name: damageName,\r\n listName: `${usageGroupListName}: ${damageName}`,\r\n encodedValue: [actionType, damageId].join(this.delimiter),\r\n systemSelected: this.addDamageAndCritical\r\n })\r\n\r\n // Get Critical\r\n const criticalId = encodeURIComponent(`${blast.item.id}>${blast.element}>critical>${usage}`)\r\n const criticalName = coreModule.api.Utils.i18n('PF2E.CriticalDamageLabel')\r\n actions.push({\r\n id: criticalId,\r\n name: criticalName,\r\n listName: `${usageGroupListName}: ${criticalName}`,\r\n encodedValue: [actionType, criticalId].join(this.delimiter),\r\n systemSelected: this.addDamageAndCritical\r\n })\r\n\r\n usageData.push({ actions, usageGroupData })\r\n }\r\n\r\n if (this.splitStrikes) {\r\n this.addActions(damageTypeActions, strikeGroupData)\r\n for (const usage of usageData) {\r\n this.addGroup(usage.usageGroupData, strikeGroupData)\r\n this.addActions(usage.actions, usage.usageGroupData)\r\n }\r\n } else {\r\n this.addActions([...(usageData[0]?.actions || []), ...damageTypeActions], strikeGroupData)\r\n usageData.shift()\r\n for (const usage of usageData) {\r\n this.addGroup(usage.usageGroupData, strikeGroupData)\r\n this.addActions(usage.actions, usage.usageGroupData)\r\n }\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Build strikes\r\n */\r\n async #buildStrikes () {\r\n const actionType = 'strike'\r\n\r\n // Create parent group data\r\n const parentGroupData = { id: 'strikes', type: 'system' }\r\n\r\n // Get strikes\r\n const strikes = this.actor.system.actions\r\n .filter(action => (action.type === actionType && (action.item.system.quantity > 0 || this.actor.type === 'hazard' || this.actor.type === 'npc')))\r\n\r\n // Exit if no strikes exist\r\n if (!strikes) return\r\n\r\n for (const strike of strikes) {\r\n let auxiliaryActions = []\r\n let versatileOptionActions = []\r\n let strikeGroupData = null\r\n const usageData = []\r\n\r\n const strikeId = `${strike.item.id}-${strike.slug}`\r\n const strikeGroupId = `strikes+${strikeId}`\r\n const strikeGroupName = (() => {\r\n let groupName = strike.label\r\n if (this.showStrikeTraits && this.showStrikeNames) {\r\n let strikeTraits\r\n if (this.actor.type === 'character') {\r\n strikeTraits = strike.weaponTraits\r\n } else {\r\n strikeTraits = strike.traits\r\n }\r\n if (strikeTraits.length > 0) {\r\n groupName += ' - '\r\n for (const trait of strikeTraits) {\r\n groupName += '[' + trait.label + ']'\r\n }\r\n }\r\n }\r\n return groupName\r\n })()\r\n const strikeGroupListName = `${coreModule.api.Utils.i18n(ACTION_TYPE[actionType])}: ${strike.label} (${strike.item.id})`\r\n const image = strike.imageUrl ?? strike.item?.img\r\n const showTitle = this.showStrikeNames\r\n const tooltipData = await this.#getTooltipData(strike, actionType)\r\n const tooltip = await this.#getTooltip(actionType, tooltipData)\r\n // Create group data\r\n strikeGroupData = { id: strikeGroupId, name: strikeGroupName, listName: strikeGroupListName, type: 'system-derived', settings: { showTitle }, tooltip }\r\n if (this.showStrikeImages) { strikeGroupData.settings.image = image }\r\n if (typeof strikeGroupData.settings.sort === 'undefined' && coreModule.api.Utils.getSetting('sortActions')) strikeGroupData.settings.sort = false\r\n\r\n // Add group to action list\r\n this.addGroup(strikeGroupData, parentGroupData)\r\n\r\n if (strike.auxiliaryActions?.length && this.addAuxiliaryActions) {\r\n // Get actions\r\n auxiliaryActions = strike.auxiliaryActions.flatMap((auxiliaryAction, index) => {\r\n if (auxiliaryAction.purpose === 'Modular') {\r\n const modularOptions = strike.item.system.traits.toggles.modular.options\r\n const modularSelection = strike.item.system.traits.toggles.modular.selection\r\n return modularOptions.map(modularOption => {\r\n const id = encodeURIComponent(`${strike.item.id}>${strike.slug}>${index}>${modularOption}`)\r\n const name = coreModule.api.Utils.i18n(MODULAR_OPTION[modularOption])\r\n return {\r\n id,\r\n name,\r\n listName: `${strikeGroupListName}: ${name}`,\r\n encodedValue: ['strikeAuxiliaryAction', id].join(this.delimiter),\r\n icon1: this.#getActionIcon(auxiliaryAction.glyph),\r\n cssClass: this.#getActionCss({ selected: (modularOption === modularSelection) })\r\n }\r\n })\r\n } else {\r\n const id = encodeURIComponent(`${strike.item.id}>${strike.slug}>${index}>`)\r\n const name = auxiliaryAction.label\r\n return {\r\n id,\r\n name,\r\n listName: `${strikeGroupListName}: ${name}`,\r\n encodedValue: ['strikeAuxiliaryAction', id].join(this.delimiter),\r\n icon1: this.#getActionIcon(auxiliaryAction.glyph),\r\n info: this.#getItemInfo(auxiliaryAction)\r\n }\r\n }\r\n })\r\n }\r\n if (strike.ready) {\r\n if (strike.versatileOptions?.length) {\r\n // Get actions\r\n versatileOptionActions = strike.versatileOptions.map(versatileOption => {\r\n const encodedId = encodeURIComponent(`${strike.item.id}>${strike.slug}>${versatileOption.value}>`)\r\n const fullName = coreModule.api.Utils.i18n(versatileOption.label)\r\n return {\r\n id: encodedId,\r\n name: '',\r\n fullName,\r\n listName: `${strikeGroupListName}: ${fullName}`,\r\n encodedValue: ['versatileOption', encodedId].join(this.delimiter),\r\n cssClass: this.#getActionCss(versatileOption),\r\n icon1: this.#getActionIcon(versatileOption.glyph, fullName)\r\n }\r\n })\r\n }\r\n\r\n const strikeUsages = (strike.altUsages) ? [strike, ...strike.altUsages] : [strike]\r\n\r\n for (const strikeUsage of strikeUsages) {\r\n const glyph = strike.glyph\r\n const encodedUsage = `${strikeUsage.item.isMelee}_${strikeUsage.item.isThrown}_${strikeUsage.item.isRanged}`\r\n let usage\r\n switch (encodedUsage) {\r\n case 'true_false_false':\r\n usage = 'melee'\r\n break\r\n case 'false_true_true':\r\n usage = 'thrown'\r\n break\r\n case 'false_false_true':\r\n usage = 'ranged'\r\n break\r\n }\r\n const usageGroupId = `${strikeGroupId}+${usage}`\r\n const usageGroupName = (strikeUsage.attackRollType)\r\n ? coreModule.api.Utils.i18n(strikeUsage.attackRollType)\r\n : coreModule.api.Utils.i18n(STRIKE_USAGE[usage].name)\r\n const usageGroupListName = `${strikeGroupListName}: ${usageGroupName}`\r\n const usageGroupIcon = (usage !== 'thrown' && glyph)\r\n ? `${glyph}`\r\n : STRIKE_ICON[usage]\r\n const usageGroupImage = (strikeUsages.length > 1) ? STRIKE_ICON[usage] : ''\r\n const usageGroupShowTitle = !((usageGroupImage || strikeUsages.length <= 1))\r\n const settings = { showTitle: usageGroupShowTitle, image: usageGroupImage }\r\n\r\n const usageGroupData = {\r\n id: usageGroupId,\r\n name: usageGroupName,\r\n listName: usageGroupListName,\r\n icon: usageGroupIcon,\r\n type: 'system-derived',\r\n settings\r\n }\r\n\r\n if (typeof usageGroupData.settings.sort === 'undefined' && coreModule.api.Utils.getSetting('sortActions')) {\r\n usageGroupData.settings.sort = false\r\n }\r\n\r\n const actions = strikeUsage.variants.map((variant, index) => {\r\n const id = encodeURIComponent(`${strike.item.id}>${strike.slug}>${index}>` + usage)\r\n const isMap = variant.label.includes(this.mapLabel)\r\n const modifier = (isMap)\r\n ? variant.label.split(' ')[0]\r\n : variant.label.replace(coreModule.api.Utils.i18n('PF2E.WeaponStrikeLabel'), '').replace(' ', '')\r\n const name = (this.calculateAttackPenalty) ? modifier : variant.label\r\n return {\r\n id,\r\n name,\r\n encodedValue: [actionType, id].join(this.delimiter),\r\n listName: `${usageGroupListName}: ${name}`\r\n }\r\n })\r\n\r\n // Get Damage\r\n const damageId = encodeURIComponent(`${strike.item.id}>${strike.slug}>damage>${usage}`)\r\n const damageName = coreModule.api.Utils.i18n('PF2E.DamageLabel')\r\n actions.push({\r\n id: damageId,\r\n name: damageName,\r\n listName: `${usageGroupListName}: ${damageName}`,\r\n encodedValue: [actionType, damageId].join(this.delimiter),\r\n systemSelected: this.addDamageAndCritical\r\n })\r\n\r\n // Get Critical\r\n const criticalId = encodeURIComponent(`${strike.item.id}>${strike.slug}>critical>${usage}`)\r\n const criticalName = coreModule.api.Utils.i18n('PF2E.CriticalDamageLabel')\r\n actions.push({\r\n id: criticalId,\r\n name: criticalName,\r\n listName: `${usageGroupListName}: ${criticalName}`,\r\n encodedValue: [actionType, criticalId].join(this.delimiter),\r\n systemSelected: this.addDamageAndCritical\r\n })\r\n\r\n // Get Ammo\r\n if (strikeUsage.selectedAmmoId && !strikeUsage.ammunition) {\r\n const item = this.actor.items.get(strikeUsage.selectedAmmoId)\r\n\r\n if (!item) {\r\n const id = 'noAmmo'\r\n const name = coreModule.api.Utils.i18n('tokenActionHud.pf2e.noAmmo')\r\n actions.push({\r\n id,\r\n name,\r\n listName: `${usageGroupListName}: ${name}`,\r\n encodedValue: id\r\n })\r\n } else {\r\n const id = this.#getActionId(item)\r\n const name = this.#getActionName(item)\r\n actions.push({\r\n id,\r\n name,\r\n\r\n listName: `${usageGroupListName}: ${name}`,\r\n encodedValue: [actionType, id].join(this.delimiter)\r\n })\r\n }\r\n }\r\n\r\n usageData.push({ actions, usageGroupData })\r\n }\r\n }\r\n\r\n if (this.splitStrikes) {\r\n this.addActions([...versatileOptionActions, ...auxiliaryActions], strikeGroupData)\r\n for (const usage of usageData) {\r\n this.addGroup(usage.usageGroupData, strikeGroupData)\r\n this.addActions(usage.actions, usage.usageGroupData)\r\n }\r\n } else {\r\n this.addActions([...(usageData[0]?.actions || []), ...versatileOptionActions, ...auxiliaryActions], strikeGroupData)\r\n usageData.shift()\r\n for (const usage of usageData) {\r\n this.addGroup(usage.usageGroupData, strikeGroupData)\r\n this.addActions(usage.actions, usage.usageGroupData)\r\n }\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Build toggles\r\n */\r\n #buildToggles () {\r\n const actionType = 'toggle'\r\n\r\n // Get toggles\r\n const toggles = Object.values(this.actor.synthetics.toggles).flatMap(domain => Object.values(domain))\r\n\r\n // Exit if no toggles exist\r\n if (!toggles.length) return\r\n\r\n const togglesWithoutSuboptions = toggles.filter(toggle => toggle.suboptions.length === 0)\r\n const togglesWithSuboptions = toggles.filter(toggle => toggle.suboptions.length !== 0)\r\n\r\n // Create group data\r\n const groupData = { id: 'toggles', type: 'system' }\r\n\r\n // Get actions\r\n const actions = togglesWithoutSuboptions.map(toggle => {\r\n const id = encodeURIComponent(`${toggle.domain}>${toggle.option}>${toggle.itemId}>>`)\r\n const name = coreModule.api.Utils.i18n(toggle.label)\r\n const encodedValue = [actionType, id].join(this.delimiter)\r\n const active = (toggle.checked) ? ' active' : ''\r\n const cssClass = `toggle${active}`\r\n\r\n return { id, encodedValue, name, cssClass }\r\n })\r\n\r\n // Add actions to action list\r\n this.addActions(actions, groupData)\r\n\r\n for (const toggle of togglesWithSuboptions) {\r\n const id = [toggle.domain, toggle.option].join('.')\r\n const subgroupName = coreModule.api.Utils.i18n(toggle.label)\r\n const subgroupListName = `${ACTION_TYPE.toggle}: ${subgroupName}`\r\n const subgroupData = {\r\n id,\r\n name: subgroupName,\r\n listName: subgroupListName,\r\n type: 'system-derived'\r\n }\r\n\r\n this.addGroup(subgroupData, groupData)\r\n\r\n // Get actions\r\n const actions = toggle.suboptions.map(suboption => {\r\n const id = encodeURIComponent(`${toggle.domain}>${toggle.option}>${toggle.itemId}>${suboption.value}`)\r\n const name = coreModule.api.Utils.i18n(suboption.label)\r\n const selected = suboption.selected && toggle.enabled && toggle.checked\r\n\r\n return {\r\n id,\r\n name,\r\n listName: `${subgroupListName}: ${name}`,\r\n encodedValue: ['toggle', id].join(this.delimiter),\r\n cssClass: this.#getActionCss({ selected })\r\n }\r\n })\r\n\r\n // Add actions to action list\r\n this.addActions(actions, subgroupData)\r\n }\r\n }\r\n\r\n #getActionId (entity, actionType, spellLevel) {\r\n return (actionType === 'spell') ? `${entity.id ?? entity._id}-${spellLevel}` : entity.id ?? entity._id\r\n }\r\n\r\n #getActionName (entity) {\r\n return entity?.name ?? entity?.label ?? ''\r\n }\r\n\r\n #getActionListName (entity, actionType) {\r\n const name = this.#getActionName(entity)\r\n const actionTypeName = `${coreModule.api.Utils.i18n(ACTION_TYPE[actionType])}: ` ?? ''\r\n return entity.listName ?? `${actionTypeName}${name}`\r\n }\r\n\r\n #getActionCss (entity) {\r\n if (Object.hasOwn(entity, 'disabled')) {\r\n const active = (!entity.disabled) ? ' active' : ''\r\n return `toggle${active}`\r\n }\r\n if (Object.hasOwn(entity, 'selected')) {\r\n const active = (entity.selected) ? ' active' : ''\r\n return `toggle${active}`\r\n }\r\n }\r\n\r\n #getActionEncodedValue (entity, actionType, spellLevel) {\r\n const spellcastingId = entity?.spellcasting?.id\r\n const encodedId = (actionType === 'spell') ? `${spellcastingId}>${spellLevel}>${entity.id ?? entity._id}` : this.#getActionId(entity, actionType, spellLevel)\r\n return [actionType, encodedId].join(this.delimiter)\r\n }\r\n\r\n #getIcon1 (entity, actionType) {\r\n const actions = entity.system?.actions\r\n const actionTypes = ['free', 'reaction', 'passive']\r\n const actionTypeValue = entity.system?.actionType?.value\r\n const actionsCost = (actions) ? parseInt((actions || {}).value, 10) : null\r\n const timeValue = entity.system?.time?.value\r\n const actionIcon = entity.actionIcon\r\n const iconType = (actionType === 'spell') ? timeValue : (actionTypes.includes(actionTypeValue)) ? actionTypeValue : actionsCost ?? actionIcon\r\n const name = this.#getActionName(entity)\r\n return this.#getActionIcon(iconType, name)\r\n }\r\n\r\n /**\r\n * Get spell DC info\r\n * @private\r\n * @param {object} spellcastingEntry The spellcasting entry\r\n * @returns {string} The spell DC info\r\n */\r\n #getSpellDcInfo (spellcastingEntry) {\r\n const statistic = spellcastingEntry.statistic\r\n const spellDc = typeof statistic.dc === 'function'\r\n ? statistic.dc().value\r\n : statistic.dc.value\r\n const spellAttackModifier = statistic.check.mod\r\n const spellAttackBonus = spellAttackModifier >= 0\r\n ? `${coreModule.api.Utils.i18n('tokenActionHud.pf2e.atk')} +${spellAttackModifier}`\r\n : `${coreModule.api.Utils.i18n('tokenActionHud.pf2e.atk')} ${spellAttackModifier}`\r\n const spellDcInfo = `${coreModule.api.Utils.i18n('tokenActionHud.pf2e.dc')}${spellDc}`\r\n return `${spellAttackBonus} ${spellDcInfo}`\r\n }\r\n\r\n /**\r\n * Get actors\r\n * @private\r\n * @returns {object}\r\n */\r\n #getActors () {\r\n const allowedTypes = ['character', 'npc']\r\n const actors = canvas.tokens.controlled.map(token => token.actor)\r\n if (actors.every(actor => allowedTypes.includes(actor.type))) { return actors }\r\n }\r\n\r\n /**\r\n * Is equipped item\r\n * @private\r\n * @param {object} item\r\n * @returns {boolean}\r\n */\r\n #isEquippedItem (item) {\r\n const carryTypes = ['held', 'worn']\r\n const carryType = item.system.equipped?.carryType\r\n\r\n if (this.addUnequippedItems) return true\r\n if (carryTypes.includes(carryType) && !item.system.containerId?.value?.length) return true\r\n return false\r\n }\r\n\r\n #isAddItem (groupType, item) {\r\n if (item.system.equipped?.carryType !== 'stowed') return true\r\n return this.#isAddStowedItem(groupType, item)\r\n }\r\n\r\n /**\r\n * Is add stowed item\r\n * @private\r\n * @param {string} groupType The group type: container or nonContainer\r\n * @param {object} item The item\r\n * @returns {boolean} Whether the stowed item should be added to the group\r\n */\r\n #isAddStowedItem (groupType, item) {\r\n if (item.system.equipped?.carryType !== 'stowed') return true\r\n if (this.addStowedItems === 'both') return true\r\n if (groupType === 'container' && this.addStowedItems === 'containers') return true\r\n if (groupType === 'nonContainer' && this.addStowedItems === 'nonContainers') return true\r\n return false\r\n }\r\n\r\n /**\r\n * Get item info\r\n * @private\r\n * @param {object} item\r\n * @returns {object}\r\n */\r\n #getItemInfo (item) {\r\n const quantityData = this.#getQuantityData(item) ?? ''\r\n return {\r\n info1: { text: quantityData }\r\n }\r\n }\r\n\r\n /**\r\n * Get quantity\r\n * @private\r\n * @param {object} item\r\n * @returns {string}\r\n */\r\n #getQuantityData (item) {\r\n const quantity = item?.system?.quantity?.value\r\n return (quantity > 1) ? quantity : ''\r\n }\r\n\r\n /**\r\n * Get action icon\r\n * @private\r\n * @param {object} action\r\n * @returns {string}\r\n */\r\n #getActionIcon (action, title = '') {\r\n if (DAMAGE_TYPE_ICONS[action]) {\r\n return ``\r\n }\r\n return ACTION_ICON[action]\r\n }\r\n\r\n /**\r\n * Get carry type icon\r\n * @private\r\n * @param {object} itemData The item data\r\n * @returns {string}\r\n */\r\n #getCarryTypeIcon (itemData) {\r\n let carryType = ''\r\n switch (itemData?.carryType) {\r\n case 'held':\r\n if (itemData?.handsHeld === 2) {\r\n carryType = 'held2'\r\n } else {\r\n carryType = 'held1'\r\n }\r\n break\r\n default:\r\n carryType = itemData?.carryType\r\n break\r\n }\r\n const tooltip = coreModule.api.Utils.i18n(CARRY_TYPE_ICON[carryType]?.tooltip) ?? ''\r\n return CARRY_TYPE_ICON[carryType]?.icon.replace('placeholder', tooltip) ?? ''\r\n }\r\n\r\n /**\r\n * Get tooltip data\r\n * @param {object} entity The entity\r\n * @param {string} actionType The action type\r\n * @returns {Promise