diff --git a/daggerheart.mjs b/daggerheart.mjs index 0d78e8fe..22586e90 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -140,6 +140,7 @@ Hooks.once('init', () => { CONFIG.Combat.documentClass = documents.DhpCombat; CONFIG.ui.combat = applications.ui.DhCombatTracker; CONFIG.ui.chat = applications.ui.DhChatLog; + CONFIG.ui.hotbar = applications.ui.DhHotbar; CONFIG.Token.rulerClass = placeables.DhTokenRuler; CONFIG.ui.resources = applications.ui.DhFearTracker; diff --git a/lang/en.json b/lang/en.json index bf15d6be..ac71c319 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1350,8 +1350,8 @@ }, "DomainCard": { "type": "Type", - "foundation": "Foundation", "recallCost": "Recall Cost", + "foundationTitle": "Foundation", "specializationTitle": "Specialization", "masteryTitle": "Mastery" }, @@ -1562,7 +1562,18 @@ "noAvailableArmorMarks": "You have no more available armor marks", "notEnoughStress": "You don't have enough stress", "damageIgnore": "{character} did not take damage", - "featureIsMissing": "Feature is missing" + "featureIsMissing": "Feature is missing", + "actionIsMissing": "Action is missing", + "attackIsMissing": "Attack is missing", + "unownedActionMacro": "Cannot make a Use macro for an Action not on your character", + "unownedAttackMacro": "Cannot make a Use macro for an Attack that doesn't belong to one of your characters", + "featureNotHope": "This feature is used as something else than a Hope feature and cannot be used here.", + "featureNotClass": "This feature is used as something else than a Class feature and cannot be used here.", + "featureNotPrimary": "This feature is used as something else than a Primary feature and cannot be used here.", + "featureNotSecondary": "This feature is used as something else than a Secondary feature and cannot be used here.", + "featureNotFoundation": "This feature is used as something else than a Foundation feature and cannot be used here.", + "featureNotSpecialization": "This feature is used as something else than a Specialization feature and cannot be used here.", + "featureNotMastery": "This feature is used as something else than a Mastery feature and cannot be used here." }, "Tooltip": { "openItemWorld": "Open Item World", diff --git a/module/applications/characterCreation/characterCreation.mjs b/module/applications/characterCreation/characterCreation.mjs index ed0ee5a7..b8759cc5 100644 --- a/module/applications/characterCreation/characterCreation.mjs +++ b/module/applications/characterCreation/characterCreation.mjs @@ -506,9 +506,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl name: this.setup.ancestryName ?? this.setup.primaryAncestry.name, system: { ...this.setup.primaryAncestry.system, - features: [primaryAncestryFeature.uuid, secondaryAncestryFeature.uuid], - primaryFeature: primaryAncestryFeature.uuid, - secondaryFeature: secondaryAncestryFeature.uuid + features: [primaryAncestryFeature.uuid, secondaryAncestryFeature.uuid] } }; diff --git a/module/applications/dialogs/resourceDiceDialog.mjs b/module/applications/dialogs/resourceDiceDialog.mjs index b79ff895..8205dee5 100644 --- a/module/applications/dialogs/resourceDiceDialog.mjs +++ b/module/applications/dialogs/resourceDiceDialog.mjs @@ -67,7 +67,7 @@ export default class ResourceDiceDialog extends HandlebarsApplicationMixin(Appli static async rerollDice() { const max = itemAbleRollParse(this.item.system.resource.max, this.actor, this.item); - const diceFormula = `${max}d${this.item.system.resource.dieFaces}`; + const diceFormula = `${max}${this.item.system.resource.dieFaces}`; const roll = await new Roll(diceFormula).evaluate(); if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true); this.rollValues = roll.terms[0].results.map(x => ({ value: x.result, used: false })); diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index 7102fa1c..78df0aac 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -23,7 +23,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { actions: { openSettings: DHBaseActorSheet.#openSettings }, - dragDrop: [] + dragDrop: [{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null }] }; /**@type {typeof DHBaseActorSettings}*/ @@ -49,4 +49,27 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { static async #openSettings() { await this.settingSheet.render({ force: true }); } + + /* -------------------------------------------- */ + /* Application Drag/Drop */ + /* -------------------------------------------- */ + + /** + * On dragStart on the item. + * @param {DragEvent} event - The drag event + */ + async _onDragStart(event) { + const attackItem = event.currentTarget.closest('.inventory-item[data-type="attack"]'); + + if (attackItem) { + const attackData = { + type: 'Attack', + actorUuid: this.document.uuid, + img: this.document.system.attack.img, + fromInternal: true + }; + event.dataTransfer.setData('text/plain', JSON.stringify(attackData)); + event.dataTransfer.setDragImage(attackItem.querySelector('img'), 60, 0); + } + } } diff --git a/module/applications/sheets/api/base-item.mjs b/module/applications/sheets/api/base-item.mjs index 0c25a44d..723b4802 100644 --- a/module/applications/sheets/api/base-item.mjs +++ b/module/applications/sheets/api/base-item.mjs @@ -30,7 +30,8 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { }, dragDrop: [ { dragSelector: null, dropSelector: '.tab.features .drop-section' }, - { dragSelector: '.feature-item', dropSelector: null } + { dragSelector: '.feature-item', dropSelector: null }, + { dragSelector: '.action-item', dropSelector: null } ] }; @@ -258,6 +259,23 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { const featureData = { type: 'Item', data: { ...feature.toObject(), _id: null }, fromInternal: true }; event.dataTransfer.setData('text/plain', JSON.stringify(featureData)); event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0); + } else { + const actionItem = event.currentTarget.closest('.action-item'); + if (actionItem) { + const action = this.document.system.actions[actionItem.dataset.index]; + if (!action) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionIsMissing')); + return; + } + + const actionData = { + type: 'Action', + data: { ...action.toObject(), id: action.id, itemUuid: this.document.uuid }, + fromInternal: true + }; + event.dataTransfer.setData('text/plain', JSON.stringify(actionData)); + event.dataTransfer.setDragImage(actionItem.querySelector('img'), 60, 0); + } } } diff --git a/module/applications/sheets/items/ancestry.mjs b/module/applications/sheets/items/ancestry.mjs index bd9e3792..de55301d 100644 --- a/module/applications/sheets/items/ancestry.mjs +++ b/module/applications/sheets/items/ancestry.mjs @@ -63,22 +63,8 @@ export default class AncestrySheet extends DHHeritageSheet { event.stopPropagation(); const target = button.closest('.feature-item'); const feature = this.document.system[`${target.dataset.type}Feature`]; - const featureExists = feature && Object.keys(feature).length > 0; - if (featureExists) { - const confirmed = await foundry.applications.api.DialogV2.confirm({ - window: { - title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', { - type: game.i18n.localize(`TYPES.Item.feature`), - name: feature.name - }) - }, - content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: feature.name }) - }); - if (!confirmed) return; - } - - if (featureExists && target.dataset.type === 'primary') await feature.update({ 'system.primary': null }); + if (feature) await feature.update({ 'system.subType': null }); await this.document.update({ 'system.features': this.document.system.features.filter(x => x && x.uuid !== feature.uuid).map(x => x.uuid) }); @@ -94,15 +80,18 @@ export default class AncestrySheet extends DHHeritageSheet { */ async _onDrop(event) { event.stopPropagation(); - event.preventDefault(); - const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); const item = await fromUuid(data.uuid); if (item?.type === 'feature') { const subType = event.target.closest('.primary-feature') ? 'primary' : 'secondary'; - await item.update({ 'system.subType': subType }); + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes[subType]) { + const error = subType === 'primary' ? 'featureNotPrimary' : 'featureNotSecondary'; + ui.notifications.warn(game.i18n.localize(`DAGGERHEART.UI.Notifications.${error}`)); + return; + } + await item.update({ 'system.subType': subType }); await this.document.update({ 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] }); diff --git a/module/applications/sheets/items/class.mjs b/module/applications/sheets/items/class.mjs index b49105be..3a34bcec 100644 --- a/module/applications/sheets/items/class.mjs +++ b/module/applications/sheets/items/class.mjs @@ -78,6 +78,7 @@ export default class ClassSheet extends DHBaseItemSheet { /* -------------------------------------------- */ async _onDrop(event) { + event.stopPropagation(); const data = TextEditor.getDragEventData(event); const item = await fromUuid(data.uuid); const target = event.target.closest('fieldset.drop-section'); @@ -87,12 +88,24 @@ export default class ClassSheet extends DHBaseItemSheet { }); } else if (item.type === 'feature') { if (target.classList.contains('hope-feature')) { + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.hope) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotHope')); + return; + } + + await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.hope }); await this.document.update({ - 'system.hopeFeatures': [...this.document.system.hopeFeatures.map(x => x.uuid), item.uuid] + 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] }); } else if (target.classList.contains('class-feature')) { + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.class) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotClass')); + return; + } + + await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.class }); await this.document.update({ - 'system.classFeatures': [...this.document.system.classFeatures.map(x => x.uuid), item.uuid] + 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] }); } } else if (item.type === 'weapon') { @@ -177,28 +190,25 @@ export default class ClassSheet extends DHBaseItemSheet { doc.sheet.render({ force: true }); } - getActionPath(type) { - return type === 'hope' ? 'hopeFeatures' : 'classFeatures'; - } - static async addFeature(_, target) { - const actionPath = this.getActionPath(target.dataset.type); const feature = await game.items.documentClass.create({ type: 'feature', - name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }) + name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }), + system: { + subType: + target.dataset.type === 'hope' + ? CONFIG.DH.ITEM.featureSubTypes.hope + : CONFIG.DH.ITEM.featureSubTypes.class + } }); await this.document.update({ - [`system.${actionPath}`]: [ - ...this.document.system[actionPath].filter(x => x).map(x => x.uuid), - feature.uuid - ] + [`system.features`]: [...this.document.system.features.filter(x => x).map(x => x.uuid), feature.uuid] }); } static async editFeature(_, button) { const target = button.closest('.feature-item'); - const actionPath = this.getActionPath(button.dataset.type); - const feature = this.document.system[actionPath].find(x => x?.id === target.dataset.featureId); + const feature = this.document.system.features.find(x => x?.id === target.dataset.featureId); if (!feature) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing')); return; @@ -210,10 +220,16 @@ export default class ClassSheet extends DHBaseItemSheet { static async deleteFeature(event, button) { event.stopPropagation(); const target = button.closest('.feature-item'); - const actionPath = this.getActionPath(button.dataset.type); + + const feature = this.document.system.features.find( + feature => feature && feature.id === target.dataset.featureId + ); + if (feature) { + await feature.update({ 'system.subType': null }); + } await this.document.update({ - [`system.${actionPath}`]: this.document.system[actionPath] + [`system.features`]: this.document.system.features .filter(feature => feature && feature.id !== target.dataset.featureId) .map(x => x.uuid) }); diff --git a/module/applications/sheets/items/subclass.mjs b/module/applications/sheets/items/subclass.mjs index 31eca43a..fcf62f69 100644 --- a/module/applications/sheets/items/subclass.mjs +++ b/module/applications/sheets/items/subclass.mjs @@ -40,28 +40,46 @@ export default class SubclassSheet extends DHBaseItemSheet { static async addFeature(_, target) { const feature = await game.items.documentClass.create({ type: 'feature', - name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }) + name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }), + system: { + subType: + target.dataset.type === 'foundation' + ? CONFIG.DH.ITEM.featureSubTypes.foundation + : target.dataset.type === 'specialization' + ? CONFIG.DH.ITEM.featureSubTypes.specialization + : CONFIG.DH.ITEM.featureSubTypes.mastery + } }); await this.document.update({ - [`system.${target.dataset.type}`]: feature.uuid + [`system.features`]: [...this.document.system.features.map(x => x.uuid), feature.uuid] }); } static async editFeature(_, button) { - const feature = this.document.system[button.dataset.type]; + const feature = this.document.system.features.find(x => x.id === button.dataset.feature); if (!feature) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing')); return; } + if (feature) { + await feature.update({ 'system.subType': null }); + } + feature.sheet.render(true); } - static async deleteFeature(event, button) { + static async deleteFeature(event, target) { event.stopPropagation(); + const feature = this.document.system.features.find(feature => feature.id === target.dataset.feature); + if (feature) { + await feature.update({ 'system.subType': null }); + } await this.document.update({ - [`system.${button.dataset.type}`]: null + [`system.features`]: this.document.system.features + .filter(feature => feature && feature.id !== target.dataset.feature) + .map(x => x.uuid) }); } @@ -82,18 +100,45 @@ export default class SubclassSheet extends DHBaseItemSheet { } async _onDrop(event) { + event.stopPropagation(); + const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); if (data.fromInternal) return; const item = await fromUuid(data.uuid); - if (item?.type === 'feature') { - const dropSection = event.target.closest('.drop-section'); - if (this.document.system[dropSection.dataset.type]) { - ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsFull')); - return; - } + const target = event.target.closest('fieldset.drop-section'); + if (item.type === 'feature') { + if (target.dataset.type === 'foundation') { + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.foundation) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotFoundation')); + return; + } - await this.document.update({ [`system.${dropSection.dataset.type}`]: item.uuid }); + await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.foundation }); + await this.document.update({ + 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] + }); + } else if (target.dataset.type === 'specialization') { + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.specialization) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotSpecialization')); + return; + } + + await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.specialization }); + await this.document.update({ + 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] + }); + } else if (target.dataset.type === 'mastery') { + if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.mastery) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotMastery')); + return; + } + + await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.mastery }); + await this.document.update({ + 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] + }); + } } } } diff --git a/module/applications/ui/_module.mjs b/module/applications/ui/_module.mjs index f1c32840..6a17a61e 100644 --- a/module/applications/ui/_module.mjs +++ b/module/applications/ui/_module.mjs @@ -2,3 +2,4 @@ export { default as DhChatLog } from './chatLog.mjs'; export { default as DhCombatTracker } from './combatTracker.mjs'; export * as DhCountdowns from './countdowns.mjs'; export { default as DhFearTracker } from './fearTracker.mjs'; +export { default as DhHotbar } from './hotbar.mjs'; diff --git a/module/applications/ui/hotbar.mjs b/module/applications/ui/hotbar.mjs new file mode 100644 index 00000000..b4ebc05c --- /dev/null +++ b/module/applications/ui/hotbar.mjs @@ -0,0 +1,129 @@ +export default class DhHotbar extends foundry.applications.ui.Hotbar { + constructor(options) { + super(options); + + this.setupHooks(); + } + + static async useItem(uuid) { + const item = await fromUuid(uuid); + if (!item) { + return ui.notifications.warn('WARNING.ObjectDoesNotExist', { + format: { + name: game.i18n.localize('Document'), + identifier: uuid + } + }); + } + + await item.use({}); + } + + static async useAction(itemUuid, actionId) { + const item = await foundry.utils.fromUuid(itemUuid); + if (!item) { + return ui.notifications.warn('WARNING.ObjectDoesNotExist', { + format: { + name: game.i18n.localize('Document'), + identifier: itemUuid + } + }); + } + + const action = item.system.actions.find(x => x.id === actionId); + if (!action) { + return ui.notifications.warn('DAGGERHEART.UI.Notifications.actionIsMissing'); + } + + await action.use({}); + } + + static async useAttack(actorUuid) { + const actor = await foundry.utils.fromUuid(actorUuid); + if (!actor) { + return ui.notifications.warn('WARNING.ObjectDoesNotExist', { + format: { + name: game.i18n.localize('Document'), + identifier: actorUuid + } + }); + } + + const attack = actor.system.attack; + if (!attack) { + return ui.notifications.warn('DAGGERHEART.UI.Notifications.attackIsMissing'); + } + + await attack.use({}); + } + + setupHooks() { + Hooks.on('hotbarDrop', (bar, data, slot) => { + if (data.type === 'Item') { + const item = foundry.utils.fromUuidSync(data.uuid); + if (item.uuid.startsWith('Compendium') || !item.isOwned || !item.isOwner) return true; + + switch (item.type) { + case 'ancestry': + case 'community': + case 'class': + case 'subclass': + return true; + default: + this.createItemMacro(item, slot); + return false; + } + } else if (data.type === 'Action') { + const item = foundry.utils.fromUuidSync(data.data.itemUuid); + if (item.uuid.startsWith('Compendium')) return true; + if (!item.isOwned || !item.isOwner) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.unownedActionMacro')); + return false; + } + + this.createActionMacro(data, slot); + return false; + } else if (data.type === 'Attack') { + const actor = foundry.utils.fromUuidSync(data.actorUuid); + if (actor.uuid.startsWith('Compendium')) return true; + if (!actor.isOwner) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.unownedAttackMacro')); + return false; + } + + this.createAttackMacro(data, slot); + return false; + } + }); + } + + async createItemMacro(data, slot) { + const macro = await Macro.implementation.create({ + name: `${game.i18n.localize('Display')} ${name}`, + type: CONST.MACRO_TYPES.SCRIPT, + img: data.img, + command: `await game.system.api.applications.ui.DhHotbar.useItem("${data.uuid}");` + }); + await game.user.assignHotbarMacro(macro, slot); + } + + async createActionMacro(data, slot) { + const macro = await Macro.implementation.create({ + name: `${game.i18n.localize('Display')} ${name}`, + type: CONST.MACRO_TYPES.SCRIPT, + img: data.data.img, + command: `await game.system.api.applications.ui.DhHotbar.useAction("${data.data.itemUuid}", "${data.data.id}");` + }); + await game.user.assignHotbarMacro(macro, slot); + } + + async createAttackMacro(data, slot) { + const macro = await Macro.implementation.create({ + name: `${game.i18n.localize('Display')} ${name}`, + type: CONST.MACRO_TYPES.SCRIPT, + img: data.img, + command: `await game.system.api.applications.ui.DhHotbar.useAttack("${data.actorUuid}");` + }); + await game.user.assignHotbarMacro(macro, slot); + } +} diff --git a/module/config/actorConfig.mjs b/module/config/actorConfig.mjs index 02cfd4a9..d05dc81c 100644 --- a/module/config/actorConfig.mjs +++ b/module/config/actorConfig.mjs @@ -411,7 +411,7 @@ export const levelupData = { }; export const subclassFeatureLabels = { - 1: 'DAGGERHEART.ITEMS.DomainCard.foundation', + 1: 'DAGGERHEART.ITEMS.DomainCard.foundationTitle', 2: 'DAGGERHEART.ITEMS.DomainCard.specializationTitle', 3: 'DAGGERHEART.ITEMS.DomainCard.masteryTitle' }; diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs index ede5ef08..b26a26ca 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -1322,7 +1322,12 @@ export const featureTypes = { export const featureSubTypes = { primary: 'primary', - secondary: 'secondary' + secondary: 'secondary', + hope: 'hope', + class: 'class', + foundation: 'foundation', + specialization: 'specialization', + mastery: 'mastery' }; export const actionTypes = { diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 9d494b69..b56d26db 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -87,6 +87,8 @@ export default class DhCharacter extends BaseDataActor { value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }), subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }) }), + advantageSources: new fields.ArrayField(new fields.StringField()), + disadvantageSources: new fields.ArrayField(new fields.StringField()), levelData: new fields.EmbeddedDataField(DhLevelData), bonuses: new fields.SchemaField({ roll: new fields.SchemaField({ @@ -285,23 +287,23 @@ export default class DhCharacter extends BaseDataActor { features = []; for (let item of this.parent.items) { - if (item.system.type === CONFIG.DH.ITEM.featureTypes.ancestry.id) { + if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.ancestry.id) { ancestryFeatures.push(item); - } else if (item.system.type === CONFIG.DH.ITEM.featureTypes.community.id) { + } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.community.id) { communityFeatures.push(item); - } else if (item.system.type === CONFIG.DH.ITEM.featureTypes.class.id) { + } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.class.id) { classFeatures.push(item); - } else if (item.system.type === CONFIG.DH.ITEM.featureTypes.subclass.id) { + } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) { const subclassState = this.class.subclass.system.featureState; - const identifier = item.system.identifier; + const subType = item.system.subType; if ( - identifier === 'foundationFeature' || - (identifier === 'specializationFeature' && subclassState >= 2) || - (identifier === 'masterFeature' && subclassState >= 3) + subType === CONFIG.DH.ITEM.featureSubTypes.foundation || + (subType === CONFIG.DH.ITEM.featureSubTypes.specialization && subclassState >= 2) || + (subType === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3) ) { subclassFeatures.push(item); } - } else if (item.system.type === CONFIG.DH.ITEM.featureTypes.companion.id) { + } else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.companion.id) { companionFeatures.push(item); } else if (item.type === 'feature' && !item.system.type) { features.push(item); diff --git a/module/data/countdowns.mjs b/module/data/countdowns.mjs index 881ecf20..34e8b790 100644 --- a/module/data/countdowns.mjs +++ b/module/data/countdowns.mjs @@ -135,8 +135,8 @@ export const registerCountdownHooks = () => { if (application) { foundry.applications.instances.get(application)?.render(); } else { - foundry.applications.instances.get('narrative-countdowns').render(); - foundry.applications.instances.get('encounter-countdowns').render(); + foundry.applications.instances.get('narrative-countdowns')?.render(); + foundry.applications.instances.get('encounter-countdowns')?.render(); } return false; diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs index e99c85c3..24e5e0cc 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -53,11 +53,14 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { }), diceStates: new fields.TypedObjectField( new fields.SchemaField({ - value: new fields.NumberField({ integer: true, nullable: true, initial: null }), + value: new fields.NumberField({ integer: true, initial: 1, min: 1 }), used: new fields.BooleanField({ initial: false }) }) ), - dieFaces: new fields.StringField({ initial: '4' }) + dieFaces: new fields.StringField({ + choices: CONFIG.DH.GENERAL.diceTypes, + initial: CONFIG.DH.GENERAL.diceTypes.d4 + }) }, { nullable: true, initial: null } ); diff --git a/module/data/item/beastform.mjs b/module/data/item/beastform.mjs index b7ea5cb9..d19521d0 100644 --- a/module/data/item/beastform.mjs +++ b/module/data/item/beastform.mjs @@ -69,6 +69,7 @@ export default class DHBeastform extends BaseDataItem { const beastformEffect = this.parent.effects.find(x => x.type === 'beastform'); await beastformEffect.updateSource({ + changes: [...beastformEffect.changes, { key: 'system.advantageSources', mode: 2, value: this.advantageOn }], system: { characterTokenData: { tokenImg: this.parent.parent.prototypeToken.texture.src, diff --git a/module/data/item/class.mjs b/module/data/item/class.mjs index 281b0a48..b8a9ab81 100644 --- a/module/data/item/class.mjs +++ b/module/data/item/class.mjs @@ -27,8 +27,7 @@ export default class DHClass extends BaseDataItem { label: 'DAGGERHEART.GENERAL.hitPoints.plural' }), evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }), - hopeFeatures: new ForeignDocumentUUIDArrayField({ type: 'Item' }), - classFeatures: new ForeignDocumentUUIDArrayField({ type: 'Item' }), + features: new ForeignDocumentUUIDArrayField({ type: 'Item' }), subclasses: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }), inventory: new fields.SchemaField({ take: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }), @@ -52,12 +51,18 @@ export default class DHClass extends BaseDataItem { }; } - get hopeFeature() { - return this.hopeFeatures.length > 0 ? this.hopeFeatures[0] : null; + get hopeFeatures() { + return ( + this.features.filter(x => x?.system?.subType === CONFIG.DH.ITEM.featureSubTypes.hope) ?? + (this.features.filter(x => !x).length > 0 ? {} : null) + ); } - get features() { - return [...this.hopeFeatures.filter(x => x), ...this.classFeatures.filter(x => x)]; + get classFeatures() { + return ( + this.features.filter(x => x?.system?.subType === CONFIG.DH.ITEM.featureSubTypes.class) ?? + (this.features.filter(x => !x).length > 0 ? {} : null) + ); } async _preCreate(data, options, user) { diff --git a/module/data/item/domainCard.mjs b/module/data/item/domainCard.mjs index 89bbfb40..df60b9d1 100644 --- a/module/data/item/domainCard.mjs +++ b/module/data/item/domainCard.mjs @@ -29,7 +29,6 @@ export default class DHDomainCard extends BaseDataItem { required: true, initial: CONFIG.DH.DOMAIN.cardTypes.ability.id }), - foundation: new fields.BooleanField({ initial: false }), inVault: new fields.BooleanField({ initial: false }), actions: new fields.ArrayField(new ActionField()) }; diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index 265c2566..e0a76092 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -1,4 +1,4 @@ -import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; +import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; import BaseDataItem from './base.mjs'; export default class DHSubclass extends BaseDataItem { @@ -22,20 +22,22 @@ export default class DHSubclass extends BaseDataItem { nullable: true, initial: null }), - foundationFeature: new ForeignDocumentUUIDField({ type: 'Item' }), - specializationFeature: new ForeignDocumentUUIDField({ type: 'Item' }), - masteryFeature: new ForeignDocumentUUIDField({ type: 'Item' }), + features: new ForeignDocumentUUIDArrayField({ type: 'Item' }), featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }), isMulticlass: new fields.BooleanField({ initial: false }) }; } - get features() { - return [ - { ...this.foundationFeature?.toObject(), identifier: 'foundationFeature' }, - { ...this.specializationFeature?.toObject(), identifier: 'specializationFeature' }, - { ...this.masteryFeature?.toObject(), identifier: 'masteryFeature' } - ]; + get foundationFeatures() { + return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.foundation); + } + + get specializationFeatures() { + return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.specialization); + } + + get masteryFeatures() { + return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.mastery); } async _preCreate(data, options, user) { diff --git a/module/documents/tooltipManager.mjs b/module/documents/tooltipManager.mjs index ab1eefd6..e622059d 100644 --- a/module/documents/tooltipManager.mjs +++ b/module/documents/tooltipManager.mjs @@ -43,6 +43,24 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti this.constructor.TOOLTIP_DIRECTIONS.UP ); } + + const isAdvantage = element.dataset.tooltip?.startsWith('#advantage#'); + const isDisadvantage = element.dataset.tooltip?.startsWith('#disadvantage#'); + if (isAdvantage || isDisadvantage) { + const actorUuid = element.dataset.tooltip.slice(isAdvantage ? 11 : 14); + const actor = await foundry.utils.fromUuid(actorUuid); + + if (actor) { + html = await foundry.applications.handlebars.renderTemplate( + `systems/daggerheart/templates/ui/tooltip/advantage.hbs`, + { + sources: isAdvantage ? actor.system.advantageSources : actor.system.disadvantageSources + } + ); + + this.tooltip.innerHTML = html; + } + } } super.activate(element, { ...options, html: html }); diff --git a/styles/less/dialog/dice-roll/roll-selection.less b/styles/less/dialog/dice-roll/roll-selection.less index 55db8bb7..af6c3c20 100644 --- a/styles/less/dialog/dice-roll/roll-selection.less +++ b/styles/less/dialog/dice-roll/roll-selection.less @@ -100,6 +100,10 @@ font-size: 14px; line-height: 17px; } + + .advantage-chip-tooltip { + pointer-events: all; + } } .advantage-chip { diff --git a/templates/dialogs/dice-roll/resourceDice.hbs b/templates/dialogs/dice-roll/resourceDice.hbs index 33c93386..bebe8f4e 100644 --- a/templates/dialogs/dice-roll/resourceDice.hbs +++ b/templates/dialogs/dice-roll/resourceDice.hbs @@ -3,14 +3,14 @@ {{#times (rollParsed item.system.resource.max actor item numerical=true)}} {{#with (ifThen (lookup ../diceStates this) (lookup ../diceStates this) this) as | state |}}