diff --git a/module/applications/sheets-configs/action-config.mjs b/module/applications/sheets-configs/action-config.mjs index 0beb8d79..cf02eb70 100644 --- a/module/applications/sheets-configs/action-config.mjs +++ b/module/applications/sheets-configs/action-config.mjs @@ -111,6 +111,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) { context.hasBaseDamage = !!this.action.parent.attack; context.getRealIndex = this.getRealIndex.bind(this); context.getEffectDetails = this.getEffectDetails.bind(this); + context.costOptions = await this.getCostOptions(); context.disableOption = this.disableOption.bind(this); context.isNPC = this.action.actor && this.action.actor.type !== 'character'; context.hasRoll = this.action.hasRoll; @@ -129,8 +130,46 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) { this.render(true); } - disableOption(index, options, choices) { - const filtered = foundry.utils.deepClone(options); + async getCostOptions() { + const worldItems = this.action.actor ? this.action.actor.items : game.items; + const compendiumItems = this.action.actor + ? [] + : Array.from(game.packs).flatMap(x => + x.index.map(x => ({ + id: x._id, + uuid: x.uuid, + name: x.name + })) + ); + + const resourceOptions = [...worldItems, ...compendiumItems].reduce((acc, x) => { + if (['feature', 'domainCard'].includes(x.type) && x.system.resource?.max) { + acc.push({ + id: x.id, + uuid: x.uuid, + label: x.name, + group: 'TYPES.Actor.character' + }); + } + + return acc; + }, []); + + const abilityCosts = Object.values(CONFIG.DH.GENERAL.abilityCosts); + const sortedOptions = [...abilityCosts, ...resourceOptions].sort((a, b) => { + const groupSort = game.i18n.localize(a.group).localeCompare(game.i18n.localize(b.group)); + return groupSort ? groupSort : game.i18n.localize(a.label).localeCompare(game.i18n.localize(b.label)); + }); + + return sortedOptions.reduce((acc, x) => { + acc[x.uuid ?? x.id] = x; + + return acc; + }, {}); + } + + disableOption(index, costOptions, choices) { + const filtered = foundry.utils.deepClone(costOptions); Object.keys(filtered).forEach(o => { if (choices.find((c, idx) => c.type === o && index !== idx)) filtered[o].disabled = true; }); @@ -146,17 +185,25 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) { return this.action.item.effects.get(id); } - _prepareSubmitData(event, formData) { + async _prepareSubmitData(_event, formData) { const submitData = foundry.utils.expandObject(formData.object); for (const keyPath of this.constructor.CLEAN_ARRAYS) { const data = foundry.utils.getProperty(submitData, keyPath); - if (data) foundry.utils.setProperty(submitData, keyPath, Object.values(data)); + const dataValues = data ? Object.values(data) : []; + if (keyPath === 'cost') { + for (var value of dataValues) { + const item = await foundry.utils.fromUuid(value.key ?? ''); + value.keyIsUUID = Boolean(item); + } + } + + if (data) foundry.utils.setProperty(submitData, keyPath, dataValues); } return submitData; } static async updateForm(event, _, formData) { - const submitData = this._prepareSubmitData(event, formData), + const submitData = await this._prepareSubmitData(event, formData), data = foundry.utils.mergeObject(this.action.toObject(), submitData), container = foundry.utils.getProperty(this.action.parent, this.action.systemPath); let newActions; diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index d251a67e..7a718ffb 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -345,37 +345,6 @@ export const refreshTypes = { } }; -export const featureTokenTypes = { - tide: { - id: 'tide', - label: 'Tide', - group: 'TYPES.Actor.character' - }, - chaos: { - id: 'chaos', - label: 'Chaos', - group: 'TYPES.Actor.character' - } -}; - -export const featureDiceTypes = { - prayer: { - id: 'prayer', - label: 'Prayer Dice', - group: 'TYPES.Actor.character' - }, - favor: { - id: 'favor', - label: 'Favor Points', - group: 'TYPES.Actor.character' - }, - slayer: { - id: 'slayer', - label: 'Slayer Dice', - group: 'TYPES.Actor.character' - } -}; - export const abilityCosts = { hp: { id: 'hp', @@ -401,9 +370,9 @@ export const abilityCosts = { id: 'fear', label: 'Fear', group: 'TYPES.Actor.adversary' - }, - ...featureTokenTypes, - ...featureDiceTypes + } + // ...featureTokenTypes, + // ...featureDiceTypes }; export const countdownTypes = { diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index e2eafbf2..2516d28f 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -1,6 +1,7 @@ -import { DHActionDiceData, DHActionRollData, DHDamageData, DHDamageField } from './actionDice.mjs'; +import { DHActionDiceData, DHActionRollData, DHDamageField } from './actionDice.mjs'; import DhpActor from '../../documents/actor.mjs'; import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs'; +import { getResources } from '../../helpers/utils.mjs'; const fields = foundry.data.fields; @@ -35,12 +36,12 @@ export default class DHBaseAction extends foundry.abstract.DataModel { }), cost: new fields.ArrayField( new fields.SchemaField({ - type: new fields.StringField({ - choices: CONFIG.DH.GENERAL.abilityCosts, + key: new fields.StringField({ nullable: false, required: true, initial: 'hope' }), + keyIsUUID: new fields.BooleanField(), value: new fields.NumberField({ nullable: true, initial: 1 }), scalable: new fields.BooleanField({ initial: false }), step: new fields.NumberField({ nullable: true, initial: null }) @@ -211,7 +212,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel { // Prepare Costs const costsConfig = this.prepareCost(); - if (isFastForward && !this.hasCost(costsConfig)) + if (isFastForward && !(await this.hasCost(costsConfig))) return ui.notifications.warn("You don't have the resources to use that action."); // Prepare Uses @@ -285,7 +286,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel { prepareCost() { const costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : []; - return costs; + return this.calcCosts(costs); } prepareUse() { @@ -334,11 +335,27 @@ export default class DHBaseAction extends foundry.abstract.DataModel { } async consume(config) { + const usefulResources = foundry.utils.deepClone(this.actor.system.resources); + for (var cost of config.costs) { + if (cost.keyIsUUID) { + const item = await foundry.utils.fromUuid(cost.key); + usefulResources[cost.key] = { + value: item.system.resource.value, + target: item, + keyIsUUID: true + }; + } + } const resources = config.costs .filter(c => c.enabled !== false) .map(c => { - const resource = this.actor.system.resources[c.type]; - return { type: c.type, value: (c.total ?? c.value) * (resource.hasOwnProperty('maxTotal') ? 1 : -1) }; + const resource = usefulResources[c.key]; + return { + key: c.key, + value: (c.total ?? c.value) * (resource.hasOwnProperty('maxTotal') ? 1 : -1), + target: resource.target, + keyIsUUID: resource.keyIsUUID + }; }); await this.actor.modifyResource(resources); @@ -379,9 +396,27 @@ export default class DHBaseAction extends foundry.abstract.DataModel { }); } - hasCost(costs) { + async getResources(costs) { + const actorResources = this.actor.system.resources; + const itemResources = {}; + for (var itemResource of costs) { + if (itemResource.keyIsUUID) { + const item = await foundry.utils.fromUuid(itemResource.key); + itemResources[itemResource.key] = { + value: item?.system?.resource?.value ?? 0 + }; + } + } + + return { + ...actorResources, + ...itemResources + }; + } + + async hasCost(costs) { const realCosts = this.getRealCosts(costs), - hasFearCost = realCosts.findIndex(c => c.type === 'fear'); + hasFearCost = realCosts.findIndex(c => c.key === 'fear'); if (hasFearCost > -1) { const fearCost = realCosts.splice(hasFearCost, 1)[0]; if ( @@ -392,12 +427,12 @@ export default class DHBaseAction extends foundry.abstract.DataModel { } /* maxTotal is a sign that the resource is inverted, IE it counts upwards instead of down */ - const resources = this.actor.system.resources; + const resources = await this.getResources(realCosts); return realCosts.reduce( (a, c) => - a && resources[c.type].hasOwnProperty('maxTotal') - ? resources[c.type].value + (c.total ?? c.value) <= resources[c.type].maxTotal - : resources[c.type]?.value >= (c.total ?? c.value), + a && resources[c.key].hasOwnProperty('maxTotal') + ? resources[c.key].value + (c.total ?? c.value) <= resources[c.key].maxTotal + : resources[c.key]?.value >= (c.total ?? c.value), true ); } diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 93926d9a..4da87de9 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -42,9 +42,7 @@ export default class DhCharacter extends BaseDataActor { bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true }) }), stress: resourceField(6), - hope: resourceField(6), - tokens: new fields.ObjectField(), - dice: new fields.ObjectField() + hope: resourceField(6) }), traits: new fields.SchemaField({ agility: attributeField(), diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index aba0e0fa..ad1ada90 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -476,7 +476,7 @@ export default class DhpActor extends Actor { if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, baseDamage, type) === false) return null; if (this.type === 'companion') { - await this.modifyResource([{ value: 1, type: 'stress' }]); + await this.modifyResource([{ value: 1, key: 'stress' }]); return; } @@ -500,8 +500,8 @@ export default class DhpActor extends Actor { const { modifiedDamage, armorSpent, stressSpent } = armorStackResult; updates.find(u => u.type === 'hitPoints').value = modifiedDamage; updates.push( - ...(armorSpent ? [{ value: armorSpent, type: 'armorStack' }] : []), - ...(stressSpent ? [{ value: stressSpent, type: 'stress' }] : []) + ...(armorSpent ? [{ value: armorSpent, key: 'armorStack' }] : []), + ...(stressSpent ? [{ value: stressSpent, key: 'stress' }] : []) ); } } @@ -520,51 +520,77 @@ export default class DhpActor extends Actor { if (!resources.length) return; if (resources.find(r => r.type === 'stress')) this.convertStressDamageToHP(resources); - let updates = { actor: { target: this, resources: {} }, armor: { target: this.system.armor, resources: {} } }; + let updates = { + actor: { target: this, resources: {} }, + armor: { target: this.system.armor, resources: {} }, + items: {} + }; resources.forEach(r => { - switch (r.type) { - case 'fear': - ui.resources.updateFear( - game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear) + r.value - ); - break; - case 'armorStack': - updates.armor.resources['system.marks.value'] = Math.max( - Math.min(this.system.armor.system.marks.value + r.value, this.system.armorScore), - 0 - ); - break; - default: - updates.actor.resources[`system.resources.${r.type}.value`] = Math.max( - Math.min( - this.system.resources[r.type].value + r.value, - this.system.resources[r.type].maxTotal ?? this.system.resources[r.type].max - ), - 0 - ); - break; + if (r.keyIsUUID) { + updates.items[r.key] = { + uuid: r.key, + target: r.target, + resources: { + 'system.resource.value': r.target.system.resource.value + r.value + } + }; + } else { + switch (r.key) { + case 'fear': + ui.resources.updateFear( + game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear) + r.value + ); + break; + case 'armorStack': + updates.armor.resources['system.marks.value'] = Math.max( + Math.min(this.system.armor.system.marks.value + r.value, this.system.armorScore), + 0 + ); + break; + default: + updates.actor.resources[`system.resources.${r.type}.value`] = Math.max( + Math.min( + this.system.resources[r.type].value + r.value, + this.system.resources[r.type].maxTotal ?? this.system.resources[r.type].max + ), + 0 + ); + break; + } } }); - Object.values(updates).forEach(async u => { - if (Object.keys(u.resources).length > 0) { - await emitAsGM( - GMUpdateEvent.UpdateDocument, - u.target.update.bind(u.target), - u.resources, - u.target.uuid - ); - /* if (game.user.isGM) { - await u.target.update(u.resources); - } else { - await game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.GMUpdate, - data: { - action: GMUpdateEvent.UpdateDocument, - uuid: u.target.uuid, - update: u.resources - } - }); - } */ + Object.keys(updates).forEach(async key => { + const u = updates[key]; + if (key === 'items') { + Object.values(u).forEach(async item => { + await emitAsGM( + GMUpdateEvent.UpdateDocument, + item.target.update.bind(item.target), + item.resources, + item.target.uuid + ); + }); + } else { + if (Object.keys(u.resources).length > 0) { + await emitAsGM( + GMUpdateEvent.UpdateDocument, + u.target.update.bind(u.target), + u.resources, + u.target.uuid + ); + /* if (game.user.isGM) { + await u.target.update(u.resources); + } else { + await game.socket.emit(`system.${CONFIG.DH.id}`, { + action: socketEvent.GMUpdate, + data: { + action: GMUpdateEvent.UpdateDocument, + uuid: u.target.uuid, + update: u.resources + } + }); + } */ + } } }); } diff --git a/templates/actionTypes/cost.hbs b/templates/actionTypes/cost.hbs index 515f4968..116fc631 100644 --- a/templates/actionTypes/cost.hbs +++ b/templates/actionTypes/cost.hbs @@ -6,7 +6,7 @@ {{#each source as |cost index|}}
{{formField ../fields.scalable label="Scalable" value=cost.scalable name=(concat "cost." index ".scalable") classes="checkbox"}} - {{formField ../fields.type choices=(@root.disableOption index ../fields.type.choices ../source) label="Resource" value=cost.type name=(concat "cost." index ".type") localize=true}} + {{formField ../fields.key choices=(@root.disableOption index @root.costOptions ../source) label="Resource" value=cost.key name=(concat "cost." index ".key") localize=true}} {{formField ../fields.value label="Amount" value=cost.value name=(concat "cost." index ".value")}} {{formField ../fields.step label="Step" value=cost.step name=(concat "cost." index ".step") disabled=(not cost.scalable)}} diff --git a/templates/sheets-settings/action-settings/configuration.hbs b/templates/sheets-settings/action-settings/configuration.hbs index 74d5fcc1..51b2a72b 100644 --- a/templates/sheets-settings/action-settings/configuration.hbs +++ b/templates/sheets-settings/action-settings/configuration.hbs @@ -4,6 +4,6 @@ data-tab="config" > {{> 'systems/daggerheart/templates/actionTypes/uses.hbs' fields=fields.uses.fields source=source.uses}} - {{> 'systems/daggerheart/templates/actionTypes/cost.hbs' fields=fields.cost.element.fields source=source.cost}} + {{> 'systems/daggerheart/templates/actionTypes/cost.hbs' fields=fields.cost.element.fields source=source.cost costOptions=costOptions}} {{> 'systems/daggerheart/templates/actionTypes/range-target.hbs' fields=(object range=fields.range target=fields.target.fields) source=(object target=source.target range=source.range)}} \ No newline at end of file