diff --git a/lang/en.json b/lang/en.json index 4b4d6fa3..f130742f 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2376,7 +2376,6 @@ "maxDomains": { "label": "Max Class Domains", "hint": "Max domains you can set on a class" } }, "currency": { - "enabled": "Enable Overrides", "title": "Currency Overrides", "currencyName": "Currency Name", "coinName": "Coin Name", @@ -2580,6 +2579,7 @@ "evasionMin": "Evasion (Min)", "evasionMax": "Evasion (Max)", "subtype": "Subtype", + "missing": "Missing", "folders": { "characters": "Characters", "adversaries": "Adversaries", @@ -2633,7 +2633,7 @@ "cardTooHighLevel": "The card is too high level!", "duplicateCard": "You cannot select the same card more than once.", "duplicateCharacter": "This actor is already registered in the party members list.", - "onlyCharactersInPartySheet": "You can drag only characters to a party sheet.", + "onlyCharactersInPartySheet": "You can only drag characters, companions and adverasries to the party sheet.", "notPrimary": "The weapon is not a primary weapon!", "notSecondary": "The weapon is not a secondary weapon!", "itemTooHighTier": "The item must be from Tier1", @@ -2670,7 +2670,8 @@ "subclassAlreadyLinked": "{name} is already a subclass in the class {class}. Remove it from there if you want it to be a subclass to this class.", "gmRequired": "This action requires an online GM", "gmOnly": "This can only be accessed by the GM", - "noActorOwnership": "You do not have permissions for this character" + "noActorOwnership": "You do not have permissions for this character", + "documentIsMissing": "The {documentType} is missing from the world." }, "Sidebar": { "daggerheartMenu": { diff --git a/module/applications/dialogs/downtime.mjs b/module/applications/dialogs/downtime.mjs index b0f51c4e..2ed2302c 100644 --- a/module/applications/dialogs/downtime.mjs +++ b/module/applications/dialogs/downtime.mjs @@ -178,10 +178,17 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV } static async takeDowntime() { - const moves = Object.values(this.moveData).flatMap(category => { - return Object.values(category.moves) - .filter(x => x.selected) - .flatMap(move => [...Array(move.selected).keys()].map(_ => move)); + const moves = Object.keys(this.moveData).flatMap(categoryKey => { + const category = this.moveData[categoryKey]; + return Object.keys(category.moves) + .filter(x => category.moves[x].selected) + .flatMap(key => { + const move = category.moves[key]; + return [...Array(move.selected).keys()].map(_ => ({ + ...move, + movePath: `${categoryKey}.moves.${key}` + })); + }); }); const cls = getDocumentClass('ChatMessage'); diff --git a/module/applications/sheets-configs/activeEffectConfig.mjs b/module/applications/sheets-configs/activeEffectConfig.mjs index 6a466c55..468aba5c 100644 --- a/module/applications/sheets-configs/activeEffectConfig.mjs +++ b/module/applications/sheets-configs/activeEffectConfig.mjs @@ -112,7 +112,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac CONFIG.DH.SETTINGS.gameSettings.appearance ).showGenericStatusEffects; if (!useGeneric) { - partContext.statuses = Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({ + partContext.statuses = Object.values(CONFIG.DH.GENERAL.conditions()).map(status => ({ value: status.id, label: game.i18n.localize(status.name) })); diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 6b89bf48..85267944 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -210,26 +210,33 @@ export default class CharacterSheet extends DHBaseActorSheet { context.resources.stress.emptyPips = context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0; - context.inventory = { - currency: { - title: game.i18n.localize('DAGGERHEART.CONFIG.Gold.title'), - coins: game.i18n.localize('DAGGERHEART.CONFIG.Gold.coins'), - handfuls: game.i18n.localize('DAGGERHEART.CONFIG.Gold.handfuls'), - bags: game.i18n.localize('DAGGERHEART.CONFIG.Gold.bags'), - chests: game.i18n.localize('DAGGERHEART.CONFIG.Gold.chests') - } - }; + context.inventory = { currencies: {} }; + const { title, ...currencies } = game.settings.get( + CONFIG.DH.id, + CONFIG.DH.SETTINGS.gameSettings.Homebrew + ).currency; + for (let key in currencies) { + context.inventory.currencies[key] = { + ...currencies[key], + field: context.systemFields.gold.fields[key], + value: context.source.system.gold[key] + }; + } + // context.inventory = { + // currency: { + // title: game.i18n.localize('DAGGERHEART.CONFIG.Gold.title'), + // coins: game.i18n.localize('DAGGERHEART.CONFIG.Gold.coins'), + // handfuls: game.i18n.localize('DAGGERHEART.CONFIG.Gold.handfuls'), + // bags: game.i18n.localize('DAGGERHEART.CONFIG.Gold.bags'), + // chests: game.i18n.localize('DAGGERHEART.CONFIG.Gold.chests') + // } + // }; context.beastformActive = this.document.effects.find(x => x.type === 'beastform'); - const homebrewCurrency = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).currency; - if (homebrewCurrency.enabled) { - context.inventory.currency = homebrewCurrency; - } - - if (context.inventory.length === 0) { - context.inventory = Array(1).fill(Array(5).fill([])); - } + // if (context.inventory.length === 0) { + // context.inventory = Array(1).fill(Array(5).fill([])); + // } return context; } diff --git a/module/applications/sheets/actors/party.mjs b/module/applications/sheets/actors/party.mjs index a622dcec..9b48a797 100644 --- a/module/applications/sheets/actors/party.mjs +++ b/module/applications/sheets/actors/party.mjs @@ -7,6 +7,7 @@ import { socketEvent } from '../../../systemRegistration/socket.mjs'; import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs'; import DhpActor from '../../../documents/actor.mjs'; import DHItem from '../../../documents/item.mjs'; +import DhParty from '../../../data/actor/party.mjs'; export default class Party extends DHBaseActorSheet { constructor(options) { @@ -19,7 +20,8 @@ export default class Party extends DHBaseActorSheet { static DEFAULT_OPTIONS = { classes: ['party'], position: { - width: 550 + width: 550, + height: 900, }, window: { resizable: true @@ -79,6 +81,9 @@ export default class Party extends DHBaseActorSheet { } }; + static ALLOWED_ACTOR_TYPES = ['character', 'companion', 'adversary']; + static DICE_ROLL_ACTOR_TYPES = ['character']; + async _onRender(context, options) { await super._onRender(context, options); this._createFilterMenus(); @@ -92,23 +97,17 @@ export default class Party extends DHBaseActorSheet { async _prepareContext(_options) { const context = await super._prepareContext(_options); - context.inventory = { - currency: { - title: game.i18n.localize('DAGGERHEART.CONFIG.Gold.title'), - coins: game.i18n.localize('DAGGERHEART.CONFIG.Gold.coins'), - handfuls: game.i18n.localize('DAGGERHEART.CONFIG.Gold.handfuls'), - bags: game.i18n.localize('DAGGERHEART.CONFIG.Gold.bags'), - chests: game.i18n.localize('DAGGERHEART.CONFIG.Gold.chests') - } - }; - - const homebrewCurrency = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).currency; - if (homebrewCurrency.enabled) { - context.inventory.currency = homebrewCurrency; - } - - if (context.inventory.length === 0) { - context.inventory = Array(1).fill(Array(5).fill([])); + context.inventory = { currencies: {} }; + const { title, ...currencies } = game.settings.get( + CONFIG.DH.id, + CONFIG.DH.SETTINGS.gameSettings.Homebrew + ).currency; + for (let key in currencies) { + context.inventory.currencies[key] = { + ...currencies[key], + field: context.systemFields.gold.fields[key], + value: context.source.system.gold[key] + }; } return context; @@ -277,13 +276,17 @@ export default class Party extends DHBaseActorSheet { } static async #tagTeamRoll() { - new game.system.api.applications.dialogs.TagTeamDialog(this.document.system.partyMembers).render({ + new game.system.api.applications.dialogs.TagTeamDialog( + this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type)) + ).render({ force: true }); } - static async #groupRoll(params) { - new GroupRollDialog(this.document.system.partyMembers).render({ force: true }); + static async #groupRoll(_params) { + new GroupRollDialog( + this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type)) + ).render({ force: true }); } /** @@ -453,17 +456,17 @@ export default class Party extends DHBaseActorSheet { event.stopPropagation(); const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); - const item = await foundry.utils.fromUuid(data.uuid); + const document = await foundry.utils.fromUuid(data.uuid); - if (item instanceof DhpActor) { + if (document instanceof DhpActor && Party.ALLOWED_ACTOR_TYPES.includes(document.type)) { const currentMembers = this.document.system.partyMembers.map(x => x.uuid); if (currentMembers.includes(data.uuid)) { return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateCharacter')); } - await this.document.update({ 'system.partyMembers': [...currentMembers, item.uuid] }); - } else if (item instanceof DHItem) { - this.document.createEmbeddedDocuments('Item', [item.toObject()]); + await this.document.update({ 'system.partyMembers': [...currentMembers, document.uuid] }); + } else if (document instanceof DHItem) { + this.document.createEmbeddedDocuments('Item', [document.toObject()]); } else { ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.onlyCharactersInPartySheet')); } diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 6b05fe74..e2e3b6d4 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -132,12 +132,21 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo } async actionUseButton(event, message) { - const { moveIndex, actionIndex } = event.currentTarget.dataset; + const { moveIndex, actionIndex, movePath } = event.currentTarget.dataset; const parent = await foundry.utils.fromUuid(message.system.actor); const actionType = message.system.moves[moveIndex].actions[actionIndex]; const cls = game.system.api.models.actions.actionsTypes[actionType.type]; const action = new cls( - { ...actionType, _id: foundry.utils.randomID(), name: game.i18n.localize(actionType.name) }, + { + ...actionType, + _id: foundry.utils.randomID(), + name: game.i18n.localize(actionType.name), + originItem: { + type: CONFIG.DH.ITEM.originItemType.restMove, + itemPath: movePath, + actionIndex: actionIndex + } + }, { parent: parent.system } ); @@ -192,9 +201,18 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo async groupRollButton(event, message) { const path = event.currentTarget.dataset.path; + const isLeader = path === 'leader'; const { actor: actorData, trait } = foundry.utils.getProperty(message.system, path); const actor = game.actors.get(actorData._id); + if (!actor) { + return ui.notifications.error( + game.i18n.format('DAGGERHEART.UI.Notifications.documentIsMissing', { + documentType: game.i18n.localize('TYPES.Actor.character') + }) + ); + } + if (!actor.testUserPermission(game.user, 'OWNER')) { return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership')); } @@ -214,7 +232,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo hasRoll: true, skips: { createMessage: true, - resources: true + resources: !isLeader } }; const result = await actor.diceRoll({ @@ -225,6 +243,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo }) }); + if (!result) return; + await game.system.api.fields.ActionFields.CostField.execute.call({ actor }, result); + const newMessageData = foundry.utils.deepClone(message.system); foundry.utils.setProperty(newMessageData, `${path}.result`, result.roll); const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) }; diff --git a/module/applications/ui/itemBrowser.mjs b/module/applications/ui/itemBrowser.mjs index 33995aa9..07d5cd74 100644 --- a/module/applications/ui/itemBrowser.mjs +++ b/module/applications/ui/itemBrowser.mjs @@ -294,7 +294,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { const property = foundry.utils.getProperty(item, field.key); if (Array.isArray(property)) property.join(', '); if (typeof field.format !== 'function') return property ?? '-'; - return field.format(property); + return game.i18n.localize(field.format(property)); } formatChoices(data) { @@ -430,7 +430,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { if (matchesMenu) this.#filteredItems.browser.input.add(item.id); const { search } = this.#filteredItems.browser; - li.hidden = !(search.has(item.id) && matchesMenu); + li.hidden = !((this.#search.browser.query.length === 0 || search.has(item.id)) && matchesMenu); } } @@ -469,6 +469,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) { static resetFilters() { this.render({ force: true }); + this.loadItems(); } static getFolderConfig(folder, property = 'columns') { diff --git a/module/config/itemBrowserConfig.mjs b/module/config/itemBrowserConfig.mjs index 2c3e1dfb..3b10409c 100644 --- a/module/config/itemBrowserConfig.mjs +++ b/module/config/itemBrowserConfig.mjs @@ -363,7 +363,7 @@ export const typeConfig = { { key: 'system.linkedClass', label: 'Class', - format: linkedClass => linkedClass.name + format: linkedClass => linkedClass?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing' }, { key: 'system.spellcastingTrait', @@ -375,10 +375,12 @@ export const typeConfig = { key: 'system.linkedClass.uuid', label: 'Class', choices: items => { - const list = items.map(item => ({ - value: item.system.linkedClass.uuid, - label: item.system.linkedClass.name - })); + const list = items + .filter(item => item.system.linkedClass) + .map(item => ({ + value: item.system.linkedClass.uuid, + label: item.system.linkedClass.name + })); return list.reduce((a, c) => { if (!a.find(i => i.value === c.value)) a.push(c); return a; diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs index d815181b..544d6b2d 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -1547,3 +1547,8 @@ export const beastformTypes = { label: 'DAGGERHEART.CONFIG.BeastformType.hybrid' } }; + +export const originItemType = { + itemCollection: 'itemCollection', + restMove: 'restMove' +}; diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 8cadd4f8..ba401ae9 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -1,6 +1,7 @@ import DhpActor from '../../documents/actor.mjs'; import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs'; import { ActionMixin } from '../fields/actionField.mjs'; +import { originItemField } from '../chat-message/actorRoll.mjs'; const fields = foundry.data.fields; @@ -25,6 +26,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel description: new fields.HTMLField(), img: new fields.FilePathField({ initial: undefined, categories: ['IMAGE'], base64: false }), chatDisplay: new fields.BooleanField({ initial: true, label: 'DAGGERHEART.ACTIONS.Config.displayInChat' }), + originItem: originItemField(), actionType: new fields.StringField({ choices: CONFIG.DH.ITEM.actionTypes, initial: 'action', @@ -215,6 +217,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel title: `${this.item instanceof CONFIG.Actor.documentClass ? '' : `${this.item.name}: `}${game.i18n.localize(this.name)}`, source: { item: this.item._id, + originItem: this.originItem, action: this._id, actor: this.actor.uuid }, diff --git a/module/data/activeEffect/beastformEffect.mjs b/module/data/activeEffect/beastformEffect.mjs index b5cbdb28..769b8153 100644 --- a/module/data/activeEffect/beastformEffect.mjs +++ b/module/data/activeEffect/beastformEffect.mjs @@ -6,10 +6,12 @@ export default class BeastformEffect extends BaseEffect { const fields = foundry.data.fields; return { characterTokenData: new fields.SchemaField({ + usesDynamicToken: new fields.BooleanField({ initial: false }), tokenImg: new fields.FilePathField({ categories: ['IMAGE'], base64: false, - nullable: true + nullable: true, + wildcard: true }), tokenRingImg: new fields.FilePathField({ initial: 'icons/svg/mystery-man.svg', @@ -38,20 +40,38 @@ export default class BeastformEffect extends BaseEffect { async _preDelete() { if (this.parent.parent.type === 'character') { - const update = { + const baseUpdate = { height: this.characterTokenData.tokenSize.height, - width: this.characterTokenData.tokenSize.width, + width: this.characterTokenData.tokenSize.width + }; + const update = { + ...baseUpdate, texture: { src: this.characterTokenData.tokenImg }, ring: { + enabled: this.characterTokenData.usesDynamicToken, subject: { texture: this.characterTokenData.tokenRingImg } } }; - await updateActorTokens(this.parent.parent, update); + const updateToken = token => ({ + ...baseUpdate, + 'texture': { + enabled: this.characterTokenData.usesDynamicToken, + src: token.flags.daggerheart.beastformTokenImg + }, + 'ring': { + subject: { + texture: token.flags.daggerheart.beastformSubjectTexture + } + }, + 'flags.daggerheart': { '-=beastformTokenImg': null, '-=beastformSubjectTexture': null } + }); + + await updateActorTokens(this.parent.parent, update, updateToken); await this.parent.parent.deleteEmbeddedDocuments('Item', this.featureIds); await this.parent.parent.deleteEmbeddedDocuments('ActiveEffect', this.effectIds); diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 1cf082f7..645a50da 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -675,6 +675,8 @@ export default class DhCharacter extends BaseDataActor { } _getTags() { - return [this.class.value?.name, this.class.subclass?.name, this.community?.name, this.ancestry?.name].filter((t) => !!t); + return [this.class.value?.name, this.class.subclass?.name, this.community?.name, this.ancestry?.name].filter( + t => !!t + ); } } diff --git a/module/data/actor/party.mjs b/module/data/actor/party.mjs index 77b17045..06731246 100644 --- a/module/data/actor/party.mjs +++ b/module/data/actor/party.mjs @@ -41,7 +41,7 @@ export default class DhParty extends BaseDataActor { // Clear this party from all members that aren't deleted for (const member of this.partyMembers) { - member.parties?.delete(this.parent); + member?.parties?.delete(this.parent); } } } diff --git a/module/data/chat-message/actorRoll.mjs b/module/data/chat-message/actorRoll.mjs index a2cb03f9..61262529 100644 --- a/module/data/chat-message/actorRoll.mjs +++ b/module/data/chat-message/actorRoll.mjs @@ -17,6 +17,16 @@ const targetsField = () => }) ); +export const originItemField = () => + new fields.SchemaField({ + type: new fields.StringField({ + choices: CONFIG.DH.ITEM.originItemType, + initial: CONFIG.DH.ITEM.originItemType.itemCollection + }), + itemPath: new fields.StringField(), + actionIndex: new fields.StringField() + }); + export default class DHActorRoll extends foundry.abstract.TypeDataModel { static defineSchema() { return { @@ -35,6 +45,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { source: new fields.SchemaField({ actor: new fields.StringField(), item: new fields.StringField(), + originItem: originItemField(), action: new fields.StringField() }), damage: new fields.ObjectField(), @@ -51,14 +62,23 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { get actionItem() { const actionActor = this.actionActor; if (!actionActor || !this.source.item) return null; - return actionActor.items.get(this.source.item); + + switch (this.source.originItem.type) { + case CONFIG.DH.ITEM.originItemType.restMove: + const restMoves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves; + return Array.from(foundry.utils.getProperty(restMoves, `${this.source.originItem.itemPath}`).actions)[ + this.source.originItem.actionIndex + ]; + default: + const item = actionActor.items.get(this.source.item); + return item ? item.system.actionsList?.find(a => a.id === this.source.action) : null; + } } get action() { - const actionActor = this.actionActor, - actionItem = this.actionItem; + const { actionActor, actionItem: itemAction } = this; if (!this.source.action) return null; - if (actionItem) return actionItem.system.actionsList?.find(a => a.id === this.source.action); + if (itemAction) return itemAction; else if (actionActor?.system.attack?._id === this.source.action) return actionActor.system.attack; return null; } diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index c5fd19b9..bb81c702 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -98,7 +98,9 @@ export default class DamageField extends fields.SchemaField { }); } - const token = game.scenes.find(x => x.active).tokens.find(x => x.id === target.id); + const token = target.id + ? game.scenes.find(x => x.active).tokens.find(x => x.id === target.id) + : actor.prototypeToken; if (config.hasHealing) damagePromises.push( actor.takeHealing(config.damage).then(updates => targetDamage.push({ token, updates })) diff --git a/module/data/fields/action/targetField.mjs b/module/data/fields/action/targetField.mjs index 439f2be1..41383fea 100644 --- a/module/data/fields/action/targetField.mjs +++ b/module/data/fields/action/targetField.mjs @@ -24,7 +24,7 @@ export default class TargetField extends fields.SchemaField { if (!this.target?.type) return (config.targets = []); config.hasTarget = true; let targets; - // If the Action is configured as self-targeted, set targets as the owner. + // If the Action is configured as self-targeted, set targets as the owner. Probably better way than to fallback to getDependentTokens if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id) targets = [this.actor.token ?? this.actor.prototypeToken]; else { @@ -72,17 +72,17 @@ export default class TargetField extends fields.SchemaField { /** * Format actor to useful datas for Action roll workflow. - * @param {*} actor Actor object to format. + * @param {*} token Token object to format. * @returns {*} Formatted Actor. */ - static formatTarget(actor) { + static formatTarget(token) { return { - id: actor.id, - actorId: actor.actor.uuid, - name: actor.actor.name, - img: actor.actor.img, - difficulty: actor.actor.system.difficulty, - evasion: actor.actor.system.evasion, + id: token.id, + actorId: token.actor.uuid, + name: token.actor.name, + img: token.actor.img, + difficulty: token.actor.system.difficulty, + evasion: token.actor.system.evasion, saved: { value: null, success: null diff --git a/module/data/item/beastform.mjs b/module/data/item/beastform.mjs index 8f5dffe6..51ca298d 100644 --- a/module/data/item/beastform.mjs +++ b/module/data/item/beastform.mjs @@ -160,7 +160,6 @@ export default class DHBeastform extends BaseDataItem { this.parent.effects.filter(x => x.type !== 'beastform').map(x => x.toObject()) ); - const tokenImages = await this.parent.parent.getTokenImages(); const beastformEffect = this.parent.effects.find(x => x.type === 'beastform'); await beastformEffect.updateSource({ changes: [ @@ -175,7 +174,8 @@ export default class DHBeastform extends BaseDataItem { ], system: { characterTokenData: { - tokenImg: tokenImages[0], + usesDynamicToken: this.parent.parent.prototypeToken.ring.enabled, + tokenImg: this.parent.parent.prototypeToken.texture.src, tokenRingImg: this.parent.parent.prototypeToken.ring.subject.texture, tokenSize: { height: this.parent.parent.prototypeToken.height, @@ -190,7 +190,7 @@ export default class DHBeastform extends BaseDataItem { await this.parent.parent.createEmbeddedDocuments('ActiveEffect', [beastformEffect.toObject()]); - await updateActorTokens(this.parent.parent, { + const prototypeTokenUpdate = { height: this.tokenSize.height, width: this.tokenSize.width, texture: { @@ -201,22 +201,20 @@ export default class DHBeastform extends BaseDataItem { texture: this.tokenRingImg } } + }; + + const tokenUpdate = token => ({ + ...prototypeTokenUpdate, + flags: { + daggerheart: { + beastformTokenImg: token.texture.src, + beastformSubjectTexture: token.ring.subject.texture + } + } }); + await updateActorTokens(this.parent.parent, prototypeTokenUpdate, tokenUpdate); + return false; } - - _onCreate(_data, _options, userId) { - if (userId !== game.user.id) return; - - if (!this.parent.effects.find(x => x.type === 'beastform')) { - this.parent.createEmbeddedDocuments('ActiveEffect', [ - { - type: 'beastform', - name: game.i18n.localize('DAGGERHEART.ITEMS.Beastform.beastformEffect'), - img: 'icons/creatures/abilities/paw-print-pair-purple.webp' - } - ]); - } - } } diff --git a/module/data/settings/Homebrew.mjs b/module/data/settings/Homebrew.mjs index ca44a3ed..ead3c962 100644 --- a/module/data/settings/Homebrew.mjs +++ b/module/data/settings/Homebrew.mjs @@ -1,6 +1,16 @@ import { defaultRestOptions } from '../../config/generalConfig.mjs'; import { ActionsField } from '../fields/actionField.mjs'; +const currencyField = (initial, label) => + new foundry.data.fields.SchemaField({ + enabled: new foundry.data.fields.BooleanField({ required: true, initial: true }), + label: new foundry.data.fields.StringField({ + required: true, + initial, + label + }) + }); + export default class DhHomebrew extends foundry.abstract.DataModel { static defineSchema() { const fields = foundry.data.fields; @@ -30,36 +40,15 @@ export default class DhHomebrew extends foundry.abstract.DataModel { initial: () => [2, 1, 1, 0, 0, -1] }), currency: new fields.SchemaField({ - enabled: new fields.BooleanField({ - required: true, - initial: false, - label: 'DAGGERHEART.SETTINGS.Homebrew.currency.enabled' - }), title: new fields.StringField({ required: true, initial: 'Gold', label: 'DAGGERHEART.SETTINGS.Homebrew.currency.currencyName' }), - coins: new fields.StringField({ - required: true, - initial: 'Coins', - label: 'DAGGERHEART.SETTINGS.Homebrew.currency.coinName' - }), - handfuls: new fields.StringField({ - required: true, - initial: 'Handfuls', - label: 'DAGGERHEART.SETTINGS.Homebrew.currency.handfulName' - }), - bags: new fields.StringField({ - required: true, - initial: 'Bags', - label: 'DAGGERHEART.SETTINGS.Homebrew.currency.bagName' - }), - chests: new fields.StringField({ - required: true, - initial: 'Chests', - label: 'DAGGERHEART.SETTINGS.Homebrew.currency.chestName' - }) + coins: currencyField('Coins', 'DAGGERHEART.SETTINGS.Homebrew.currency.coinName'), + handfuls: currencyField('Handfuls', 'DAGGERHEART.SETTINGS.Homebrew.currency.handfulName'), + bags: currencyField('Bags', 'DAGGERHEART.SETTINGS.Homebrew.currency.bagName'), + chests: currencyField('Chests', 'DAGGERHEART.SETTINGS.Homebrew.currency.chestName') }), restMoves: new fields.SchemaField({ longRest: new fields.SchemaField({ @@ -146,4 +135,26 @@ export default class DhHomebrew extends foundry.abstract.DataModel { }) }; } + + /** @inheritDoc */ + _initializeSource(source, options = {}) { + source = super._initializeSource(source, options); + source.currency.coins = { + enabled: source.currency.coins.enabled ?? true, + label: source.currency.coins.label || source.currency.coins + }; + source.currency.handfuls = { + enabled: source.currency.handfuls.enabled ?? true, + label: source.currency.handfuls.label || source.currency.handfuls + }; + source.currency.bags = { + enabled: source.currency.bags.enabled ?? true, + label: source.currency.bags.label || source.currency.bags + }; + source.currency.chests = { + enabled: source.currency.chests.enabled ?? true, + label: source.currency.chests.label || source.currency.chests + }; + return source; + } } diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index ec4c5a49..7e313891 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -145,9 +145,11 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { }); html.querySelectorAll('.token-target-container').forEach(element => { - element.addEventListener('pointerover', this.hoverTarget); - element.addEventListener('pointerout', this.unhoverTarget); - element.addEventListener('click', this.clickTarget); + if (element.dataset.token) { + element.addEventListener('pointerover', this.hoverTarget); + element.addEventListener('pointerout', this.unhoverTarget); + element.addEventListener('click', this.clickTarget); + } }); } diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index d5a402f2..f1bec45c 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -251,7 +251,13 @@ export const adjustRange = (rangeVal, decrease) => { return range[rangeKeys[newIndex]]; }; -export const updateActorTokens = async (actor, update) => { +/** + * + * @param {DhActor} actor - The actor for which all tokens will run a data update. + * @param {string} update - The data update to be applied to all tokens. + * @param {func} updateToken - Optional, specific data update for the non-prototype tokens as a function using the token data. Useful to handle wildcard images where each token has a different image but the prototype has a wildcard path. + */ +export const updateActorTokens = async (actor, update, updateToken) => { await actor.prototypeToken.update({ ...update }); /* Update the tokens in all scenes belonging to Actor */ @@ -259,7 +265,7 @@ export const updateActorTokens = async (actor, update) => { const tokenActor = token.baseActor ?? token.actor; if (token.id && tokenActor?.id === actor.id) { await token.update({ - ...update, + ...(updateToken ? updateToken(token) : update), _id: token.id }); } diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs index e3777a94..00b07dc1 100644 --- a/module/systemRegistration/migrations.mjs +++ b/module/systemRegistration/migrations.mjs @@ -4,6 +4,7 @@ export async function runMigrations() { let lastMigrationVersion = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion); if (!lastMigrationVersion) lastMigrationVersion = game.system.version; + //#region old migrations if (foundry.utils.isNewerVersion('1.1.0', lastMigrationVersion)) { const lockedPacks = []; const compendiumActors = []; @@ -190,6 +191,7 @@ export async function runMigrations() { lastMigrationVersion = '1.2.0'; } + //#endregion await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion); } diff --git a/styles/less/sheets/actors/character/inventory.less b/styles/less/sheets/actors/character/inventory.less index 5605a3d0..b555aa3d 100644 --- a/styles/less/sheets/actors/character/inventory.less +++ b/styles/less/sheets/actors/character/inventory.less @@ -57,11 +57,12 @@ } .currency-section { - display: flex; + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; gap: 10px; padding: 10px 10px 0; - input { + .input { color: light-dark(@dark, @beige); } } diff --git a/styles/less/sheets/actors/party/inventory.less b/styles/less/sheets/actors/party/inventory.less index 1dfc66de..2dcc97d8 100644 --- a/styles/less/sheets/actors/party/inventory.less +++ b/styles/less/sheets/actors/party/inventory.less @@ -61,11 +61,12 @@ } .currency-section { - display: flex; + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; gap: 10px; padding: 10px 10px 0; - input { + .input { color: light-dark(@dark, @beige); } } diff --git a/styles/less/sheets/actors/party/resources.less b/styles/less/sheets/actors/party/resources.less index fc7e0110..4db254bf 100644 --- a/styles/less/sheets/actors/party/resources.less +++ b/styles/less/sheets/actors/party/resources.less @@ -32,7 +32,7 @@ body.game:is(.performance-low, .noblur) { background: light-dark(@dark-blue-40, @dark-golden-40); border-radius: 6px; border: 1px solid light-dark(@dark-blue, @golden); - max-width: 230px; + width: 230px; height: -webkit-fill-available; .actor-name { diff --git a/styles/less/sheets/actors/party/sheet.less b/styles/less/sheets/actors/party/sheet.less index 658d9446..2d1344e8 100644 --- a/styles/less/sheets/actors/party/sheet.less +++ b/styles/less/sheets/actors/party/sheet.less @@ -14,11 +14,11 @@ .application.sheet.daggerheart.actor.dh-style.party { .tab { - height: -webkit-fill-available; - max-height: 514px; + flex: 1; overflow-y: auto; scrollbar-width: thin; scrollbar-color: light-dark(@dark-blue, @golden) transparent; + scrollbar-gutter: stable; &.active { overflow: auto; diff --git a/styles/less/ui/chat/damage-summary.less b/styles/less/ui/chat/damage-summary.less index 02fdbadf..3fea45e5 100644 --- a/styles/less/ui/chat/damage-summary.less +++ b/styles/less/ui/chat/damage-summary.less @@ -28,12 +28,15 @@ display: flex; flex-direction: column; gap: 2px; - cursor: pointer; transition: all 0.3s ease; border-radius: 6px; - &:hover { - background: @golden-10; + &.clickable { + cursor: pointer; + + &:hover { + background: @golden-10; + } } header { diff --git a/styles/less/ui/chat/effect-summary.less b/styles/less/ui/chat/effect-summary.less index 9bea1fd9..053c0be8 100644 --- a/styles/less/ui/chat/effect-summary.less +++ b/styles/less/ui/chat/effect-summary.less @@ -90,11 +90,14 @@ background: transparent; transition: all 0.3s ease; padding: 5px; - cursor: pointer; transition: all 0.3s ease; - &:hover { - background: @golden-10; + &.clickable { + cursor: pointer; + + &:hover { + background: @golden-10; + } } img { diff --git a/styles/less/ui/combat-sidebar/token-actions.less b/styles/less/ui/combat-sidebar/token-actions.less index 41fb38ab..f3a11235 100644 --- a/styles/less/ui/combat-sidebar/token-actions.less +++ b/styles/less/ui/combat-sidebar/token-actions.less @@ -21,11 +21,6 @@ padding: 8px; --button-size: 0; - &.used { - opacity: 0.5; - background: transparent; - } - &.inactive { background: var(--color-warm-2); color: var(--color-light-1); @@ -35,6 +30,11 @@ filter: none; } } + + &.used { + opacity: 0.5; + background: transparent; + } } } diff --git a/styles/less/ui/settings/settings.less b/styles/less/ui/settings/settings.less index 788db394..49c9fc7c 100644 --- a/styles/less/ui/settings/settings.less +++ b/styles/less/ui/settings/settings.less @@ -27,6 +27,21 @@ } } + .toggleable-row { + width: 100%; + display: flex; + align-items: center; + gap: 4px; + + &.spaced { + padding-right: 28.5px; + } + + .form-group { + flex: 1; + } + } + .setting-group-field { white-space: nowrap; display: flex; diff --git a/system.json b/system.json index a7c79226..467e9f7d 100644 --- a/system.json +++ b/system.json @@ -2,10 +2,10 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "1.2.1", + "version": "1.2.2", "compatibility": { "minimum": "13", - "verified": "13.350", + "verified": "13.351", "maximum": "13" }, "authors": [ diff --git a/templates/settings/homebrew-settings/settings.hbs b/templates/settings/homebrew-settings/settings.hbs index 893e1bc3..d59a9cd5 100644 --- a/templates/settings/homebrew-settings/settings.hbs +++ b/templates/settings/homebrew-settings/settings.hbs @@ -29,13 +29,25 @@ - {{formGroup settingFields.schema.fields.currency.fields.enabled value=settingFields._source.currency.enabled localize=true}} - {{formGroup settingFields.schema.fields.currency.fields.title value=settingFields._source.currency.title localize=true}} - {{formGroup settingFields.schema.fields.currency.fields.coins value=settingFields._source.currency.coins localize=true}} - {{formGroup settingFields.schema.fields.currency.fields.handfuls value=settingFields._source.currency.handfuls localize=true}} - {{formGroup settingFields.schema.fields.currency.fields.bags value=settingFields._source.currency.bags localize=true}} - {{formGroup settingFields.schema.fields.currency.fields.chests value=settingFields._source.currency.chests localize=true}} - +