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 6f3810a5..71dbd08a 100755 --- a/lang/en.json +++ b/lang/en.json @@ -825,7 +825,8 @@ "Input": "Input", "Dice": "Dice" }, - "Max": "Max" + "Max": "Max", + "NewEffect": "New Effect" }, "FeatureType": { "Normal": "Normal", @@ -1145,6 +1146,8 @@ "Appearance": "Appearance", "settings": "Settings" }, + "HopeFeatures": "Hope Features", + "Class Features": "Class Features", "Domains": "Domains", "DamageThresholds": { "Title": "Damage Thresholds", @@ -1254,7 +1257,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/class.mjs b/module/applications/sheets/items/class.mjs index c8f5c1e1..54f29361 100644 --- a/module/applications/sheets/items/class.mjs +++ b/module/applications/sheets/items/class.mjs @@ -1,8 +1,11 @@ +import { actionsTypes } from '../../../data/_module.mjs'; import { tagifyElement } from '../../../helpers/utils.mjs'; +import DHActionConfig from '../../config/Action.mjs'; import DaggerheartSheet from '../daggerheart-sheet.mjs'; const { ItemSheetV2 } = foundry.applications.sheets; const { TextEditor } = foundry.applications.ux; + export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) { static DEFAULT_OPTIONS = { tag: 'form', @@ -11,8 +14,9 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) { actions: { removeSubclass: this.removeSubclass, viewSubclass: this.viewSubclass, - deleteFeature: this.deleteFeature, + addFeature: this.addFeature, editFeature: this.editFeature, + deleteFeature: this.deleteFeature, removeItem: this.removeItem, viewItem: this.viewItem, removePrimaryWeapon: this.removePrimaryWeapon, @@ -151,6 +155,69 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) { await this.document.update({ 'system.characterGuide.suggestedArmor': null }, { diff: false }); } + 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 + }); + } + + getActionPath(type) { + return type === 'hope' ? 'hopeFeatures' : 'classFeatures'; + } + + static async addFeature(_, target) { + const actionPath = this.getActionPath(target.dataset.type); + const actionType = await this.selectActionType(); + const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack, + action = new cls( + { + _id: foundry.utils.randomID(), + systemPath: actionPath, + 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.${actionPath}`]: [...this.document.system[actionPath], action] }); + } + + static async editFeature(_, target) { + const action = this.document.system[this.getActionPath(target.dataset.type)].find( + x => x._id === target.dataset.feature + ); + await new DHActionConfig(action).render(true); + } + + static async deleteFeature(_, target) { + const actionPath = this.getActionPath(target.dataset.type); + await this.document.update({ + [`system.${actionPath}`]: this.document.system[actionPath].filter( + action => action._id !== target.dataset.feature + ) + }); + } + async _onDrop(event) { const data = TextEditor.getDragEventData(event); const item = await fromUuid(data.uuid); @@ -158,10 +225,6 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) { await this.document.update({ 'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid] }); - } else if (item.type === 'feature') { - await this.document.update({ - 'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid] - }); } else if (item.type === 'weapon') { if (event.currentTarget.classList.contains('primary-weapon-section')) { if (!this.document.system.characterGuide.suggestedPrimaryWeapon && !item.system.secondary) 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 7b877415..16f74945 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() { @@ -203,7 +204,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/class.mjs b/module/data/item/class.mjs index 335014b9..47acb712 100644 --- a/module/data/item/class.mjs +++ b/module/data/item/class.mjs @@ -1,5 +1,6 @@ import BaseDataItem from './base.mjs'; import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; +import ActionField from '../fields/actionField.mjs'; export default class DHClass extends BaseDataItem { /** @inheritDoc */ @@ -19,7 +20,8 @@ export default class DHClass extends BaseDataItem { domains: new fields.ArrayField(new fields.StringField(), { max: 2 }), classItems: new fields.ArrayField(new ForeignDocumentUUIDField({ type: 'Item' })), evasion: new fields.NumberField({ initial: 0, integer: true }), - features: new fields.ArrayField(new ForeignDocumentUUIDField({ type: 'Item' })), + hopeFeatures: new foundry.data.fields.ArrayField(new ActionField()), + classFeatures: new foundry.data.fields.ArrayField(new ActionField()), subclasses: new fields.ArrayField( new ForeignDocumentUUIDField({ type: 'Item', required: false, nullable: true, initial: undefined }) ), @@ -51,6 +53,10 @@ export default class DHClass extends BaseDataItem { }; } + get hopeFeature() { + return this.hopeFeatures.length > 0 ? this.hopeFeatures[0] : null; + } + async _preCreate(data, options, user) { const allowed = await super._preCreate(data, options, user); if (allowed === false) return; 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 c39c6df6..e5d27cb5 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -3579,7 +3579,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/global/partials/feature-section-item.hbs b/templates/sheets/global/partials/feature-section-item.hbs index ebaabefe..7bcf7736 100644 --- a/templates/sheets/global/partials/feature-section-item.hbs +++ b/templates/sheets/global/partials/feature-section-item.hbs @@ -9,7 +9,8 @@ @@ -17,7 +18,8 @@ diff --git a/templates/sheets/items/class/features.hbs b/templates/sheets/items/class/features.hbs index dfa386d1..8b615121 100644 --- a/templates/sheets/items/class/features.hbs +++ b/templates/sheets/items/class/features.hbs @@ -3,15 +3,25 @@ data-tab='{{tabs.features.id}}' data-group='{{tabs.features.group}}' > +
+
+ {{localize "DAGGERHEART.Sheets.Class.HopeFeatures"}} +
+ {{#each source.system.hopeFeatures as |feature index|}} + {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='hope' feature=feature}} + {{/each}} +
+
-
- {{localize "DAGGERHEART.Sheets.Feature.Tabs.Features"}} -
- {{#each source.system.features as |feature index|}} - {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' feature=feature}} - {{/each}} -
-
+
+ {{localize "DAGGERHEART.Sheets.Class.ClassFeatures"}} +
+ {{#each source.system.classFeatures as |feature index|}} + {{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='class' feature=feature}} + {{/each}} +
+
+
{{localize "TYPES.Item.subclass"}} 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}} +
    +
    +
    +