diff --git a/daggerheart.mjs b/daggerheart.mjs
index 12900f25..444f8454 100644
--- a/daggerheart.mjs
+++ b/daggerheart.mjs
@@ -10,7 +10,9 @@ import DhpChatLog from './module/ui/chatLog.mjs';
import DhpPlayers from './module/ui/players.mjs';
import DhpRuler from './module/ui/ruler.mjs';
import DhpTokenRuler from './module/ui/tokenRuler.mjs';
-import { dualityRollEnricher } from './module/enrichers/DualityRollEnricher.mjs';
+import { dualityRollEnricher, getDualityMessage } from './module/enrichers/DualityRollEnricher.mjs';
+import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
+import { abilities } from './module/config/actorConfig.mjs';
globalThis.SYSTEM = SYSTEM;
@@ -130,26 +132,108 @@ Hooks.on(socketEvent.GMUpdate, async (action, uuid, update) => {
Hooks.on('renderChatMessageHTML', (message, element) => {
element.querySelectorAll('.duality-roll-button').forEach(element =>
element.addEventListener('click', async event => {
+ let target = getCommandTarget();
+ if (!target) return;
+
const button = event.currentTarget;
- let target = game.canvas.tokens.controlled.length > 0 ? game.canvas.tokens.controlled[0].actor : null;
- if (!game.user.isGM) {
- target = game.user.character;
- if (!target) {
- notifications.error('DAGGERHEART.Notification.Error.NoAssignedPlayerCharacter');
- return;
- }
- }
+ const rollModifier = button.dataset.attribute
+ ? target.system.attributes[button.dataset.attribute].data.value
+ : null;
+ const { roll, hope, fear, advantage, disadvantage, modifiers } = await target.diceRoll({
+ title: button.dataset.label,
+ value: rollModifier
+ });
+ const cls = getDocumentClass('ChatMessage');
+ const msgData = {
+ type: 'dualityRoll',
+ sound: CONFIG.sounds.dice,
+ system: {
+ title: button.dataset.label,
+ origin: target.id,
+ roll: roll._formula,
+ modifiers: modifiers,
+ hope: hope,
+ fear: fear,
+ advantage: advantage,
+ disadvantage: disadvantage
+ },
+ user: game.user.id,
+ content: 'systems/daggerheart/templates/chat/duality-roll.hbs',
+ rolls: [roll]
+ };
- if (!target) {
- notifications.error('DAGGERHEART.Notification.Error.NoSelectedToken');
- return;
- }
-
- const test = await gmTarget.diceRoll(3);
+ await cls.create(msgData);
})
);
});
+Hooks.on('chatMessage', (_, message) => {
+ if (message.startsWith('/dr')) {
+ const rollCommand = rollCommandToJSON(message.replace(/\/dr\s?/, ''));
+ if (!rollCommand) {
+ ui.notifications.error(game.i18n.localize('DAGGERHEART.Notification.Error.DualityParsing'));
+ return false;
+ }
+
+ const attributeValue = rollCommand.attribute?.toLowerCase();
+
+ // Target not required if an attribute is not used.
+ const target = attributeValue ? getCommandTarget() : undefined;
+ if (target || !attributeValue) {
+ new Promise(async (resolve, reject) => {
+ const attribute = target ? target.system.attributes[attributeValue] : undefined;
+ if (attributeValue && !attribute) {
+ ui.notifications.error(game.i18n.localize('DAGGERHEART.Notification.Error.AttributeFaulty'));
+ reject();
+ return;
+ }
+
+ const title = attributeValue
+ ? game.i18n.format('DAGGERHEART.Chat.DualityRoll.AbilityCheckTitle', {
+ ability: game.i18n.localize(abilities[attributeValue].label)
+ })
+ : game.i18n.localize('DAGGERHEART.General.Duality');
+
+ const hopeAndFearRoll = `1${rollCommand.hope ?? 'd12'}+1${rollCommand.fear ?? 'd12'}`;
+ const advantageRoll = `${rollCommand.advantage && !rollCommand.disadvantage ? '+d6' : rollCommand.disadvantage && !rollCommand.advantage ? '-d6' : ''}`;
+ const attributeRoll = `${attribute?.data?.value ? `${attribute.data.value > 0 ? `+${attribute.data.value}` : `${attribute.data.value}`}` : ''}`;
+ const roll = new Roll(`${hopeAndFearRoll}${advantageRoll}${attributeRoll}`);
+ await roll.evaluate();
+ resolve({ roll, attribute, title });
+ }).then(({ roll, attribute, title }) => {
+ const cls = getDocumentClass('ChatMessage');
+ const msgData = {
+ type: 'dualityRoll',
+ sound: CONFIG.sounds.dice,
+ system: {
+ title: title,
+ origin: target?.id,
+ roll: roll._formula,
+ modifiers: attribute ? [{ value: attribute.data.value }] : [],
+ hope: { dice: rollCommand.hope ?? 'd12', value: roll.dice[0].total },
+ fear: { dice: rollCommand.fear ?? 'd12', value: roll.dice[1].total },
+ advantage:
+ rollCommand.advantage && !rollCommand.disadvantage
+ ? { dice: 'd6', value: roll.dice[2].total }
+ : undefined,
+ disadvantage:
+ rollCommand.disadvantage && !rollCommand.advantage
+ ? { dice: 'd6', value: roll.dice[2].total }
+ : undefined
+ },
+ user: game.user.id,
+ content: 'systems/daggerheart/templates/chat/duality-roll.hbs',
+ rolls: [roll]
+ };
+
+ cls.create(msgData);
+ });
+ }
+
+ return false;
+ }
+});
+
const preloadHandlebarsTemplates = async function () {
return foundry.applications.handlebars.loadTemplates([
'systems/daggerheart/templates/sheets/parts/attributes.hbs',
diff --git a/lang/en.json b/lang/en.json
index 1256ba88..4fb9d502 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -96,7 +96,10 @@
"DuplicateDomainCard": "You already have a domain card with that name!",
"ActionRequiresTarget": "The action requires at least one target",
"NoAssignedPlayerCharacter": "You have no assigned character.",
- "NoSelectedToken": "You have no selected token"
+ "NoSelectedToken": "You have no selected token",
+ "OnlyUseableByPC": "This can only be used with a PC token",
+ "DualityParsing": "Duality roll not properly formated",
+ "AttributeFaulty": "The supplied Attribute doesn't exist"
}
},
"General": {
diff --git a/module/applications/sheets/pc.mjs b/module/applications/sheets/pc.mjs
index fe32752b..cde8a5f1 100644
--- a/module/applications/sheets/pc.mjs
+++ b/module/applications/sheets/pc.mjs
@@ -491,6 +491,7 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
const cls = getDocumentClass('ChatMessage');
const msgData = {
type: 'dualityRoll',
+ sound: CONFIG.sounds.dice,
system: {
title: game.i18n.format('DAGGERHEART.Chat.DualityRoll.AbilityCheckTitle', {
ability: game.i18n.localize(abilities[button.dataset.attribute].label)
@@ -1226,1029 +1227,3 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
return this._onDropItem(event, data);
}
}
-
-// export default class PCSheet extends DhpApplicationMixin(ActorSheet) {
-// static applicationType = "sheets/pc";
-// static documentType = "pc";
-
-// constructor(actor, options){
-// super(actor, options);
-
-// this.editAttributes = false;
-// this.onVaultTab = false;
-// this.currentInventoryPage = 0;
-// this.selectedScar = null;
-// this.storyEditor = null;
-// this.dropItemBlock = false;
-// this.multiclassFeatureSetSelected = false;
-// }
-
-// /** @override */
-// static get defaultOptions() {
-// return foundry.utils.mergeObject(super.defaultOptions, {
-// classes: ["daggerheart", "sheet", "pc"],
-// width: 810,
-// height: 1080,
-// resizable: false,
-// tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "features" }, { navSelector: ".loadout-tabs", contentSelector: ".loadout-body", initial: "loadout"}],
-// dragDrop: [
-// {dragSelector: null, dropSelector: ".weapon-section" },
-// {dragSelector: null, dropSelector: ".armor-section"},
-// {dragSelector: null, dropSelector: ".inventory-weapon-section-first"},
-// {dragSelector: null, dropSelector: ".inventory-weapon-section-second"},
-// {dragSelector: ".item-list .item", dropSelector: null},
-// ]
-// }, { overwrite: true, inplace: true });
-// }
-
-// async mapFeatureType(data, configType){
-// return await Promise.all(data.map(async x => {
-// const abilities = x.system.abilities ? await Promise.all(x.system.abilities.map(async x => await fromUuid(x.uuid))) : [];
-
-// return {
-// ...x,
-// uuid: x.uuid,
-// system: {
-// ...x.system,
-// abilities: abilities,
-// type: game.i18n.localize(configType[x.system.type??x.type].label)
-// }
-// }
-// }));
-// }
-
-// mapAdvancementFeatures(actor, config) {
-// if(!actor.system.subclass) return { foundation: null, advancements: [] };
-
-// const { subclass, multiclassSubclass } = actor.system.subclassFeatures;
-
-// const foundation = {
-// type: 'foundation',
-// multiclass: false,
-// img: actor.system.subclass.img,
-// subtitle: game.i18n.localize("DAGGERHEART.Sheets.PC.DomainCard.FoundationTitle"),
-// domains: actor.system.class.system.domains.map(x => config.DOMAIN.domains[x].src),
-// className: actor.system.class.name,
-// subclassUuid: actor.system.subclass.uuid,
-// subclassName: actor.system.subclass.name,
-// spellcast: config.ACTOR.abilities[actor.system.subclass.system.spellcastingTrait]?.name ?? null,
-// description: actor.system.subclass.system.foundationFeature.description,
-// abilities: subclass.foundation,
-// abilityKey: 'foundationFeature',
-// };
-
-// const firstKey = actor.system.subclass.system.specializationFeature.unlocked && actor.system.subclass.system.specializationFeature.tier === 2 ? 'sub' :
-// actor.system.multiclass?.system?.multiclassTier === 2 ? 'multi' : null;
-// const firstType = firstKey === 'sub' ? 'specialization' : 'foundation';
-// const firstBase = firstKey === 'sub' ? actor.system.subclass : firstKey === 'multi' ? actor.system.multiclassSubclass : null;
-// const first = !firstBase ? null : {
-// type: firstType,
-// multiclass: firstKey === 'multi',
-// img: firstBase.img,
-// subtitle: firstKey === 'sub' ? game.i18n.localize("DAGGERHEART.Sheets.PC.DomainCard.SpecializationTitle") : game.i18n.localize("DAGGERHEART.Sheets.PC.DomainCard.FoundationTitle"),
-// domains: firstKey === 'sub' ? actor.system.class.system.domains.map(x => config.DOMAIN.domains[x].src) : actor.system.multiclass.system.domains.map(x => config.DOMAIN.domains[x].src),
-// className: firstKey === 'sub' ? actor.system.class.name : actor.system.multiclass.name,
-// subclassUuid: firstBase.uuid,
-// subclassName: firstBase.name,
-// spellcast: firstKey === 'sub' ? null : config.ACTOR.abilities[firstBase.system.spellcastingTrait]?.name ?? null,
-// description: firstKey === 'sub' ? firstBase.system.specializationFeature.description : firstBase.system.foundationFeature.description,
-// abilities: firstKey === 'sub' ? subclass.specialization : multiclassSubclass.foundation,
-// abilityKey: firstKey === 'sub' ? 'specializationFeature' : 'foundationFeature',
-// };
-
-// const secondKey = (actor.system.subclass.system.specializationFeature.unlocked && actor.system.subclass.system.specializationFeature.tier === 3) || (actor.system.subclass.system.masteryFeature.unlocked && actor.system.subclass.system.masteryFeature.tier === 3) ? 'sub' :
-// (actor.system.multiclass?.system?.multiclassTier === 3) || (actor.system.multiclassSubclass?.system?.specializationFeature?.unlocked) ? 'multi' : null;
-// const secondBase = secondKey === 'sub' ? actor.system.subclass : secondKey === 'multi' ? actor.system.multiclassSubclass : null;
-// const secondAbilities = secondKey === 'sub' ? subclass : multiclassSubclass;
-// const secondType = secondBase ? secondBase.system.masteryFeature.unlocked ? 'mastery' : secondBase.system.specializationFeature.unlocked ? 'specialization' : 'foundation' : null;
-// const second = !secondBase ? null : {
-// type: secondType,
-// multiclass: secondKey === 'multi',
-// img: secondBase.img,
-// subtitle: secondBase.system.masteryFeature.unlocked ? game.i18n.localize("DAGGERHEART.Sheets.PC.DomainCard.MasteryTitle") :
-// secondBase.system.specializationFeature.unlocked ? game.i18n.localize("DAGGERHEART.Sheets.PC.DomainCard.SpecializationTitle") : game.i18n.localize("DAGGERHEART.Sheets.PC.DomainCard.FoundationTitle"),
-// domains: secondKey === 'sub' ? actor.system.class.system.domains.map(x => config.DOMAIN.domains[x].src) : actor.system.multiclass.system.domains.map(x => config.DOMAIN.domains[x].src),
-// className: secondKey === 'sub' ? actor.system.class.name : actor.system.multiclass.name,
-// subclassUuid: secondBase.uuid,
-// subclassName: secondBase.name,
-// spellcast: secondKey === 'sub' || secondBase.system.specializationFeature.unlocked ? null : config.ACTOR.abilities[firstBase.system.spellcastingTrait]?.name ?? null,
-// description:
-// secondBase.system.masteryFeature.unlocked ? secondBase.system.masteryFeature.description :
-// secondBase.system.specializationFeature.unlocked ? secondBase.system.specializationFeature.description : firstBase.system.foundationFeature.description,
-// abilities:
-// secondBase.system.masteryFeature.unlocked ? secondAbilities.mastery :
-// secondBase.system.specializationFeature.unlocked ? secondAbilities.specialization : secondAbilities.foundation,
-// abilityKey: secondBase.system.masteryFeature.unlocked ? 'masteryFeature' : secondBase.system.specializationFeature.unlocked ? 'specializationFeature' : 'foundationFeature',
-// };
-
-// return {
-// foundation: foundation,
-// first: first,
-// second: second,
-// }
-// }
-
-// /** @override */
-// async getData() {
-// const context = super.getData();
-// context.config = SYSTEM;
-// context.editAttributes = this.editAttributes;
-// context.onVaultTab = this.onVaultTab;
-// context.selectedScar = this.selectedScar;
-// context.storyEditor = this.storyEditor;
-// context.multiclassFeatureSetSelected = this.multiclassFeatureSetSelected;
-
-// const selectedAttributes = Object.values(this.actor.system.attributes).map(x => x.data.base);
-// context.abilityScoreArray = JSON.parse(await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.General.AbilityArray)).reduce((acc, x) => {
-// const selectedIndex = selectedAttributes.indexOf(x);
-// if(selectedIndex !== -1){
-// selectedAttributes.splice(selectedIndex, 1);
-// } else {
-// acc.push({ name: x, value: x });
-// }
-
-// return acc;
-// }, []);
-// if(!context.abilityScoreArray.includes(0)) context.abilityScoreArray.push({ name: 0, value: 0 });
-// context.abilityScoresFinished = context.abilityScoreArray.every(x => x.value === 0);
-
-// context.domains = this.actor.system.class ? {
-// first: this.actor.system.class.system.domains[0] ? SYSTEM.DOMAIN.domains[this.actor.system.class.system.domains[0]].src : null,
-// second: this.actor.system.class.system.domains[1] ? SYSTEM.DOMAIN.domains[this.actor.system.class.system.domains[1]].src : null,
-// } : { };
-
-// context.attributes = Object.keys(this.actor.system.attributes).reduce((acc, key) => {
-// acc[key] = {
-// ...this.actor.system.attributes[key],
-// name: game.i18n.localize(SYSTEM.ACTOR.abilities[key].name),
-// verbs: SYSTEM.ACTOR.abilities[key].verbs.map(x => game.i18n.localize(x)),
-// };
-
-// return acc;
-// }, {});
-
-// const ancestry = await this.mapFeatureType(this.actor.system.ancestry ? [this.actor.system.ancestry] : [], SYSTEM.GENERAL.objectTypes);
-// const community = await this.mapFeatureType(this.actor.system.community ? [this.actor.system.community] : [], SYSTEM.GENERAL.objectTypes);
-// const foundation = {
-// ancestry: ancestry[0],
-// community: community[0],
-// advancement: {
-// ...this.mapAdvancementFeatures(this.actor, SYSTEM)
-// }
-// };
-
-// const nrLoadoutCards = this.actor.system.domainCards.loadout.length;
-// const loadout = await this.mapFeatureType(this.actor.system.domainCards.loadout, SYSTEM.DOMAIN.cardTypes);
-// const vault = await this.mapFeatureType(this.actor.system.domainCards.vault, SYSTEM.DOMAIN.cardTypes);
-// context.abilities = {
-// foundation: foundation,
-// loadout: {
-// top: loadout.slice(0, Math.min(2, nrLoadoutCards)),
-// bottom: nrLoadoutCards > 2 ? loadout.slice(2, Math.min(5, nrLoadoutCards)) : [],
-// nrTotal: nrLoadoutCards,
-// },
-// vault: vault.map(x => ({ ...x, uuid: x.uuid, sendToLoadoutDisabled: this.actor.system.domainCards.loadout.length >= this.actor.system.domainData.maxLoadout }))
-// };
-
-// context.inventory = {
-// consumable: {
-// titles: {
-// name: game.i18n.localize("DAGGERHEART.Sheets.PC.InventoryTab.ConsumableTitle"),
-// quantity: game.i18n.localize("DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle"),
-// },
-// items: this.actor.items.filter(x => x.type === 'consumable'),
-// },
-// miscellaneous: {
-// titles: {
-// name: game.i18n.localize("DAGGERHEART.Sheets.PC.InventoryTab.MiscellaneousTitle"),
-// quantity: game.i18n.localize("DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle"),
-// },
-// items: this.actor.items.filter(x => x.type === 'miscellaneous'),
-// },
-// }
-
-// if(context.inventory.length === 0){
-// context.inventory = Array(1).fill(Array(5).fill([]));
-// }
-
-// context.classFeatures = (this.multiclassFeatureSetSelected ? this.actor.system.multiclassFeatures : this.actor.system.classFeatures).map(x => {
-// if(x.system.featureType.type !== 'dice'){
-// return x;
-// }
-
-// return { ...x, uuid: x.uuid, system: { ...x.system, featureType: { ...x.system.featureType, data: { ...x.system.featureType.data, property: this.actor.system.subclass ? SYSTEM.ACTOR.featureProperties[x.system.featureType.data.property].path(this.actor) : 0 }}}};
-// });
-
-// return context;
-// }
-
-// activateListeners(html) {
-// super.activateListeners(html);
-
-// html.find(".attribute-value").on("change", this.attributeChange.bind(this));
-// html.on('click', '.tab-selector', this.tabSwitch.bind(this));
-// html.on('click', '.level-title.levelup', this.openLevelUp.bind(this));
-// html.find(".feature-input").on("change", this.onFeatureInputBlur.bind(this));
-// html.find(".experience-description").on("change", this.experienceDescriptionChange.bind(this));
-// html.find(".experience-value").on("change", this.experienceValueChange.bind(this));
-// html.on("change", "[data-item]", this.itemUpdate.bind(this));
-// }
-
-// async _handleAction(action, event, button) {
-// switch(action){
-// case 'toggleEditAttributes':
-// this.toggleEditAttributes();
-// break;
-// case 'attributeRoll':
-// await this.rollAttribute(event);
-// break;
-// case 'toggleMarks':
-// await this.toggleMarks(button);
-// break;
-// case 'toggleAttributeMark':
-// await this.toggleAttributeMark(button);
-// break;
-// case 'toggleHP':
-// await this.toggleHP(button);
-// break;
-// case 'toggleStress':
-// await this.toggleStress(button);
-// break;
-// case 'toggleHope':
-// await this.toggleHope(button);
-// break;
-// case 'toggleGold':
-// await this.toggleGold(button);
-// break;
-// case 'attackRoll':
-// await this.attackRoll(event);
-// break;
-// case 'tabToLoadout':
-// this.domainCardsTab(false);
-// break;
-// case 'tabToVault':
-// this.domainCardsTab(true);
-// break;
-// case 'sendToVault':
-// await this.moveDomainCard(button, true);
-// break;
-// case 'sendToLoadout':
-// await this.moveDomainCard(button, false);
-// break;
-// case 'useDomainCard':
-// await this.useDomainCard(button);
-// break;
-// case 'selectClass':
-// await this.selectClass();
-// break;
-// case 'selectSubclass':
-// await this.selectSubclass();
-// break;
-// case 'selectAncestry':
-// await this.selectAncestry();
-// break;
-// case 'selectCommunity':
-// await this.selectCommunity();
-// break;
-// case 'viewObject':
-// await this.viewObject(button);
-// break;
-// case 'useFeature':
-// await this.useFeature(button);
-// break;
-// case 'takeShortRest':
-// await this.takeShortRest();
-// break;
-// case 'takeLongRest':
-// await this.takeLongRest();
-// break;
-// case 'removeActiveItem':
-// await this.removeActiveItem(event);
-// break;
-// case 'removeInventoryWeapon':
-// await this.removeInventoryWeapon(event);
-// break;
-// case 'addMiscItem':
-// await this.addMiscItem();
-// break;
-// case 'deleteItem':
-// await this.deleteItem(button);
-// break;
-// case 'addScar':
-// await this.addScar();
-// break;
-// case 'selectScar':
-// await this.selectScar(button);
-// break;
-// case 'deleteScar':
-// await this.deleteScar(event);
-// break;
-// case 'makeDeathMove':
-// await this.makeDeathMove();
-// break;
-// case 'toggleFeatureDice':
-// await this.toggleFeatureDice(button);
-// break;
-// case 'setStoryEditor':
-// this.setStoryEditor(button);
-// break;
-// case 'itemQuantityDecrease':
-// await this.setItemQuantity(button, -1);
-// break;
-// case 'itemQuantityIncrease':
-// await this.setItemQuantity(button, 1);
-// break;
-// case 'useAbility':
-// await this.useAbility(button);
-// break;
-// case 'useAdvancementCard':
-// await this.useAdvancementCard(button);
-// break;
-// case 'useAdvancementAbility':
-// await this.useAdvancementAbility(button);
-// break;
-// case 'selectFeatureSet':
-// await this.selectFeatureSet(button);
-// break;
-// }
-// }
-
-// async attributeChange(event){
-// const path = `system.attributes.${event.currentTarget.dataset.attribute}.data.base`;
-// await this.actor.update({ [path]: event.currentTarget.value });
-// }
-
-// toggleEditAttributes(){
-// this.editAttributes = !this.editAttributes;
-// this.render();
-// }
-
-// async rollAttribute(event){
-// const { roll, hope, fear, advantage, disadvantage, modifiers } = await this.actor.dualityRoll({ title: 'Attribute Bonus', value: event.currentTarget.dataset.value }, event.shiftKey);
-
-// const cls = getDocumentClass("ChatMessage");
-// const msg = new cls({
-// type: 'dualityRoll',
-// system: {
-// roll: roll._formula,
-// modifiers: modifiers,
-// hope: hope,
-// fear: fear,
-// advantage: advantage,
-// disadvantage: disadvantage,
-// },
-// user: game.user.id,
-// content: "systems/daggerheart/templates/chat/duality-roll.hbs",
-// rolls: [roll]
-// });
-
-// await cls.create(msg.toObject());
-// }
-
-// async toggleMarks(button){
-// const markValue = Number.parseInt(button.dataset.value);
-// const newValue = this.actor.system.armor.system.marks.value >= markValue ? markValue-1 : markValue;
-// await this.actor.system.armor.update({ 'system.marks.value': newValue });
-// }
-
-// async toggleAttributeMark(button){
-// const attribute = this.actor.system.attributes[button.dataset.attribute];
-// const newMark = this.actor.system.availableAttributeMarks.filter(x => x > Math.max.apply(null, this.actor.system.attributes[button.dataset.attribute].levelMarks)).sort((a, b) => a > b ? 1 : -1)[0];
-
-// if(attribute.levelMark || !newMark) return;
-
-// const path = `system.attributes.${button.dataset.attribute}.levelMarks`;
-// await this.actor.update({ [path]: [...attribute.levelMarks, newMark] });
-// }
-
-// async toggleHP(button){
-// const healthValue = Number.parseInt(button.dataset.value);
-// const newValue = this.actor.system.resources.health.value >= healthValue ? healthValue-1 : healthValue;
-// await this.actor.update({ 'system.resources.health.value': newValue });
-// }
-
-// async toggleStress(button){
-// const healthValue = Number.parseInt(button.dataset.value);
-// const newValue = this.actor.system.resources.stress.value >= healthValue ? healthValue-1 : healthValue;
-// await this.actor.update({ 'system.resources.stress.value': newValue });
-// }
-
-// async toggleHope(button){
-// const hopeValue = Number.parseInt(button.dataset.value);
-// const newValue = this.actor.system.resources.hope.value >= hopeValue ? hopeValue-1 : hopeValue;
-// await this.actor.update({ 'system.resources.hope.value': newValue });
-// }
-
-// async toggleGold(button){
-// const goldValue = Number.parseInt(button.dataset.value);
-// const goldType = button.dataset.type;
-// const newValue = this.actor.system.gold[goldType] >= goldValue ? goldValue-1 : goldValue;
-
-// const update = `system.gold.${goldType}`;
-// await this.actor.update({ [update]: newValue });
-// }
-
-// async attackRoll(event){
-// const weapon = await fromUuid(event.currentTarget.dataset.weapon);
-// const damage = {
-// value: `${this.actor.system.proficiency.value}${weapon.system.damage.value}`,
-// type: weapon.system.damage.type,
-// bonusDamage: this.actor.system.bonuses.damage
-// };
-// const modifier = this.actor.system.attributes[weapon.system.trait].data.value;
-
-// const { roll, hope, fear, advantage, disadvantage, modifiers, bonusDamageString } = await this.actor.dualityRoll({ title: 'Attribute Modifier', value: modifier }, event.shiftKey, damage.bonusDamage);
-
-// damage.value = damage.value.concat(bonusDamageString);
-
-// const targets = Array.from(game.user.targets).map(x => ({
-// id: x.id,
-// name: x.actor.name,
-// img: x.actor.img,
-// difficulty: x.actor.system.difficulty,
-// evasion: x.actor.system.evasion,
-// }));
-
-// const cls = getDocumentClass("ChatMessage");
-// const msg = new cls({
-// type: 'dualityRoll',
-// system: {
-// roll: roll._formula,
-// modifiers: modifiers,
-// hope: hope,
-// fear: fear,
-// advantage: advantage,
-// disadvantage: disadvantage,
-// damage: damage,
-// targets: targets,
-// },
-// content: "systems/daggerheart/templates/chat/attack-roll.hbs",
-// rolls: [roll]
-// });
-
-// await cls.create(msg.toObject());
-// }
-
-// tabSwitch(event){
-// const tab = event.currentTarget.dataset.tab;
-// if(tab !== 'loadout'){
-// this.onVaultTab = false;
-// }
-
-// this.render();
-// }
-
-// openLevelUp(event){
-// new DhpLevelup(this.actor).render(true);
-// }
-
-// domainCardsTab(toVault){
-// this.onVaultTab = toVault;
-// this.render();
-// }
-
-// async moveDomainCard(button, toVault){
-// if(!toVault && this.actor.system.domainCards.loadout.length >= this.actor.system.domainData.maxLoadout){
-// return;
-// }
-
-// const card = this.actor.items.find(x => x.uuid === button.dataset.domain);
-// await card.update({ "system.inVault": toVault });
-// }
-
-// async useDomainCard(button){
-// const card = this.actor.items.find(x => x.uuid === button.dataset.key);
-
-// const cls = getDocumentClass("ChatMessage");
-// const msg = new cls({
-// type: 'abilityUse',
-// user: game.user.id,
-// content: "systems/daggerheart/templates/chat/ability-use.hbs",
-// system: {
-// title: `${game.i18n.localize("DAGGERHEART.Chat.DomainCard.Title")} - ${capitalize(button.dataset.domain)}`,
-// img: card.img,
-// name: card.name,
-// description: card.system.effect,
-// actions: card.system.actions,
-// },
-// });
-
-// cls.create(msg.toObject());
-// }
-
-// async selectClass(){
-// (await game.packs.get('daggerheart.playtest-classes'))?.render(true);
-// }
-
-// async selectSubclass(){
-// (await game.packs.get('daggerheart.playtest-subclasses'))?.render(true);
-// }
-
-// async selectAncestry(){
-// const dialogClosed = new Promise((resolve, _) => {
-// new AncestrySelectionDialog(resolve).render(true);
-// });
-// const result = await dialogClosed;
-
-// // await this.emulateItemDrop({ type: 'item', data: result });
-// for(var ancestry of this.actor.items.filter(x => x => x.type === 'ancestry')){
-// await ancestry.delete();
-// }
-
-// const createdItems = [];
-// for(var feature of this.actor.items.filter(x => x.type === 'feature' && x.system.type === SYSTEM.ITEM.featureTypes.ancestry.id)){
-// await feature.delete();
-// }
-
-// // createdItems.push(...result.data.system.abilities);
-// createdItems.push(result.data);
-
-// await this.actor.createEmbeddedDocuments('Item', createdItems);
-
-// // await this.actor.createEmbeddedDocuments("Item", [result.toObject()]);
-// // (await game.packs.get('daggerheart.playtest-ancestries'))?.render(true);
-// }
-
-// async selectCommunity(){
-// (await game.packs.get('daggerheart.playtest-communities'))?.render(true);;
-// }
-
-// async viewObject(button){
-// const object = await fromUuid(button.dataset.value);
-// if(!object) return;
-
-// const tab = button.dataset.tab;
-// if(tab) object.sheet._tabs[0].active = tab;
-
-// if(object.sheet.editMode) object.sheet.editMode = false;
-
-// object.sheet.render(true);
-// }
-
-// async takeShortRest(){
-// await new DhpDowntime(this.actor, true).render(true);
-// await this.minimize();
-// }
-
-// async takeLongRest(){
-// await new DhpDowntime(this.actor, false).render(true);
-// await this.minimize();
-// }
-
-// async removeActiveItem(event){
-// event.stopPropagation();
-// const item = await fromUuid(event.currentTarget.dataset.item);
-// await item.delete();
-// }
-
-// async removeInventoryWeapon(event){
-// event.stopPropagation();
-// const item = await fromUuid(event.currentTarget.dataset.item);
-// await item.delete();
-// }
-
-// async addMiscItem(){
-// const result = await this.actor.createEmbeddedDocuments("Item", [{
-// name: game.i18n.localize('DAGGERHEART.Sheets.PC.NewItem'),
-// type: 'miscellaneous'
-// }]);
-
-// await result[0].sheet.render(true);
-// }
-
-// async addScar(){
-// if(this.actor.system.story.scars.length === 5) return;
-
-// await this.actor.update({ "system.story.scars": [...this.actor.system.story.scars, { name: game.i18n.localize("DAGGERHEART.Sheets.PC.NewScar"), description: '' }] });
-// }
-
-// async selectScar(button){
-// this.selectedScar = Number.parseInt(button.dataset.value);
-// this.render();
-// }
-
-// async deleteScar(event) {
-// event.stopPropagation();
-// await this.actor.update({ "system.story.scars": this.actor.system.story.scars.filter((_, index) => index !== Number.parseInt(event.currentTarget.dataset.scar) ) })
-// }
-
-// async makeDeathMove() {
-// if(this.actor.system.resources.health.value === this.actor.system.resources.health.max){
-// await new DhpDeathMove(this.actor).render(true);
-// await this.minimize();
-// }
-// }
-
-// async toggleFeatureDice(button){
-// const index = Number.parseInt(button.dataset.index);
-// const feature = this.actor.system.classFeatures.find(x => x.uuid === button.dataset.feature);
-// const path = `system.featureType.data.numbers.${index}`;
-// if(feature.system.featureType.data.numbers[index]?.used) return;
-
-// if(Object.keys(feature.system.featureType.data.numbers).length <= index) {
-// const roll = new Roll(feature.system.featureType.data.value);
-// const rollData = await roll.evaluate();
-// const cls = getDocumentClass("ChatMessage");
-// const msg = new cls({
-// user: game.user.id,
-// rolls: [roll]
-// });
-
-// await cls.create(msg.toObject());
-
-// await feature.update({ [path]: { value: Number.parseInt(rollData.total), used: false } });
-// } else {
-// await Dialog.confirm({
-// title: game.i18n.localize("Confirm feature use"),
-// content: `Are you sure you want to use ${feature.name}?`,
-// yes: async () => {
-// await feature.update({ [path]: { used: true } });
-
-// const cls = getDocumentClass("ChatMessage");
-// const msg = new cls({
-// user: game.user.id,
-// content: await renderTemplate("systems/daggerheart/templates/chat/ability-use.hbs", {
-// title: game.i18n.localize("DAGGERHEART.Chat.FeatureTitle"),
-// card: { name: `${feature.name} - Roll Of ${feature.system.featureType.data.numbers[index].value}`, img: feature.img },
-// }),
-// });
-
-// cls.create(msg.toObject());
-// },
-// no: () => { return; },
-// defaultYes: false
-// });
-
-// }
-// }
-
-// async onFeatureInputBlur(event){
-// const feature = this.actor.system.classFeatures.find(x => x.uuid === event.currentTarget.dataset.feature);
-// const value = Number.parseInt(event.currentTarget.value);
-// if(!Number.isNaN(value)) await feature?.update({ "system.featureType.data.value": value });
-// }
-
-// async experienceDescriptionChange(event){
-// const newExperiences = [...this.actor.system.experiences];
-// newExperiences[event.currentTarget.dataset.index].description = event.currentTarget.value;
-// await this.actor.update({ "system.experiences": newExperiences });
-// }
-
-// async experienceValueChange(event){
-// const newExperiences = [...this.actor.system.experiences];
-// newExperiences[event.currentTarget.dataset.index].value = event.currentTarget.value;
-// await this.actor.update({ "system.experiences": newExperiences });
-// }
-
-// setStoryEditor(button) {
-// this.storyEditor = this.storyEditor === button.dataset.value ? null : button.dataset.value;
-// this.render();
-// }
-
-// async itemUpdate(event){
-// const name = event.currentTarget.dataset.item;
-// const item = await fromUuid($(event.currentTarget).closest('[data-item-id]')[0].dataset.itemId);
-// await item.update({ [name]: event.currentTarget.value });
-// }
-
-// async deleteItem(button){
-// const item = await fromUuid($(button).closest('[data-item-id]')[0].dataset.itemId);
-// await item.delete();
-// }
-
-// async setItemQuantity(button, value){
-// const item = await fromUuid($(button).closest('[data-item-id]')[0].dataset.itemId);
-// await item.update({ "system.quantity": Math.max(item.system.quantity + value, 1) });
-// }
-
-// async useFeature(button) {
-// const item = await fromUuid(button.dataset.id);
-
-// const cls = getDocumentClass("ChatMessage");
-// const msg = new cls({
-// type: 'abilityUse',
-// user: game.user.id,
-// content: "systems/daggerheart/templates/chat/ability-use.hbs",
-// system: {
-// title: game.i18n.localize("DAGGERHEART.Chat.FeatureTitle"),
-// img: item.img,
-// name: item.name,
-// description: item.system.description,
-// actions: item.system.actions,
-// },
-// });
-
-// cls.create(msg.toObject());
-// }
-
-// async useAbility(button) {
-// const item = await fromUuid(button.dataset.feature);
-// const type = button.dataset.type
-
-// const cls = getDocumentClass("ChatMessage");
-// const msg = new cls({
-// type: 'abilityUse',
-// user: game.user.id,
-// system: {
-// title: type === 'ancestry' ? game.i18n.localize("DAGGERHEART.Chat.FoundationCard.AncestryTitle") :
-// type === 'community' ? game.i18n.localize("DAGGERHEART.Chat.FoundationCard.CommunityTitle") :
-// game.i18n.localize("DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle"),
-// img: item.img,
-// name: item.name,
-// description: item.system.description,
-// actions: [],
-// },
-// content: "systems/daggerheart/templates/chat/ability-use.hbs",
-// });
-
-// cls.create(msg.toObject());
-// }
-
-// async useAdvancementCard(button){
-// const item = button.dataset.multiclass === 'true' ? this.actor.system.multiclassSubclass : this.actor.system.subclass;
-// const ability = item.system[`${button.dataset.key}Feature`];
-// const title = `${item.name} - ${game.i18n.localize(`DAGGERHEART.Sheets.PC.DomainCard.${capitalize(button.dataset.key)}Title`)}`;
-
-// const cls = getDocumentClass("ChatMessage");
-// const msg = new cls({
-// user: game.user.id,
-// content: await renderTemplate("systems/daggerheart/templates/chat/ability-use.hbs", {
-// title: game.i18n.localize("DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle"),
-// card: { name: title, img: item.img, description: ability.description },
-// }),
-// });
-
-// cls.create(msg.toObject());
-// }
-
-// async useAdvancementAbility(button){
-// // const item = await fromUuid(button.dataset.id);
-// const item = this.actor.items.find(x => x.uuid === button.dataset.id);
-
-// const cls = getDocumentClass("ChatMessage");
-// const msg = new cls({
-// user: game.user.id,
-// content: await renderTemplate("systems/daggerheart/templates/chat/ability-use.hbs", {
-// title: game.i18n.localize("DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle"),
-// card: { name: item.name, img: item.img, description: item.system.description },
-// }),
-// });
-
-// cls.create(msg.toObject());
-// }
-
-// async selectFeatureSet(button){
-// const multiclass = button.dataset.multiclass === 'true';
-// this.multiclassFeatureSetSelected = multiclass;
-// this.render();
-// }
-
-// async close(options){
-// this.onVaultTab = false;
-// super.close(options);
-// }
-
-// async _onDragStart(event){
-// if(event.currentTarget.classList.contains('inventory-item')){
-// if(!['weapon', 'armor'].includes(event.currentTarget.dataset.type)){
-// return;
-// }
-
-// const targets = {
-// 'weapon': ['weapon-section', 'inventory-weapon-section'],
-// 'armor': ['armor-section', 'inventory-armor-section'],
-// };
-
-// event.dataTransfer.setData("text/plain", JSON.stringify({ uuid: event.currentTarget.dataset.item, internal: true, targets: targets[event.currentTarget.dataset.type] }));
-// }
-
-// super._onDragStart(event);
-// }
-
-// async _onDrop(event){
-// const itemData = event.dataTransfer?.getData('text/plain');
-// const item = itemData ? JSON.parse(itemData) : null;
-// if (item?.internal){
-// let target = null;
-// event.currentTarget.classList.forEach(x => {
-// if(item.targets.some(target => target === x)){
-// target = x;
-// }
-// });
-// if(target){
-// const itemObject = await fromUuid(item.uuid);
-// switch(target){
-// case 'weapon-section':
-// if(itemObject.system.secondary && this.actor.system.activeWeapons.burden === 'twoHanded'){
-// ui.notifications.info(game.i18n.localize("DAGGERHEART.Notification.Info.SecondaryEquipWhileTwohanded"));
-// return;
-// }
-// else if(itemObject.system.burden === 'twoHanded' && this.actor.system.activeWeapons.secondary){
-// ui.notifications.info(game.i18n.localize("DAGGERHEART.Notification.Info.TwohandedEquipWhileSecondary"));
-// return;
-// }
-
-// const existingWeapon = this.actor.items.find(x => x.system.active && x.system.secondary === itemObject.system.secondary);
-// await existingWeapon?.update({ "system.active": false });
-// await itemObject.update({ "system.active": true });
-// break;
-// case 'armor-section':
-// const existingArmor = this.actor.items.find(x => x.type === 'armor' && x.system.active);
-// await existingArmor?.update({ "system.active": false });
-// await itemObject.update({ "system.active": true });
-// break;
-// case 'inventory-weapon-section':
-// const existingInventoryWeapon = this.actor.items.find(x => x.system.inventoryWeapon);
-// await existingInventoryWeapon?.update({ "system.inventoryWeapon": false });
-// await itemObject.update({ "system.inventoryWeapon": true });
-// break;
-// case 'inventory-armor-section':
-// const existingInventoryArmor = this.actor.items.find(x => x.system.inventoryArmor);
-// await existingInventoryArmor?.update({ "system.inventoryArmor": false });
-// await itemObject.update({ "system.inventoryArmor": true });
-// break;
-// }
-// }
-// }
-// else {
-// super._onDrop(event);
-// }
-// }
-
-// async _onDropItem(event, data){
-// if(this.dropItemBlock){
-// return;
-// }
-// else {
-// this.dropItemBlock = true;
-// setTimeout(() => this.dropItemBlock = false, 500);
-// }
-
-// const element = event.currentTarget;
-// const item = await Item.implementation.fromDropData(data);
-// const itemData = item.toObject();
-
-// const createdItems = [];
-
-// if(item.type === 'domainCard'){
-// if(!this.actor.system.class)
-// {
-// ui.notifications.error(game.i18n.localize("DAGGERHEART.Notification.Error.NoClassSelected"));
-// return;
-// }
-
-// if(!this.actor.system.domains.find(x => x === item.system.domain)){
-// ui.notifications.error(game.i18n.localize("DAGGERHEART.Notification.Error.LacksDomain"));
-// return;
-// }
-
-// if(this.actor.system.domainCards.total.length === this.actor.system.domainData.maxCards){
-// ui.notifications.error(game.i18n.localize("DAGGERHEART.Notification.Error.MaxLoadoutReached"));
-// return;
-// }
-
-// if(this.actor.system.domainCards.total.find(x => x.name === item.name)){
-// ui.notifications.error(game.i18n.localize("DAGGERHEART.Notification.Error.DuplicateDomainCard"));
-// return;
-// }
-
-// if(this.actor.system.domainCards.loadout.length >= this.actor.system.domainData.maxLoadout){
-// itemData.system.inVault = true;
-// }
-
-// if ( this.actor.uuid === item.parent?.uuid ) return this._onSortItem(event, itemData);
-// const createdItem = await this._onDropItemCreate(itemData);
-
-// return createdItem;
-// }
-// else {
-// if(!item.system.multiclass && ['class', 'subclass', 'ancestry', 'community'].includes(item.type)){
-// const existing = this.actor.items.find(x => x.type === item.type);
-// await existing?.delete();
-// }
-
-// if(item.type === 'subclass'){
-// if(!item.system.multiclass){
-// if(!this.actor.system.class){
-// ui.notifications.info(game.i18n.localize("DAGGERHEART.Notification.Info.SelectClassBeforeSubclass"));
-// return;
-// }
-// else if(!this.actor.system.class.system.subclasses.some(x => x.uuid === item.uuid)){
-// ui.notifications.info(game.i18n.localize("DAGGERHEART.Notification.Info.SubclassNotOfClass"));
-// return;
-// }
-
-// for(var feature of this.actor.items.filter(x => x.type === 'feature' && x.system.type === SYSTEM.ITEM.featureTypes.subclass.id)){
-// await feature.delete();
-// }
-// }
-
-// const features = [itemData.system.foundationFeature, itemData.system.specializationFeature, itemData.system.masteryFeature];
-// for(var i = 0; i < features.length; i++){
-// const feature = features[i];
-// for(var ability of feature.abilities){
-// const data = (await fromUuid(ability.uuid)).toObject();
-// if(i > 0 ) data.system.disabled = true;
-// data.uuid = itemData.uuid;
-
-// const abilityData = await this._onDropItemCreate(data);
-// ability.uuid = abilityData[0].uuid;
-
-// createdItems.push(abilityData);
-// }
-// }
-// }
-// else if(item.type === 'class'){
-// if(!item.system.multiclass){
-// for(var feature of this.actor.items.filter(x => x.type === 'feature' && x.system.type === SYSTEM.ITEM.featureTypes.class.id)){
-// await feature.delete();
-// }
-// }
-
-// for(var feature of item.system.features){
-// const data = (await fromUuid(feature.uuid)).toObject();
-// const itemData = await this._onDropItemCreate(data);
-// createdItems.push(itemData);
-// }
-// }
-// else if(item.type === 'ancestry'){
-// for(var feature of this.actor.items.filter(x => x.type === 'feature' && x.system.type === SYSTEM.ITEM.featureTypes.ancestry.id)){
-// await feature.delete();
-// }
-
-// for(var feature of item.system.abilities){
-// const data = (await fromUuid(feature.uuid)).toObject();
-// const itemData = await this._onDropItemCreate(data);
-// createdItems.push(itemData);
-// }
-// }
-// else if(item.type === 'community'){
-// for(var feature of this.actor.items.filter(x => x.type === 'feature' && x.system.type === SYSTEM.ITEM.featureTypes.community.id)){
-// await feature.delete();
-// }
-
-// for(var feature of item.system.abilities){
-// const data = (await fromUuid(feature.uuid)).toObject();
-// const itemData = await this._onDropItemCreate(data);
-// createdItems.push(itemData);
-// }
-// }
-
-// if ( this.actor.uuid === item.parent?.uuid ) return this._onSortItem(event, item);
-
-// if(item.type === 'weapon'){
-// if(!element) return;
-
-// if(element.classList.contains('weapon-section')){
-// if(item.system.secondary && this.actor.system.activeWeapons.burden === 'twoHanded'){
-// ui.notifications.info(game.i18n.localize("DAGGERHEART.Notification.Info.SecondaryEquipWhileTwohanded"));
-// return;
-// }
-// else if(item.system.burden === 'twoHanded' && this.actor.system.activeWeapons.secondary){
-// ui.notifications.info(game.i18n.localize("DAGGERHEART.Notification.Info.TwohandedEquipWhileSecondary"));
-// return;
-// }
-
-// const existing = this.actor.system.activeWeapons.primary && !item.system.secondary ? await fromUuid(this.actor.system.activeWeapons.primary.uuid) :
-// this.actor.system.activeWeapons.secondary && item.system.secondary ? await fromUuid(this.actor.system.activeWeapons.secondary.uuid) : null;
-// await existing?.delete();
-// itemData.system.active = true;
-// }
-// else if(element.classList.contains('inventory-weapon-section-first')){
-// const existing = this.actor.system.inventoryWeapons.first ? await fromUuid(this.actor.system.inventoryWeapons.first.uuid) : null;
-// await existing?.delete();
-
-// itemData.system.inventoryWeapon = 1;
-// }
-// else if(element.classList.contains('inventory-weapon-section-second')){
-// const existing = this.actor.system.inventoryWeapons.second ? await fromUuid(this.actor.system.inventoryWeapons.second.uuid) : null;
-// await existing?.delete();
-
-// itemData.system.inventoryWeapon = 2;
-// }
-// else return [];
-// }
-
-// if(item.type === 'armor'){
-// if(!element) return;
-
-// if(element.classList.contains('armor-section')){
-// const existing = this.actor.system.armor ? await fromUuid(this.actor.system.armor.uuid) : null;
-// await existing?.delete();
-// }
-
-// else return;
-// }
-
-// const createdItem = await this._onDropItemCreate(itemData);
-// createdItems.push(createdItem);
-
-// return createdItems;
-// }
-// }
-
-// async emulateItemDrop(data) {
-// const event = new DragEvent("drop", { altKey: game.keyboard.isModifierActive("Alt") });
-// return this._onDropItem(event, data);
-// }
-// }
diff --git a/module/enrichers/DualityRollEnricher.mjs b/module/enrichers/DualityRollEnricher.mjs
index 2e965db9..93c3a999 100644
--- a/module/enrichers/DualityRollEnricher.mjs
+++ b/module/enrichers/DualityRollEnricher.mjs
@@ -1,38 +1,36 @@
import { abilities } from '../config/actorConfig.mjs';
+import { rollCommandToJSON } from '../helpers/utils.mjs';
-export async function dualityRollEnricher(match, _options) {
- try {
- const {
- hope = 'd12',
- fear = 'd12',
- attribute,
- advantage,
- disadvantage
- } = JSON.parse(`{${match[1].replace(' ', ',').replace(/(\w+(?==))(=)/g, '"$1":')}}`);
- const dualityElement = document.createElement('span');
+export function dualityRollEnricher(match, _options) {
+ const roll = rollCommandToJSON(match[1]);
+ if (!roll) return match[0];
- const attributeLabel =
- attribute && abilities[attribute]
- ? game.i18n.format('DAGGERHEART.General.Check', {
- check: game.i18n.localize(abilities[attribute].label)
- })
- : null;
- const label = attributeLabel ?? game.i18n.localize('DAGGERHEART.General.Duality');
- dualityElement.innerHTML = `
-
- `;
-
- return dualityElement;
- } catch (_) {
- return match[0];
- }
+ return getDualityMessage(roll);
+}
+
+export function getDualityMessage(roll) {
+ const attributeLabel =
+ roll.attribute && abilities[roll.attribute]
+ ? game.i18n.format('DAGGERHEART.General.Check', {
+ check: game.i18n.localize(abilities[roll.attribute].label)
+ })
+ : null;
+ const label = attributeLabel ?? game.i18n.localize('DAGGERHEART.General.Duality');
+
+ const dualityElement = document.createElement('span');
+ dualityElement.innerHTML = `
+
+ `;
+
+ return dualityElement;
}
diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs
index 2b1bc536..74f9c575 100644
--- a/module/helpers/utils.mjs
+++ b/module/helpers/utils.mjs
@@ -22,17 +22,6 @@ const getCompendiumOptions = async compendium => {
};
export const getWidthOfText = (txt, fontsize, allCaps, bold) => {
- // if(getWidthOfText.e === undefined){
- // getWidthOfText.e = document.createElement('span');
- // getWidthOfText.e.style.display = "none";
- // document.body.appendChild(getWidthOfText.e);
- // }
- // if(getWidthOfText.e.style.fontSize !== fontsize)
- // getWidthOfText.e.style.fontSize = fontsize;
- // if(getWidthOfText.e.style.fontFamily !== 'Signika, sans-serif')
- // getWidthOfText.e.style.fontFamily = 'Signika, sans-serif';
- // getWidthOfText.e.innerText = txt;
- // return getWidthOfText.e.offsetWidth;
const text = allCaps ? txt.toUpperCase() : txt;
if (getWidthOfText.c === undefined) {
getWidthOfText.c = document.createElement('canvas');
@@ -82,3 +71,32 @@ export const generateId = (title, length) => {
.join('');
return Number.isNumeric(length) ? id.slice(0, length).padEnd(length, '0') : id;
};
+
+export const rollCommandToJSON = text => {
+ try {
+ return JSON.parse(`{${text.replaceAll(' ', ',').replace(/(\w+(?==))(=)/g, '"$1":')}}`);
+ } catch (_) {
+ return null;
+ }
+};
+
+export const getCommandTarget = () => {
+ let target = game.canvas.tokens.controlled.length > 0 ? game.canvas.tokens.controlled[0].actor : null;
+ if (!game.user.isGM) {
+ target = game.user.character;
+ if (!target) {
+ ui.notifications.error(game.i18n.localize('DAGGERHEART.Notification.Error.NoAssignedPlayerCharacter'));
+ return null;
+ }
+ }
+ if (!target) {
+ ui.notifications.error(game.i18n.localize('DAGGERHEART.Notification.Error.NoSelectedToken'));
+ return null;
+ }
+ if (target.type !== 'pc') {
+ ui.notifications.error(game.i18n.localize('DAGGERHEART.Notification.Error.OnlyUseableByPC'));
+ return null;
+ }
+
+ return target;
+};