diff --git a/lang/en.json b/lang/en.json index ab522e16..cacf7d3a 100755 --- a/lang/en.json +++ b/lang/en.json @@ -440,6 +440,10 @@ "genericEffects": "Foundry Effects" } }, + "ImageSelect": { + "title": "Select Image", + "selectImage": "Select Image" + }, "Levelup": { "actions": { "creatureComfort": { diff --git a/module/applications/dialogs/_module.mjs b/module/applications/dialogs/_module.mjs index 84ba4037..b8e764c9 100644 --- a/module/applications/dialogs/_module.mjs +++ b/module/applications/dialogs/_module.mjs @@ -5,6 +5,7 @@ export { default as DamageDialog } from './damageDialog.mjs'; export { default as DamageReductionDialog } from './damageReductionDialog.mjs'; export { default as DeathMove } from './deathMove.mjs'; export { default as Downtime } from './downtime.mjs'; +export { default as ImageSelectDialog } from './imageSelectDialog.mjs'; export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs'; export { default as OwnershipSelection } from './ownershipSelection.mjs'; export { default as RerollDamageDialog } from './rerollDamageDialog.mjs'; diff --git a/module/applications/dialogs/beastformDialog.mjs b/module/applications/dialogs/beastformDialog.mjs index 35bac11c..3dd88d6c 100644 --- a/module/applications/dialogs/beastformDialog.mjs +++ b/module/applications/dialogs/beastformDialog.mjs @@ -276,7 +276,22 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat const featureItem = item; app.addEventListener( 'close', - () => resolve({ selected: app.selected, evolved: app.evolved, hybrid: app.hybrid, item: featureItem }), + async () => { + const selected = app.selected.toObject(); + const data = await game.system.api.data.items.DHBeastform.getWildcardImage( + app.configData.data.parent, + app.selected + ); + if (data) { + if (!data.selectedImage) selected = null; + else { + if (data.usesDynamicToken) selected.system.tokenRingImg = data.selectedImage; + else selected.system.tokenImg = data.selectedImage; + } + } + + resolve({ selected: selected, evolved: app.evolved, hybrid: app.hybrid, item: featureItem }); + }, { once: true } ); app.render({ force: true }); diff --git a/module/applications/dialogs/imageSelectDialog.mjs b/module/applications/dialogs/imageSelectDialog.mjs new file mode 100644 index 00000000..b54a45e0 --- /dev/null +++ b/module/applications/dialogs/imageSelectDialog.mjs @@ -0,0 +1,67 @@ +const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; + +export default class ImageSelectDialog extends HandlebarsApplicationMixin(ApplicationV2) { + constructor(titleName, images) { + super(); + + this.titleName = titleName; + this.images = images; + } + + static DEFAULT_OPTIONS = { + tag: 'form', + classes: ['daggerheart', 'dialog', 'dh-style', 'image-select'], + position: { + width: 600, + height: 'auto' + }, + window: { + icon: 'fa-solid fa-paw' + }, + actions: { + selectImage: ImageSelectDialog.#selectImage, + finishSelection: ImageSelectDialog.#finishSelection + } + }; + + get title() { + return this.titleName; + } + + /** @override */ + static PARTS = { + main: { template: 'systems/daggerheart/templates/dialogs/image-select/main.hbs' }, + footer: { template: 'systems/daggerheart/templates/dialogs/image-select/footer.hbs' } + }; + + async _prepareContext(_options) { + const context = await super._prepareContext(_options); + context.images = this.images; + context.selectedImage = this.selectedImage; + + return context; + } + + static #selectImage(_event, button) { + this.selectedImage = button.dataset.image ?? button.querySelector('img').dataset.image; + this.render(); + } + + static #finishSelection() { + this.close({ submitted: true }); + } + + async close(options = {}) { + if (!options.submitted) this.selectedImage = null; + + await super.close(); + } + + static async configure(title, images) { + return new Promise(resolve => { + const app = new this(title, images); + app.addEventListener('close', () => resolve(app.selectedImage), { once: true }); + app.render({ force: true }); + }); + } +} diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index b9859a01..227a1a39 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -844,6 +844,23 @@ export default class CharacterSheet extends DHBaseActorSheet { itemData.system.inVault = true; } + if (item.type === 'beastform') { + if (this.document.effects.find(x => x.type === 'beastform')) { + return ui.notifications.warn( + game.i18n.localize('DAGGERHEART.UI.Notifications.beastformAlreadyApplied') + ); + } + + const data = await game.system.api.data.items.DHBeastform.getWildcardImage(this.document, itemData); + if (data) { + if (!data.selectedImage) return; + else { + if (data.usesDynamicToken) itemData.system.tokenRingImg = data.selectedImage; + else itemData.system.tokenImg = data.selectedImage; + } + } + } + if (this.document.uuid === item.parent?.uuid) return this._onSortItem(event, itemData); const createdItem = await this._onDropItemCreate(itemData); diff --git a/module/data/fields/action/beastformField.mjs b/module/data/fields/action/beastformField.mjs index 0a1caea9..cefbcc21 100644 --- a/module/data/fields/action/beastformField.mjs +++ b/module/data/fields/action/beastformField.mjs @@ -79,7 +79,7 @@ export default class BeastformField extends fields.SchemaField { * @returns */ static async transform(selectedForm, evolvedData, hybridData) { - const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm.toObject(); + const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm; const beastformEffect = formData.effects.find(x => x.type === 'beastform'); if (!beastformEffect) { ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect'); diff --git a/module/data/item/beastform.mjs b/module/data/item/beastform.mjs index 3c9bef5c..8f5dffe6 100644 --- a/module/data/item/beastform.mjs +++ b/module/data/item/beastform.mjs @@ -33,11 +33,13 @@ export default class DHBeastform extends BaseDataItem { tokenImg: new fields.FilePathField({ initial: 'icons/svg/mystery-man.svg', categories: ['IMAGE'], + wildcard: true, base64: false }), tokenRingImg: new fields.FilePathField({ initial: 'icons/svg/mystery-man.svg', categories: ['IMAGE'], + wildcard: true, base64: false }), tokenSize: new fields.SchemaField({ @@ -108,6 +110,30 @@ export default class DHBeastform extends BaseDataItem { }; } + static async getWildcardImage(actor, beastform) { + const usesDynamicToken = actor.prototypeToken.ring.enabled && beastform.system.tokenRingImg; + const tokenPath = usesDynamicToken ? beastform.system.tokenRingImg : beastform.system.tokenImg; + const usesWildcard = tokenPath.includes('*'); + if (usesWildcard) { + const filePicker = new foundry.applications.apps.FilePicker.implementation(tokenPath); + const { files } = await foundry.applications.apps.FilePicker.implementation.browse( + filePicker.activeSource, + tokenPath, + { + wildcard: true, + type: 'image' + } + ); + const selectedImage = await game.system.api.applications.dialogs.ImageSelectDialog.configure( + game.i18n.localize('DAGGERHEART.APPLICATIONS.ImageSelect.title'), + files + ); + return { usesDynamicToken, selectedImage }; + } + + return null; + } + async _preCreate() { if (!this.actor) return; diff --git a/styles/less/dialog/image-select/sheet.less b/styles/less/dialog/image-select/sheet.less new file mode 100644 index 00000000..1548044a --- /dev/null +++ b/styles/less/dialog/image-select/sheet.less @@ -0,0 +1,33 @@ +.daggerheart.dh-style.dialog.image-select { + display: flex; + flex-direction: column; + + .images-container { + display: flex; + flex-wrap: wrap; + gap: 8px; + + img { + width: 136px; + height: 136px; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + opacity: 0.4; + + &.selected { + opacity: 1; + } + } + } + + .footer { + margin-top: 8px; + display: flex; + align-items: center; + gap: 8px; + + button { + flex: 1; + } + } +} diff --git a/styles/less/dialog/index.less b/styles/less/dialog/index.less index 65af4a71..d4104d3c 100644 --- a/styles/less/dialog/index.less +++ b/styles/less/dialog/index.less @@ -30,3 +30,5 @@ @import './multiclass-choice/sheet.less'; @import './reroll-dialog/sheet.less'; + +@import './image-select/sheet.less'; diff --git a/templates/dialogs/image-select/footer.hbs b/templates/dialogs/image-select/footer.hbs new file mode 100644 index 00000000..cd7d3d1a --- /dev/null +++ b/templates/dialogs/image-select/footer.hbs @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/templates/dialogs/image-select/main.hbs b/templates/dialogs/image-select/main.hbs new file mode 100644 index 00000000..099d88c3 --- /dev/null +++ b/templates/dialogs/image-select/main.hbs @@ -0,0 +1,5 @@ +
+ {{#each images as |image|}} + + {{/each}} +
\ No newline at end of file