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 0732f34e..2f04cc32 100755 --- a/lang/en.json +++ b/lang/en.json @@ -264,6 +264,7 @@ } }, "Tiers": { + "singular": "Tier", "tier1": "Tier 1", "tier2": "Tier 2", "tier3": "Tier 3", @@ -630,7 +631,7 @@ "WeaponFeature": { "Barrier": { "Name": "Barrier", - "Description": "+{armorBonus} to Armor Score; -1 to Evasion" + "Description": "+{armorScore} to Armor Score; -1 to Evasion" }, "Bonded": { "Name": "Bonded", @@ -700,6 +701,10 @@ "Name": "Greedy", "Description": "Spend a handful of gold to gain a +1 bonus to your Proficiency on a damage roll." }, + "Healing": { + "Name": "Healing", + "Description": "During downtime, automatically clear a Hit Point." + }, "Heavy": { "Name": "Heavy", "Description": "-1 to Evasion" @@ -724,6 +729,10 @@ "Name": "Locked On", "Description": "On a successful attack, your next attack against the same target with your primary weapon automatically succeeds." }, + "Lucky": { + "Name": "Lucky", + "Description": "On a failed attack, you can mark a Stress to reroll your attack." + }, "Long": { "Name": "Long", "Description": "This weapon's attack targets all adversaries in a line within range." @@ -758,7 +767,7 @@ }, "Protective": { "Name": "Protective", - "Description": "+{armorBonus} to Armor Score" + "Description": "+{tier} to Armor Score" }, "Quick": { "Name": "Quick", @@ -824,7 +833,8 @@ "Input": "Input", "Dice": "Dice" }, - "Max": "Max" + "Max": "Max", + "NewEffect": "New Effect" }, "FeatureType": { "Normal": "Normal", @@ -1144,6 +1154,8 @@ "Appearance": "Appearance", "settings": "Settings" }, + "HopeFeatures": "Hope Features", + "Class Features": "Class Features", "Domains": "Domains", "DamageThresholds": { "Title": "Damage Thresholds", @@ -1193,8 +1205,9 @@ } } }, - "Heritage": { - "Title": "Abilities" + "Global": { + "Actions": "Actions", + "Effects": "Effects" }, "DomainCard": { "Type": "Type", @@ -1252,7 +1265,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 082a9a3f..84152578 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/ancestry.mjs b/module/applications/sheets/items/ancestry.mjs index e6a92e1e..3d20df7a 100644 --- a/module/applications/sheets/items/ancestry.mjs +++ b/module/applications/sheets/items/ancestry.mjs @@ -1,88 +1,13 @@ -import DaggerheartSheet from '../daggerheart-sheet.mjs'; +import DHHeritageSheetV2 from './heritage.mjs'; const { ItemSheetV2 } = foundry.applications.sheets; -export default class AncestrySheet extends DaggerheartSheet(ItemSheetV2) { +export default class AncestrySheet extends DHHeritageSheetV2(ItemSheetV2) { static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'ancestry'], - position: { width: 450, height: 700 }, - actions: { - editFeature: this.editFeature, - deleteFeature: this.deleteFeature - }, - form: { - handler: this.updateForm, - submitOnChange: true, - closeOnSubmit: false - }, - dragDrop: [{ dragSelector: null, dropSelector: null }] + classes: ['ancestry'] }; static PARTS = { header: { template: 'systems/daggerheart/templates/sheets/items/ancestry/header.hbs' }, - tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, - description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' }, - features: { - template: 'systems/daggerheart/templates/sheets/global/tabs/tab-feature-section.hbs', - scrollable: ['.features'] - } + ...super.PARTS }; - - static TABS = { - description: { - active: true, - cssClass: '', - group: 'primary', - id: 'description', - icon: null, - label: 'DAGGERHEART.Sheets.Feature.Tabs.Description' - }, - features: { - active: false, - cssClass: '', - group: 'primary', - id: 'features', - icon: null, - label: 'DAGGERHEART.Sheets.Feature.Tabs.Features' - } - }; - - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.document = this.document; - context.tabs = super._getTabs(this.constructor.TABS); - - return context; - } - - static async updateForm(event, _, formData) { - await this.document.update(formData.object); - this.render(); - } - - static async editFeature(_, target) { - const feature = await fromUuid(target.dataset.feature); - feature.sheet.render(true); - } - - static async deleteFeature(event, target) { - event.preventDefault(); - event.stopPropagation(); - await this.item.update({ - 'system.abilities': this.item.system.abilities.filter(x => x.uuid !== target.dataset.feature) - }); - } - - async _onDrop(event) { - const data = TextEditor.getDragEventData(event); - const item = await fromUuid(data.uuid); - if (item.type === 'feature' && item.system.type === SYSTEM.ITEM.featureTypes.ancestry.id) { - await this.document.update({ - 'system.abilities': [ - ...this.document.system.abilities, - { img: item.img, name: item.name, uuid: item.uuid } - ] - }); - } - } } diff --git a/module/applications/sheets/items/armor.mjs b/module/applications/sheets/items/armor.mjs index 43e6dce9..32c482ba 100644 --- a/module/applications/sheets/items/armor.mjs +++ b/module/applications/sheets/items/armor.mjs @@ -1,3 +1,5 @@ +import { armorFeatures } from '../../../config/itemConfig.mjs'; +import { tagifyElement } from '../../../helpers/utils.mjs'; import DHItemSheetV2 from '../item.mjs'; const { ItemSheetV2 } = foundry.applications.sheets; @@ -20,4 +22,28 @@ export default class ArmorSheet extends DHItemSheetV2(ItemSheetV2) { scrollable: ['.settings'] } }; + + async _preparePartContext(partId, context) { + super._preparePartContext(partId, context); + + switch (partId) { + case 'settings': + context.features = this.document.system.features.map(x => x.value); + break; + } + + return context; + } + + _attachPartListeners(partId, htmlElement, options) { + super._attachPartListeners(partId, htmlElement, options); + + const featureInput = htmlElement.querySelector('.features-input'); + tagifyElement(featureInput, armorFeatures, this.onFeatureSelect.bind(this)); + } + + async onFeatureSelect(features) { + await this.document.update({ 'system.features': features.map(x => ({ value: x.value })) }); + this.render(true); + } } 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/community.mjs b/module/applications/sheets/items/community.mjs index 0c15e97e..890ff605 100644 --- a/module/applications/sheets/items/community.mjs +++ b/module/applications/sheets/items/community.mjs @@ -1,88 +1,13 @@ -import DaggerheartSheet from '../daggerheart-sheet.mjs'; +import DHHeritageSheetV2 from './heritage.mjs'; const { ItemSheetV2 } = foundry.applications.sheets; -export default class CommunitySheet extends DaggerheartSheet(ItemSheetV2) { +export default class CommunitySheet extends DHHeritageSheetV2(ItemSheetV2) { static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'community'], - position: { width: 450, height: 700 }, - actions: { - editFeature: this.editFeature, - deleteFeature: this.deleteFeature - }, - form: { - handler: this.updateForm, - submitOnChange: true, - closeOnSubmit: false - }, - dragDrop: [{ dragSelector: null, dropSelector: null }] + classes: ['community'] }; static PARTS = { header: { template: 'systems/daggerheart/templates/sheets/items/community/header.hbs' }, - tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, - description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' }, - features: { - template: 'systems/daggerheart/templates/sheets/global/tabs/tab-feature-section.hbs', - scrollable: ['.features'] - } + ...super.PARTS }; - - static TABS = { - description: { - active: true, - cssClass: '', - group: 'primary', - id: 'description', - icon: null, - label: 'DAGGERHEART.Sheets.Feature.Tabs.Description' - }, - features: { - active: false, - cssClass: '', - group: 'primary', - id: 'features', - icon: null, - label: 'DAGGERHEART.Sheets.Feature.Tabs.Features' - } - }; - - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.document = this.document; - context.tabs = super._getTabs(this.constructor.TABS); - - return context; - } - - static async updateForm(event, _, formData) { - await this.document.update(formData.object); - this.render(); - } - - static async editFeature(_, target) { - const feature = await fromUuid(target.dataset.feature); - feature.sheet.render(true); - } - - static async deleteFeature(event, target) { - event.preventDefault(); - event.stopPropagation(); - await this.item.update({ - 'system.abilities': this.item.system.abilities.filter(x => x.uuid !== target.dataset.feature) - }); - } - - async _onDrop(event) { - const data = TextEditor.getDragEventData(event); - const item = await fromUuid(data.uuid); - if (item.type === 'feature' && item.system.type === SYSTEM.ITEM.featureTypes.community.id) { - await this.document.update({ - 'system.abilities': [ - ...this.document.system.abilities, - { img: item.img, name: item.name, uuid: item.uuid } - ] - }); - } - } } diff --git a/module/applications/sheets/items/heritage.mjs b/module/applications/sheets/items/heritage.mjs new file mode 100644 index 00000000..44d950ca --- /dev/null +++ b/module/applications/sheets/items/heritage.mjs @@ -0,0 +1,147 @@ +import { actionsTypes } from '../../../data/_module.mjs'; +import DHActionConfig from '../../config/Action.mjs'; +import DHItemMixin from '../item.mjs'; + +export default function DHHeritageMixin(Base) { + return class DHHeritageSheetV2 extends DHItemMixin(Base) { + static DEFAULT_OPTIONS = { + tag: 'form', + position: { width: 450, height: 700 }, + actions: { + addAction: this.addAction, + editAction: this.editAction, + removeAction: this.removeAction, + addEffect: this.addEffect, + editEffect: this.editEffect, + removeEffect: this.removeEffect + }, + form: { + handler: this.updateForm, + submitOnChange: true, + closeOnSubmit: false + } + }; + + static PARTS = { + tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, + description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' }, + actions: { + template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs', + scrollable: ['.actions'] + }, + effects: { + template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs', + scrollable: ['.effects'] + } + }; + + static TABS = { + description: { + active: true, + cssClass: '', + group: 'primary', + id: 'description', + icon: null, + label: 'DAGGERHEART.Sheets.Feature.Tabs.Description' + }, + actions: { + active: false, + cssClass: '', + group: 'primary', + id: 'actions', + icon: null, + label: 'DAGGERHEART.Sheets.Feature.Tabs.Actions' + }, + effects: { + active: false, + cssClass: '', + group: 'primary', + id: 'effects', + icon: null, + label: 'DAGGERHEART.Sheets.Feature.Tabs.Effects' + } + }; + + async _prepareContext(_options) { + const context = await super._prepareContext(_options); + context.document = this.document; + context.tabs = super._getTabs(this.constructor.TABS); + + return context; + } + + static async updateForm(event, _, formData) { + await this.document.update(formData.object); + this.render(); + } + + 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 + }); + } + + static async addAction() { + const actionType = await this.selectActionType(); + const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack, + action = new cls( + { + _id: foundry.utils.randomID(), + 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.actions': [...this.document.system.actions, action] }); + } + + static async editAction(_, button) { + const action = this.document.system.actions[button.dataset.index]; + await new DHActionConfig(action).render(true); + } + + static async removeAction(event, button) { + event.stopPropagation(); + await this.document.update({ + 'system.actions': this.document.system.actions.filter( + (_, index) => index !== Number.parseInt(button.dataset.index) + ) + }); + } + + static async addEffect() { + await this.document.createEmbeddedDocuments('ActiveEffect', [ + { name: game.i18n.localize('DAGGERHEART.Feature.NewEffect') } + ]); + } + + static async editEffect(_, target) { + const effect = this.document.effects.get(target.dataset.effect); + effect.sheet.render(true); + } + + static async removeEffect(_, target) { + await this.document.effects.get(target.dataset.effect).delete(); + } + }; +} 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/applications/sheets/items/weapon.mjs b/module/applications/sheets/items/weapon.mjs index a54c0140..b381e8bf 100644 --- a/module/applications/sheets/items/weapon.mjs +++ b/module/applications/sheets/items/weapon.mjs @@ -1,3 +1,5 @@ +import { weaponFeatures } from '../../../config/itemConfig.mjs'; +import { tagifyElement } from '../../../helpers/utils.mjs'; import DHItemSheetV2 from '../item.mjs'; const { ItemSheetV2 } = foundry.applications.sheets; @@ -19,4 +21,28 @@ export default class WeaponSheet extends DHItemSheetV2(ItemSheetV2) { scrollable: ['.settings'] } }; + + async _preparePartContext(partId, context) { + super._preparePartContext(partId, context); + + switch (partId) { + case 'settings': + context.features = this.document.system.features.map(x => x.value); + break; + } + + return context; + } + + _attachPartListeners(partId, htmlElement, options) { + super._attachPartListeners(partId, htmlElement, options); + + const featureInput = htmlElement.querySelector('.features-input'); + tagifyElement(featureInput, weaponFeatures, this.onFeatureSelect.bind(this)); + } + + async onFeatureSelect(features) { + await this.document.update({ 'system.features': features.map(x => ({ value: x.value })) }); + this.render(true); + } } diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs index fe2037f2..a2a786b9 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -5,15 +5,78 @@ export const armorFeatures = { }, channeling: { label: 'DAGGERHEART.ArmorFeature.Channeling.Name', - description: 'DAGGERHEART.ArmorFeature.Channeling.Description' + description: 'DAGGERHEART.ArmorFeature.Channeling.Description', + effects: [ + { + changes: [ + { + key: 'system.bonuses.spellcast', + mode: 2, + value: '1' + } + ] + } + ] }, difficult: { label: 'DAGGERHEART.ArmorFeature.Difficult.Name', - description: 'DAGGERHEART.ArmorFeature.Difficult.Description' + description: 'DAGGERHEART.ArmorFeature.Difficult.Description', + effects: [ + { + changes: [ + { + key: 'system.traits.agility.bonus', + mode: 2, + value: '-1' + }, + { + key: 'system.traits.strength.bonus', + mode: 2, + value: '-1' + }, + { + key: 'system.traits.finesse.bonus', + mode: 2, + value: '-1' + }, + { + key: 'system.traits.instinct.bonus', + mode: 2, + value: '-1' + }, + { + key: 'system.traits.presence.bonus', + mode: 2, + value: '-1' + }, + { + key: 'system.traits.knowledge.bonus', + mode: 2, + value: '-1' + }, + { + key: 'system.evasion.bonus', + mode: 2, + value: '-1' + } + ] + } + ] }, flexible: { label: 'DAGGERHEART.ArmorFeature.Flexible.Name', - description: 'DAGGERHEART.ArmorFeature.Flexible.Description' + description: 'DAGGERHEART.ArmorFeature.Flexible.Description', + effects: [ + { + changes: [ + { + key: 'system.evasion.bonus', + mode: 2, + value: '1' + } + ] + } + ] }, fortified: { label: 'DAGGERHEART.ArmorFeature.Fortified.Name', @@ -21,11 +84,33 @@ export const armorFeatures = { }, gilded: { label: 'DAGGERHEART.ArmorFeature.Gilded.Name', - description: 'DAGGERHEART.ArmorFeature.Gilded.Description' + description: 'DAGGERHEART.ArmorFeature.Gilded.Description', + effects: [ + { + changes: [ + { + key: 'system.traits.presence.bonus', + mode: 2, + value: '1' + } + ] + } + ] }, heavy: { label: 'DAGGERHEART.ArmorFeature.Heavy.Name', - description: 'DAGGERHEART.ArmorFeature.Heavy.Description' + description: 'DAGGERHEART.ArmorFeature.Heavy.Description', + effects: [ + { + changes: [ + { + key: 'system.evasion.bonus', + mode: 2, + value: '-1' + } + ] + } + ] }, hopeful: { label: 'DAGGERHEART.ArmorFeature.Hopeful.Name', @@ -77,7 +162,23 @@ export const armorFeatures = { }, veryheavy: { label: 'DAGGERHEART.ArmorFeature.VeryHeavy.Name', - description: 'DAGGERHEART.ArmorFeature.VeryHeavy.Description' + description: 'DAGGERHEART.ArmorFeature.VeryHeavy.Description', + effects: [ + { + changes: [ + { + key: 'system.evasion.bonus', + mode: 2, + value: '-2' + }, + { + key: 'system.traits.agility.bonus', + mode: 2, + value: '-1' + } + ] + } + ] }, warded: { label: 'DAGGERHEART.ArmorFeature.Warded.Name', @@ -89,13 +190,41 @@ export const weaponFeatures = { barrier: { label: 'DAGGERHEART.WeaponFeature.Barrier.Name', description: 'DAGGERHEART.WeaponFeature.Barrier.Description', - override: { - armorBonus: 1 - } + effects: [ + { + changes: [ + { + key: 'system.bonuses.armorScore', + mode: 2, + value: '@system.tier + 1' + } + ] + }, + { + changes: [ + { + key: 'system.evasion.bonus', + mode: 2, + value: '-1' + } + ] + } + ] }, bonded: { label: 'DAGGERHEART.WeaponFeature.Bonded.Name', - description: 'DAGGERHEART.WeaponFeature.Bonded.Description' + description: 'DAGGERHEART.WeaponFeature.Bonded.Description', + effects: [ + { + changes: [ + { + key: 'system.bonuses.damage', + mode: 2, + value: 'system.levelData.levels.current' + } + ] + } + ] }, bouncing: { label: 'DAGGERHEART.WeaponFeature.Bouncing.Name', @@ -103,7 +232,27 @@ export const weaponFeatures = { }, brave: { label: 'DAGGERHEART.WeaponFeature.Brave.Name', - description: 'DAGGERHEART.WeaponFeature.Brave.Description' + description: 'DAGGERHEART.WeaponFeature.Brave.Description', + effects: [ + { + changes: [ + { + key: 'system.evasion.bonus', + mode: 2, + value: '-1' + } + ] + }, + { + changes: [ + { + key: 'system.damageThresholds.severe', + mode: 2, + value: '3' + } + ] + } + ] }, brutal: { label: 'DAGGERHEART.WeaponFeature.Brutal.Name', @@ -111,15 +260,55 @@ export const weaponFeatures = { }, charged: { label: 'DAGGERHEART.WeaponFeature.Charged.Name', - description: 'DAGGERHEART.WeaponFeature.Charged.Description' + description: 'DAGGERHEART.WeaponFeature.Charged.Description', + actions: [ + { + type: 'effect', + name: 'DAGGERHEART.WeaponFeature.Concussive.Name', + img: 'icons/skills/melee/shield-damaged-broken-brown.webp', + actionType: 'action', + cost: [ + { + type: 'stress', + value: 1 + } + ] + // Should add an effect with path system.proficiency.bonus +1 + } + ] }, concussive: { label: 'DAGGERHEART.WeaponFeature.Concussive.Name', - description: 'DAGGERHEART.WeaponFeature.Concussive.Description' + description: 'DAGGERHEART.WeaponFeature.Concussive.Description', + actions: [ + { + type: 'resource', + name: 'DAGGERHEART.WeaponFeature.Concussive.Name', + img: 'icons/skills/melee/shield-damaged-broken-brown.webp', + actionType: 'action', + cost: [ + { + type: 'hope', + value: 1 + } + ] + } + ] }, cumbersome: { label: 'DAGGERHEART.WeaponFeature.Cumbersome.Name', - description: 'DAGGERHEART.WeaponFeature.Cumbersome.Description' + description: 'DAGGERHEART.WeaponFeature.Cumbersome.Description', + effects: [ + { + changes: [ + { + key: 'system.traits.finesse.bonus', + mode: 2, + value: '-1' + } + ] + } + ] }, deadly: { label: 'DAGGERHEART.WeaponFeature.Deadly.Name', @@ -128,18 +317,64 @@ export const weaponFeatures = { deflecting: { label: 'DAGGERHEART.WeaponFeature.Deflecting.Name', description: 'DAGGERHEART.WeaponFeature.Deflecting.Description' + // actions: [{ + // type: 'effect', + // name: 'DAGGERHEART.WeaponFeature.Deflecting.Name', + // img: 'icons/skills/melee/strike-flail-destructive-yellow.webp', + // actionType: 'reaction', + // cost: [{ + // type: 'armorSlot', // Needs armorSlot as type + // value: 1 + // }], + // }], }, destructive: { label: 'DAGGERHEART.WeaponFeature.Destructive.Name', - description: 'DAGGERHEART.WeaponFeature.Destructive.Description' + description: 'DAGGERHEART.WeaponFeature.Destructive.Description', + effects: [ + { + changes: [ + { + key: 'system.traits.agility.bonus', + mode: 2, + value: '-1' + } + ] + } + ] }, devastating: { label: 'DAGGERHEART.WeaponFeature.Devastating.Name', - description: 'DAGGERHEART.WeaponFeature.Devastating.Description' + description: 'DAGGERHEART.WeaponFeature.Devastating.Description', + actions: [ + { + type: 'resource', + name: 'DAGGERHEART.WeaponFeature.Devastating.Name', + img: 'icons/skills/melee/strike-flail-destructive-yellow.webp', + actionType: 'action', + cost: [ + { + type: 'stress', + value: 1 + } + ] + } + ] }, doubleduty: { label: 'DAGGERHEART.WeaponFeature.DoubleDuty.Name', - description: 'DAGGERHEART.WeaponFeature.DoubleDuty.Description' + description: 'DAGGERHEART.WeaponFeature.DoubleDuty.Description', + effects: [ + { + changes: [ + { + key: 'system.bonuses.armorScore', + mode: 2, + value: '1' + } + ] + } + ] }, doubledup: { label: 'DAGGERHEART.WeaponFeature.DoubledUp.Name', @@ -155,15 +390,61 @@ export const weaponFeatures = { }, grappling: { label: 'DAGGERHEART.WeaponFeature.Grappling.Name', - description: 'DAGGERHEART.WeaponFeature.Grappling.Description' + description: 'DAGGERHEART.WeaponFeature.Grappling.Description', + actions: [ + { + type: 'resource', + name: 'DAGGERHEART.WeaponFeature.Grappling.Name', + img: 'icons/magic/control/debuff-chains-ropes-net-white.webp', + actionType: 'action', + cost: [ + { + type: 'stress', + value: 1 + } + ] + } + ] }, greedy: { label: 'DAGGERHEART.WeaponFeature.Greedy.Name', description: 'DAGGERHEART.WeaponFeature.Greedy.Description' }, + healing: { + label: 'DAGGERHEART.WeaponFeature.Healing.Name', + description: 'DAGGERHEART.WeaponFeature.Healing.Description', + actions: [ + { + type: 'healing', + name: 'DAGGERHEART.WeaponFeature.Healing.Name', + img: 'icons/magic/life/cross-beam-green.webp', + actionType: 'action', + healing: { + type: 'health', + value: { + custom: { + enabled: true, + formula: '1' + } + } + } + } + ] + }, heavy: { label: 'DAGGERHEART.WeaponFeature.Heavy.Name', - description: 'DAGGERHEART.WeaponFeature.Heavy.Description' + description: 'DAGGERHEART.WeaponFeature.Heavy.Description', + effects: [ + { + changes: [ + { + key: 'system.evasion.bonus', + mode: 2, + value: '-1' + } + ] + } + ] }, hooked: { label: 'DAGGERHEART.WeaponFeature.Hooked.Name', @@ -189,13 +470,56 @@ export const weaponFeatures = { label: 'DAGGERHEART.WeaponFeature.Long.Name', description: 'DAGGERHEART.WeaponFeature.Long.Description' }, + lucky: { + label: 'DAGGERHEART.WeaponFeature.Lucky.Name', + description: 'DAGGERHEART.WeaponFeature.Lucky.Description', + actions: [ + { + type: 'resource', + name: 'DAGGERHEART.WeaponFeature.Lucky.Name', + img: 'icons/magic/control/buff-luck-fortune-green.webp', + actionType: 'action', + cost: [ + { + type: 'stress', + value: 1 + } + ] + } + ] + }, massive: { label: 'DAGGERHEART.WeaponFeature.Massive.Name', - description: 'DAGGERHEART.WeaponFeature.Massive.Description' + description: 'DAGGERHEART.WeaponFeature.Massive.Description', + effects: [ + { + changes: [ + { + key: 'system.evasion.bonus', + mode: 2, + value: '-1' + } + ] + } + ] }, painful: { label: 'DAGGERHEART.WeaponFeature.Painful.Name', - description: 'DAGGERHEART.WeaponFeature.Painful.Description' + description: 'DAGGERHEART.WeaponFeature.Painful.Description', + actions: [ + { + type: 'resource', + name: 'DAGGERHEART.WeaponFeature.Painful.Name', + img: 'icons/skills/wounds/injury-face-impact-orange.webp', + actionType: 'action', + cost: [ + { + type: 'stress', + value: 1 + } + ] + } + ] }, paired: { label: 'DAGGERHEART.WeaponFeature.Paired.Name', @@ -223,17 +547,50 @@ export const weaponFeatures = { protective: { label: 'DAGGERHEART.WeaponFeature.Protective.Name', description: 'DAGGERHEART.WeaponFeature.Protective.Description', - override: { - armorBonus: 1 - } + effects: [ + { + changes: [ + { + key: 'system.bonuses.armorScore', + mode: 2, + value: '@system.tier' + } + ] + } + ] }, quick: { label: 'DAGGERHEART.WeaponFeature.Quick.Name', - description: 'DAGGERHEART.WeaponFeature.Quick.Description' + description: 'DAGGERHEART.WeaponFeature.Quick.Description', + actions: [ + { + type: 'resource', + name: 'DAGGERHEART.WeaponFeature.Quick.Name', + img: 'icons/skills/movement/arrow-upward-yellow.webp', + actionType: 'action', + cost: [ + { + type: 'stress', + value: 1 + } + ] + } + ] }, reliable: { label: 'DAGGERHEART.WeaponFeature.Reliable.Name', - description: 'DAGGERHEART.WeaponFeature.Reliable.Description' + description: 'DAGGERHEART.WeaponFeature.Reliable.Description', + effects: [ + { + changes: [ + { + key: 'system.bonuses.attack', + mode: 2, + value: 1 + } + ] + } + ] }, reloading: { label: 'DAGGERHEART.WeaponFeature.Reloading.Name', @@ -265,7 +622,21 @@ export const weaponFeatures = { }, startling: { label: 'DAGGERHEART.WeaponFeature.Startling.Name', - description: 'DAGGERHEART.WeaponFeature.Startling.Description' + description: 'DAGGERHEART.WeaponFeature.Startling.Description', + actions: [ + { + type: 'resource', + name: 'DAGGERHEART.WeaponFeature.Startling.Name', + img: 'icons/magic/control/fear-fright-mask-orange.webp', + actionType: 'action', + cost: [ + { + type: 'stress', + value: 1 + } + ] + } + ] }, timebending: { label: 'DAGGERHEART.WeaponFeature.Timebending.Name', diff --git a/module/data/action/action.mjs b/module/data/action/action.mjs index a5f31248..e8a9c9b2 100644 --- a/module/data/action/action.mjs +++ b/module/data/action/action.mjs @@ -36,6 +36,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 }), description: new fields.HTMLField(), @@ -119,7 +120,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() { diff --git a/module/data/action/actionDice.mjs b/module/data/action/actionDice.mjs index 591a2baf..8ec327a0 100644 --- a/module/data/action/actionDice.mjs +++ b/module/data/action/actionDice.mjs @@ -23,7 +23,7 @@ export class DHActionDiceData extends foundry.abstract.DataModel { getFormula(actor) { return this.custom.enabled ? this.custom.formula - : `${actor.system[this.multiplier].value ?? 1}${this.dice}${this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''}`; + : `${actor.system[this.multiplier]?.total ?? 1}${this.dice}${this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''}`; } } diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 2bc3ca06..1ebe217c 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -84,7 +84,12 @@ export default class DhCharacter extends BaseDataActor { value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }), subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }) }), - levelData: new fields.EmbeddedDataField(DhPCLevelData) + levelData: new fields.EmbeddedDataField(DhPCLevelData), + bonuses: new fields.SchemaField({ + attack: new fields.NumberField({ integer: true, initial: 0 }), + spellcast: new fields.NumberField({ integer: true, initial: 0 }), + armorScore: new fields.NumberField({ integer: true, initial: 0 }) + }) }; } diff --git a/module/data/item/ancestry.mjs b/module/data/item/ancestry.mjs index 0359e9fc..8af6e081 100644 --- a/module/data/item/ancestry.mjs +++ b/module/data/item/ancestry.mjs @@ -1,12 +1,13 @@ +import ActionField from '../fields/actionField.mjs'; import BaseDataItem from './base.mjs'; export default class DHAncestry extends BaseDataItem { /** @inheritDoc */ static get metadata() { return foundry.utils.mergeObject(super.metadata, { - label: "TYPES.Item.ancestry", - type: "ancestry", - hasDescription: true, + label: 'TYPES.Item.ancestry', + type: 'ancestry', + hasDescription: true }); } @@ -15,7 +16,7 @@ export default class DHAncestry extends BaseDataItem { const fields = foundry.data.fields; return { ...super.defineSchema(), - //TODO: add features field + actions: new fields.ArrayField(new ActionField()) }; } } diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index 7ee20c29..b0fdf0ae 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -1,14 +1,15 @@ import BaseDataItem from './base.mjs'; import ActionField from '../fields/actionField.mjs'; +import { armorFeatures } from '../../config/itemConfig.mjs'; export default class DHArmor extends BaseDataItem { /** @inheritDoc */ static get metadata() { return foundry.utils.mergeObject(super.metadata, { - label: "TYPES.Item.armor", - type: "armor", + label: 'TYPES.Item.armor', + type: 'armor', hasDescription: true, - isQuantifiable: true, + isQuantifiable: true }); } @@ -17,9 +18,16 @@ export default class DHArmor extends BaseDataItem { const fields = foundry.data.fields; return { ...super.defineSchema(), + tier: new fields.NumberField({ required: true, integer: true, initial: 1, min: 1 }), equipped: new fields.BooleanField({ initial: false }), baseScore: new fields.NumberField({ integer: true, initial: 0 }), - feature: new fields.StringField({ choices: SYSTEM.ITEM.armorFeatures, blank: true }), + features: new fields.ArrayField( + new fields.SchemaField({ + value: new fields.StringField({ required: true, choices: SYSTEM.ITEM.armorFeatures, blank: true }), + effectIds: new fields.ArrayField(new fields.StringField({ required: true })), + actionIds: new fields.ArrayField(new fields.StringField({ required: true })) + }) + ), marks: new fields.SchemaField({ max: new fields.NumberField({ initial: 6, integer: true }), value: new fields.NumberField({ initial: 0, integer: true }) @@ -35,4 +43,47 @@ export default class DHArmor extends BaseDataItem { get featureInfo() { return this.feature ? CONFIG.daggerheart.ITEM.armorFeatures[this.feature] : null; } + + async _preUpdate(changes, options, user) { + const allowed = await super._preUpdate(changes, options, user); + if (allowed === false) return false; + + if (changes.system.features) { + const removed = this.features.filter(x => !changes.system.features.includes(x)); + const added = changes.system.features.filter(x => !this.features.includes(x)); + + for (var feature of removed) { + for (var effectId of feature.effectIds) { + await this.parent.effects.get(effectId).delete(); + } + + changes.system.actions = this.actions.filter(x => !feature.actionIds.includes(x._id)); + } + + for (var feature of added) { + const featureData = armorFeatures[feature.value]; + if (featureData.effects?.length > 0) { + const embeddedItems = await this.parent.createEmbeddedDocuments('ActiveEffect', [ + { + name: game.i18n.localize(featureData.label), + description: game.i18n.localize(featureData.description), + changes: featureData.effects.flatMap(x => x.changes) + } + ]); + feature.effectIds = embeddedItems.map(x => x.id); + } + if (featureData.actions?.length > 0) { + const newActions = featureData.actions.map(action => { + const cls = actionsTypes[action.type]; + return new cls( + { ...action, _id: foundry.utils.randomID(), name: game.i18n.localize(action.name) }, + { parent: this } + ); + }); + changes.system.actions = [...this.actions, ...newActions]; + feature.actionIds = newActions.map(x => x._id); + } + } + } + } } 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/community.mjs b/module/data/item/community.mjs index ffa6f921..d0ede69a 100644 --- a/module/data/item/community.mjs +++ b/module/data/item/community.mjs @@ -1,22 +1,22 @@ +import ActionField from '../fields/actionField.mjs'; import BaseDataItem from './base.mjs'; export default class DHCommunity extends BaseDataItem { /** @inheritDoc */ static get metadata() { return foundry.utils.mergeObject(super.metadata, { - label: "TYPES.Item.community", - type: "community", - hasDescription: true, + label: 'TYPES.Item.community', + type: 'community', + hasDescription: true }); } - /** @inheritDoc */ static defineSchema() { const fields = foundry.data.fields; return { ...super.defineSchema(), - //TODO: add features field + actions: new fields.ArrayField(new ActionField()) }; } } 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/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index c007bd35..3da7705e 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -1,8 +1,8 @@ import BaseDataItem from './base.mjs'; import FormulaField from '../fields/formulaField.mjs'; -import PseudoDocumentsField from '../fields/pseudoDocumentsField.mjs'; -import BaseFeatureData from '../pseudo-documents/feature/baseFeatureData.mjs'; import ActionField from '../fields/actionField.mjs'; +import { weaponFeatures } from '../../config/itemConfig.mjs'; +import { actionsTypes } from '../../data/_module.mjs'; export default class DHWeapon extends BaseDataItem { /** @inheritDoc */ @@ -23,6 +23,7 @@ export default class DHWeapon extends BaseDataItem { const fields = foundry.data.fields; return { ...super.defineSchema(), + tier: new fields.NumberField({ required: true, integer: true, initial: 1, min: 1 }), equipped: new fields.BooleanField({ initial: false }), //SETTINGS @@ -39,14 +40,57 @@ export default class DHWeapon extends BaseDataItem { initial: 'physical' }) }), - feature: new fields.StringField({ choices: SYSTEM.ITEM.weaponFeatures, blank: true }), - featureTest: new PseudoDocumentsField(BaseFeatureData, { - required: true, - nullable: true, - max: 1, - validTypes: ['weapon'] - }), + features: new fields.ArrayField( + new fields.SchemaField({ + value: new fields.StringField({ required: true, choices: SYSTEM.ITEM.weaponFeatures, blank: true }), + effectIds: new fields.ArrayField(new fields.StringField({ required: true })), + actionIds: new fields.ArrayField(new fields.StringField({ required: true })) + }) + ), actions: new fields.ArrayField(new ActionField()) }; } + + async _preUpdate(changes, options, user) { + const allowed = await super._preUpdate(changes, options, user); + if (allowed === false) return false; + + if (changes.system.features) { + const removed = this.features.filter(x => !changes.system.features.includes(x)); + const added = changes.system.features.filter(x => !this.features.includes(x)); + + for (var feature of removed) { + for (var effectId of feature.effectIds) { + await this.parent.effects.get(effectId).delete(); + } + + changes.system.actions = this.actions.filter(x => !feature.actionIds.includes(x._id)); + } + + for (var feature of added) { + const featureData = weaponFeatures[feature.value]; + if (featureData.effects?.length > 0) { + const embeddedItems = await this.parent.createEmbeddedDocuments('ActiveEffect', [ + { + name: game.i18n.localize(featureData.label), + description: game.i18n.localize(featureData.description), + changes: featureData.effects.flatMap(x => x.changes) + } + ]); + feature.effectIds = embeddedItems.map(x => x.id); + } + if (featureData.actions?.length > 0) { + const newActions = featureData.actions.map(action => { + const cls = actionsTypes[action.type]; + return new cls( + { ...action, _id: foundry.utils.randomID(), name: game.i18n.localize(action.name) }, + { parent: this } + ); + }); + changes.system.actions = [...this.actions, ...newActions]; + feature.actionIds = newActions.map(x => x._id); + } + } + } + } } diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index d6671501..7bbf79e3 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -1,4 +1,12 @@ export default class DhActiveEffect extends ActiveEffect { + get isSuppressed() { + if (['weapon', 'armor'].includes(this.parent.type)) { + return !this.parent.system.equipped; + } + + return super.isSuppressed; + } + async _preCreate(data, options, user) { const update = {}; if (!data.img) { @@ -11,4 +19,9 @@ export default class DhActiveEffect extends ActiveEffect { await super._preCreate(data, options, user); } + + static applyField(model, change, field) { + change.value = Roll.safeEval(Roll.replaceFormulaData(change.value, change.effect.parent)); + super.applyField(model, change, field); + } } diff --git a/styles/daggerheart.css b/styles/daggerheart.css index abdd8ac0..e5d27cb5 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -3415,6 +3415,41 @@ div.daggerheart.views.multiclass { .sheet.daggerheart.dh-style .tab.actions .actions-list .action-item .controls a { text-shadow: none; } +.sheet.daggerheart.dh-style .tab.effects .effects-list { + display: flex; + flex-direction: column; + list-style: none; + padding: 0; + margin: 0; + width: 100%; + gap: 5px; +} +.sheet.daggerheart.dh-style .tab.effects .effects-list .effect-item { + display: grid; + align-items: center; + grid-template-columns: 1fr 4fr 1fr; + cursor: pointer; +} +.sheet.daggerheart.dh-style .tab.effects .effects-list .effect-item h4 { + font-family: 'Montserrat', sans-serif; + font-weight: lighter; + color: #efe6d8; +} +.sheet.daggerheart.dh-style .tab.effects .effects-list .effect-item .image { + height: 40px; + width: 40px; + object-fit: cover; + border-radius: 6px; + border: none; +} +.sheet.daggerheart.dh-style .tab.effects .effects-list .effect-item .controls { + display: flex; + justify-content: center; + gap: 10px; +} +.sheet.daggerheart.dh-style .tab.effects .effects-list .effect-item .controls a { + text-shadow: none; +} .application.sheet.daggerheart.dh-style .item-sheet-header { display: flex; } @@ -3544,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/daggerheart.less b/styles/daggerheart.less index bb603548..0b0f3926 100755 --- a/styles/daggerheart.less +++ b/styles/daggerheart.less @@ -29,6 +29,7 @@ @import './less/global/tab-navigation.less'; @import './less/global/tab-form-footer.less'; @import './less/global/tab-actions.less'; +@import './less/global/tab-effects.less'; @import './less/global/item-header.less'; @import './less/global/feature-section.less'; 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/styles/less/global/tab-effects.less b/styles/less/global/tab-effects.less new file mode 100644 index 00000000..c926f5d1 --- /dev/null +++ b/styles/less/global/tab-effects.less @@ -0,0 +1,46 @@ +@import '../utils/colors.less'; +@import '../utils/fonts.less'; + +.sheet.daggerheart.dh-style { + .tab.effects { + .effects-list { + display: flex; + flex-direction: column; + list-style: none; + padding: 0; + margin: 0; + width: 100%; + gap: 5px; + + .effect-item { + display: grid; + align-items: center; + grid-template-columns: 1fr 4fr 1fr; + cursor: pointer; + + h4 { + font-family: @font-body; + font-weight: lighter; + color: @beige; + } + + .image { + height: 40px; + width: 40px; + object-fit: cover; + border-radius: 6px; + border: none; + } + + .controls { + display: flex; + justify-content: center; + gap: 10px; + a { + text-shadow: none; + } + } + } + } + } +} diff --git a/templates/sheets/character/sections/inventory.hbs b/templates/sheets/character/sections/inventory.hbs index f56138dc..6f4aeb49 100644 --- a/templates/sheets/character/sections/inventory.hbs +++ b/templates/sheets/character/sections/inventory.hbs @@ -13,7 +13,7 @@
  • -
    +
    {{item.name}}
    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/global/tabs/tab-actions.hbs b/templates/sheets/global/tabs/tab-actions.hbs index be986927..6cc1b652 100644 --- a/templates/sheets/global/tabs/tab-actions.hbs +++ b/templates/sheets/global/tabs/tab-actions.hbs @@ -4,7 +4,7 @@ data-group='{{tabs.actions.group}}' >
    - {{localize "Actions"}} + {{localize "DAGGERHEART.Sheets.Global.Actions"}}
    {{#each document.system.actions as |action index|}}
    diff --git a/templates/sheets/global/tabs/tab-effects.hbs b/templates/sheets/global/tabs/tab-effects.hbs new file mode 100644 index 00000000..e6191c80 --- /dev/null +++ b/templates/sheets/global/tabs/tab-effects.hbs @@ -0,0 +1,21 @@ +
    +
    + {{localize "DAGGERHEART.Sheets.Global.Effects"}} +
    + {{#each document.effects as |effect|}} +
    + + {{effect.name}} +
    + + +
    +
    + {{/each}} +
    +
    +
    \ No newline at end of file diff --git a/templates/sheets/items/armor/settings.hbs b/templates/sheets/items/armor/settings.hbs index cf0fea50..5f3d749b 100644 --- a/templates/sheets/items/armor/settings.hbs +++ b/templates/sheets/items/armor/settings.hbs @@ -6,11 +6,13 @@
    {{localize tabs.settings.label}} + {{localize "DAGGERHEART.Tiers.singular"}} + {{formField systemFields.tier value=source.system.tier}} {{localize "DAGGERHEART.Sheets.Armor.baseScore"}} {{formField systemFields.baseScore value=source.system.baseScore}} {{localize "DAGGERHEART.Sheets.Armor.feature"}} - {{formField systemFields.feature value=source.system.feature localize=true blank=""}} + {{localize "DAGGERHEART.Sheets.Armor.baseThresholds.base"}}
    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}} +
    +
    +
    + diff --git a/templates/sheets/items/weapon/settings.hbs b/templates/sheets/items/weapon/settings.hbs index 00d2e432..aaa5192b 100644 --- a/templates/sheets/items/weapon/settings.hbs +++ b/templates/sheets/items/weapon/settings.hbs @@ -5,6 +5,8 @@ >
    {{localize tabs.settings.label}} + {{localize "DAGGERHEART.Tiers.singular"}} + {{formField systemFields.tier value=source.system.tier}} {{localize "DAGGERHEART.Sheets.Weapon.SecondaryWeapon"}} {{formField systemFields.secondary value=source.system.secondary}} {{localize "DAGGERHEART.Sheets.Weapon.Trait"}} @@ -25,6 +27,6 @@
    {{localize "DAGGERHEART.Sheets.Weapon.Feature"}} {{localize "DAGGERHEART.Sheets.Weapon.Feature"}} - {{formField systemFields.feature value=source.system.feature localize=true}} +
    \ No newline at end of file