From 4a596179d8178d2f836a2264e764396330ffb7f2 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sun, 15 Jun 2025 02:50:29 +0200 Subject: [PATCH] Added Actions and effects --- daggerheart.mjs | 11 +- lang/en.json | 7 +- module/applications/config/Action.mjs | 6 +- module/applications/sheets/items/subclass.mjs | 136 ++++++++++++------ module/data/action/action.mjs | 5 +- module/data/item/subclass.mjs | 15 +- styles/daggerheart.css | 1 - styles/less/global/feature-section.less | 1 - templates/sheets/items/subclass/features.hbs | 6 +- .../items/subclass/parts/subclass-feature.hbs | 30 ++++ .../subclass/parts/subclass-features.hbs | 22 +++ 11 files changed, 182 insertions(+), 58 deletions(-) create mode 100644 templates/sheets/items/subclass/parts/subclass-feature.hbs create mode 100644 templates/sheets/items/subclass/parts/subclass-features.hbs diff --git a/daggerheart.mjs b/daggerheart.mjs index 747fd490..96b407d3 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -106,8 +106,13 @@ Hooks.once('init', () => { Hooks.on('ready', () => { ui.resources = new CONFIG.ui.resources(); - if(game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.DisplayFear) !== 'hide') ui.resources.render({ force: true }); - document.body.classList.toggle('theme-colorful', game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance).dualityColorScheme === DualityRollColor.colorful.value); + if (game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.DisplayFear) !== 'hide') + ui.resources.render({ force: true }); + document.body.classList.toggle( + 'theme-colorful', + game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance).dualityColorScheme === + DualityRollColor.colorful.value + ); }); Hooks.once('dicesoniceready', () => {}); @@ -269,6 +274,8 @@ const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/sheets/character/sections/loadout.hbs', 'systems/daggerheart/templates/sheets/character/parts/heritageCard.hbs', 'systems/daggerheart/templates/sheets/character/parts/advancementCard.hbs', + 'systems/daggerheart/templates/sheets/items/subclass/parts/subclass-features.hbs', + 'systems/daggerheart/templates/sheets/items/subclass/parts/subclass-feature.hbs', 'systems/daggerheart/templates/components/card-preview.hbs', 'systems/daggerheart/templates/views/levelup/parts/selectable-card-preview.hbs', 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs', diff --git a/lang/en.json b/lang/en.json index 0b43ee8f..17d2c9d5 100755 --- a/lang/en.json +++ b/lang/en.json @@ -816,7 +816,8 @@ "Input": "Input", "Dice": "Dice" }, - "Max": "Max" + "Max": "Max", + "NewEffect": "New Effect" }, "FeatureType": { "Normal": "Normal", @@ -1244,7 +1245,9 @@ "Description": "Description", "SubclassFeature": { "Description": "Description", - "Abilities": "Abilities" + "Abilities": "Abilities", + "Actions": "Actions", + "Effects": "Effects" } }, "Weapon": { diff --git a/module/applications/config/Action.mjs b/module/applications/config/Action.mjs index 6453f896..d8118fa3 100644 --- a/module/applications/config/Action.mjs +++ b/module/applications/config/Action.mjs @@ -86,11 +86,11 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) { static async updateForm(event, _, formData) { const submitData = this._prepareSubmitData(event, formData), data = foundry.utils.expandObject(foundry.utils.mergeObject(this.action.toObject(), submitData)), - newActions = this.action.parent.actions.map(x => x.toObject()); // Find better way + newActions = foundry.utils.getProperty(this.action.parent, this.action.systemPath).map(x => x.toObject()); // Find better way if (!newActions.findSplice(x => x._id === data._id, data)) newActions.push(data); - const updates = await this.action.parent.parent.update({ 'system.actions': newActions }); + const updates = await this.action.parent.parent.update({ [`system.${this.action.systemPath}`]: newActions }); if (!updates) return; - this.action = updates.system.actions[this.action.index]; + this.action = foundry.utils.getProperty(updates.system, this.action.systemPath)[this.action.index]; this.render(); } diff --git a/module/applications/sheets/items/subclass.mjs b/module/applications/sheets/items/subclass.mjs index 5869d84d..e6e9725f 100644 --- a/module/applications/sheets/items/subclass.mjs +++ b/module/applications/sheets/items/subclass.mjs @@ -1,28 +1,24 @@ -import DaggerheartSheet from '../daggerheart-sheet.mjs'; +import { actionsTypes } from '../../../data/_module.mjs'; +import DHActionConfig from '../../config/Action.mjs'; +import DhpApplicationMixin from '../daggerheart-sheet.mjs'; const { ItemSheetV2 } = foundry.applications.sheets; -const { TextEditor } = foundry.applications.ux; -const { duplicate, getProperty } = foundry.utils; -export default class SubclassSheet extends DaggerheartSheet(ItemSheetV2) { +export default class SubclassSheet extends DhpApplicationMixin(ItemSheetV2) { static DEFAULT_OPTIONS = { tag: 'form', classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'subclass'], position: { width: 600 }, window: { resizable: false }, actions: { - editAbility: this.editAbility, - deleteFeatureAbility: this.deleteFeatureAbility + addFeature: this.addFeature, + editFeature: this.editFeature, + deleteFeature: this.deleteFeature }, form: { handler: this.updateForm, submitOnChange: true, closeOnSubmit: false - }, - dragDrop: [ - { dragSelector: null, dropSelector: '.foundation-tab' }, - { dragSelector: null, dropSelector: '.specialization-tab' }, - { dragSelector: null, dropSelector: '.mastery-tab' } - ] + } }; static PARTS = { @@ -80,41 +76,99 @@ export default class SubclassSheet extends DaggerheartSheet(ItemSheetV2) { this.render(); } - static async editAbility(_, button) { - const feature = await fromUuid(button.dataset.ability); - feature.sheet.render(true); + static addFeature(_, target) { + if (target.dataset.type === 'action') this.addAction(target.dataset.level); + else this.addEffect(target.dataset.level); } - static async deleteFeatureAbility(event, button) { - event.preventDefault(); - event.stopPropagation(); - - const feature = button.dataset.feature; - const newAbilities = this.document.system[`${feature}Feature`].abilities.filter( - x => x.uuid !== button.dataset.ability - ); - const path = `system.${feature}Feature.abilities`; - - await this.document.update({ [path]: newAbilities }); + static async editFeature(_, target) { + if (target.dataset.type === 'action') this.editAction(target.dataset.level, target.dataset.feature); + else this.editEffect(target.dataset.feature); } - async _onDrop(event) { - event.preventDefault(); - const data = TextEditor.getDragEventData(event); - const item = await fromUuid(data.uuid); - if (!(item.type === 'feature' && item.system.type === SYSTEM.ITEM.featureTypes.subclass.id)) return; + static async deleteFeature(_, target) { + if (target.dataset.type === 'action') this.removeAction(target.dataset.level, target.dataset.feature); + else this.removeEffect(target.dataset.level, target.dataset.feature); + } - let featureField; - if (event.currentTarget.classList.contains('foundation-tab')) featureField = 'foundation'; - else if (event.currentTarget.classList.contains('specialization-tab')) featureField = 'specialization'; - else if (event.currentTarget.classList.contains('mastery-tab')) featureField = 'mastery'; - else return; + async #selectActionType() { + const content = await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/views/actionType.hbs', + { types: SYSTEM.ACTIONS.actionTypes } + ), + title = 'Select Action Type', + type = 'form', + data = {}; + return Dialog.prompt({ + title, + label: title, + content, + type, + callback: html => { + const form = html[0].querySelector('form'), + fd = new foundry.applications.ux.FormDataExtended(form); + foundry.utils.mergeObject(data, fd.object, { inplace: true }); + return data; + }, + rejectClose: false + }); + } - const path = `system.${featureField}Feature.abilities`; - const abilities = duplicate(getProperty(this.document, path)) || []; - const featureData = { name: item.name, img: item.img, uuid: item.uuid }; - abilities.push(featureData); + async addAction(level) { + const actionType = await this.#selectActionType(); + const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack, + action = new cls( + { + _id: foundry.utils.randomID(), + systemPath: `${level}.actions`, + type: actionType.type, + name: game.i18n.localize(SYSTEM.ACTIONS.actionTypes[actionType.type].name), + ...cls.getSourceConfig(this.document) + }, + { + parent: this.document + } + ); + await this.document.update({ [`system.${level}.actions`]: [...this.document.system[level].actions, action] }); + await new DHActionConfig( + this.document.system[level].actions[this.document.system[level].actions.length - 1] + ).render(true); + } - await this.document.update({ [path]: abilities }); + async addEffect(level) { + const embeddedItems = await this.document.createEmbeddedDocuments('ActiveEffect', [ + { name: game.i18n.localize('DAGGERHEART.Feature.NewEffect') } + ]); + await this.document.update({ + [`system.${level}.effects`]: [ + ...this.document.system[level].effects.map(x => x.uuid), + embeddedItems[0].uuid + ] + }); + } + + async editAction(level, id) { + const action = this.document.system[level].actions.find(x => x._id === id); + await new DHActionConfig(action).render(true); + } + + async editEffect(id) { + const effect = this.document.effects.get(id); + effect.sheet.render(true); + } + + async removeAction(level, id) { + await this.document.update({ + [`system.${level}.actions`]: this.document.system[level].actions.filter(action => action._id !== id) + }); + } + + async removeEffect(level, id) { + await this.document.effects.get(id).delete(); + await this.document.update({ + [`system.${level}.effects`]: this.document.system[level].effects + .filter(x => x && x.id !== id) + .map(effect => effect.uuid) + }); } } diff --git a/module/data/action/action.mjs b/module/data/action/action.mjs index 0462980f..da37fc67 100644 --- a/module/data/action/action.mjs +++ b/module/data/action/action.mjs @@ -55,6 +55,7 @@ export class DHBaseAction extends foundry.abstract.DataModel { static defineSchema() { return { _id: new fields.DocumentIdField(), + systemPath: new fields.StringField({ required: true, initial: 'actions' }), type: new fields.StringField({ initial: undefined, readonly: true, required: true }), name: new fields.StringField({ initial: undefined }), img: new fields.FilePathField({ initial: undefined, categories: ['IMAGE'], base64: false }), @@ -93,7 +94,7 @@ export class DHBaseAction extends foundry.abstract.DataModel { prepareData() {} get index() { - return this.parent.actions.indexOf(this); + return foundry.utils.getProperty(this.parent, this.systemPath).indexOf(this); } get item() { @@ -202,7 +203,7 @@ export class DHAttackAction extends DHBaseAction { static getRollType() { return 'weapon'; } - + get chatTitle() { return game.i18n.format('DAGGERHEART.Chat.AttackRoll.Title', { attack: this.item.name diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index bb315fcd..1e236ff4 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -1,6 +1,15 @@ +import ActionField from '../fields/actionField.mjs'; import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import BaseDataItem from './base.mjs'; +const featureSchema = () => { + return new foundry.data.fields.SchemaField({ + name: new foundry.data.fields.StringField({ required: true }), + effects: new foundry.data.fields.ArrayField(new ForeignDocumentUUIDField({ type: 'ActiveEffect' })), + actions: new foundry.data.fields.ArrayField(new ActionField()) + }); +}; + export default class DHSubclass extends BaseDataItem { /** @inheritDoc */ static get metadata() { @@ -22,9 +31,9 @@ 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' }), + foundationFeature: featureSchema(), + specializationFeature: featureSchema(), + masteryFeature: featureSchema(), featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }), isMulticlass: new fields.BooleanField({ initial: false }) }; diff --git a/styles/daggerheart.css b/styles/daggerheart.css index abdd8ac0..68a3ebb4 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -3544,7 +3544,6 @@ div.daggerheart.views.multiclass { } .sheet.daggerheart.dh-style.item .tab.features { padding: 0 10px; - max-height: 265px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: light-dark(#18162e, #f3c267) transparent; diff --git a/styles/less/global/feature-section.less b/styles/less/global/feature-section.less index a294926f..db1c117a 100644 --- a/styles/less/global/feature-section.less +++ b/styles/less/global/feature-section.less @@ -4,7 +4,6 @@ .sheet.daggerheart.dh-style.item { .tab.features { padding: 0 10px; - max-height: 265px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: light-dark(@dark-blue, @golden) transparent; diff --git a/templates/sheets/items/subclass/features.hbs b/templates/sheets/items/subclass/features.hbs index d9ebb3c1..d7f7cfd0 100644 --- a/templates/sheets/items/subclass/features.hbs +++ b/templates/sheets/items/subclass/features.hbs @@ -5,16 +5,16 @@ >
{{localize "DAGGERHEART.Sheets.Subclass.Tabs.Foundation"}} - {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' feature=source.system.foundationFeature}} + {{> 'systems/daggerheart/templates/sheets/items/subclass/parts/subclass-features.hbs' level='foundationFeature' feature=source.system.foundationFeature}}
{{localize "DAGGERHEART.Sheets.Subclass.Tabs.Specialization"}} - {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' feature=source.system.specializationFeature}} + {{> 'systems/daggerheart/templates/sheets/items/subclass/parts/subclass-features.hbs' level='specializationFeature' feature=source.system.specializationFeature}}
{{localize "DAGGERHEART.Sheets.Subclass.Tabs.Mastery"}} - {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' feature=source.system.masteryFeature}} + {{> 'systems/daggerheart/templates/sheets/items/subclass/parts/subclass-features.hbs' level='masteryFeature' feature=source.system.masteryFeature}}
\ No newline at end of file diff --git a/templates/sheets/items/subclass/parts/subclass-feature.hbs b/templates/sheets/items/subclass/parts/subclass-feature.hbs new file mode 100644 index 00000000..e818e406 --- /dev/null +++ b/templates/sheets/items/subclass/parts/subclass-feature.hbs @@ -0,0 +1,30 @@ +
  • +
    + +

    {{feature.name}}

    + {{#unless hideContrals}} + + {{/unless}} +
    +
  • \ No newline at end of file diff --git a/templates/sheets/items/subclass/parts/subclass-features.hbs b/templates/sheets/items/subclass/parts/subclass-features.hbs new file mode 100644 index 00000000..7bfb1496 --- /dev/null +++ b/templates/sheets/items/subclass/parts/subclass-features.hbs @@ -0,0 +1,22 @@ +
    +
    + {{localize "DAGGERHEART.Sheets.Subclass.SubclassFeature.Actions"}} + +
    + {{#each feature.actions}} + {{> 'systems/daggerheart/templates/sheets/items/subclass/parts/subclass-feature.hbs' level=../level type="action" id=this._id feature=this}} + {{/each}} +
    +
    + +
    + {{localize "DAGGERHEART.Sheets.Subclass.SubclassFeature.Effects"}} + +
    + {{#each feature.effects}} + {{> 'systems/daggerheart/templates/sheets/items/subclass/parts/subclass-feature.hbs' level=../level type="effect" id=this.id feature=this}} + {{/each}} +
    +
    +
    +