diff --git a/daggerheart.d.ts b/daggerheart.d.ts index 02abf063..891a3a2a 100644 --- a/daggerheart.d.ts +++ b/daggerheart.d.ts @@ -11,9 +11,6 @@ import * as documents from './module/documents/_module.mjs'; import { macros } from './module/_module.mjs'; import * as dice from './module/dice/_module.mjs'; import * as fields from './module/data/fields/_module.mjs'; -import { gameSettings } from './module/config/settingsConfig.mjs'; -import DhCountdowns from './module/data/countdowns.mjs'; -import DhAutomation from './module/data/settings/Automation.mjs'; // Foundry's use of `Object.assign(globalThis) means many globally available objects are not read as such @@ -106,13 +103,3 @@ declare module '@client/packages/system.mjs' { }; } } - -declare module '@client/helpers/client-settings.mjs' { - // Add explicit typed overrides for auto complete. These require /** @type {"string"} on the vars themselves to work */ - export default interface ClientSettings { - get(namespace: 'daggerheart', key: typeof gameSettings.Automation): DhAutomation; - get(namespace: 'daggerheart', key: typeof gameSettings.Homebrew): DhHomebrew; - get(namespace: 'daggerheart', key: typeof gameSettings.Countdowns): DhCountdowns; - get(namespace: 'daggerheart', key: string): unknown; - } -} \ No newline at end of file diff --git a/module/applications/ui/countdownEdit.mjs b/module/applications/ui/countdownEdit.mjs index af1af46b..1dbc56be 100644 --- a/module/applications/ui/countdownEdit.mjs +++ b/module/applications/ui/countdownEdit.mjs @@ -153,6 +153,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio action: socketEvent.Refresh, data: { refreshType: RefreshType.Countdown } }); + Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown }); } static #addCountdown() { diff --git a/module/applications/ui/countdowns.mjs b/module/applications/ui/countdowns.mjs index 996a46ea..5cf79100 100644 --- a/module/applications/ui/countdowns.mjs +++ b/module/applications/ui/countdowns.mjs @@ -12,11 +12,14 @@ const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; export default class DhCountdowns extends HandlebarsApplicationMixin(ApplicationV2) { previousCountdownData = null; + changedCountdownsForAnimation = new Set(); constructor(options = {}) { super(options); + this.previousCountdownData = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns).countdowns; + this.setupHooks(); } /** @inheritDoc */ @@ -77,15 +80,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application return frame; } - /** @inheritdoc */ - async _onFirstRender(context, options) { - await super._onFirstRender(context, options); - this._createContextMenu(this._getCountdownContextOptions, '.countdown-container[data-countdown]', { - parentClassHooks: false, fixed: true - }); - } - /** @inheritdoc */ async _onRender(context, options) { await super._onRender(context, options); @@ -100,7 +95,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application /* Handle animations to draw attention to countdown values changing */ const typesToAnimate = new Set(); - for (const countdownKey of options.animate ?? []) { + for (const countdownKey of this.changedCountdownsForAnimation) { const shimmerAnimation = [ { backgroundPositionX: '98%' }, { backgroundPositionX: '0%' } @@ -132,6 +127,8 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application const element = this.element.querySelector(`.header-type-toggles .header-type[data-type="${type}"]`); element?.animate(pulseAnimation, pulseTiming); } + + this.changedCountdownsForAnimation.clear(); } /** Returns countdown data filtered by ownership */ @@ -140,15 +137,17 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application const values = Object.entries(setting.countdowns).map(([key, countdown]) => ({ key, countdown, - ownership: countdown.getUserLevel(game.user) + ownership: DhCountdowns.#getPlayerOwnership(game.user, setting, countdown) })); return values.filter(v => v.ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE); } _getCountdownData() { + const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); + return this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => { const playersWithAccess = game.users.reduce((acc, user) => { - const ownership = countdown.getUserLevel(user); + const ownership = DhCountdowns.#getPlayerOwnership(user, setting, countdown); if (!user.isGM && ownership && ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) { acc.push(user); } @@ -214,6 +213,19 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application return context; } + static #getPlayerOwnership(user, setting, countdown) { + if (user.isGM) return CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER; + + const playerOwnership = countdown.ownership[user.id]; + return playerOwnership === undefined || playerOwnership === CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT + ? setting.defaultOwnership + : playerOwnership; + } + + cooldownRefresh = ({ refreshType }) => { + if (refreshType === RefreshType.Countdown) this.render(); + }; + static canPerformEdit() { if (game.user.isGM) return true; @@ -311,12 +323,18 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application action: socketEvent.Refresh, data: { refreshType: RefreshType.Countdown } }); + Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown }); + } + + setupHooks() { + Hooks.on(socketEvent.Refresh, this.cooldownRefresh.bind()); } async close(options) { /* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */ if (options.closeKey) return; + Hooks.off(socketEvent.Refresh, this.cooldownRefresh); return super.close(options); } @@ -359,30 +377,4 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application refreshType: RefreshType.Countdown }); } - - /** - * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - */ - _getCountdownContextOptions() { - /** @param {HTMLElement} element */ - const getCountdownFromElement = element => { - const id = element.closest('[data-countdown]').dataset.countdown; - if (!id) return null; - const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); - return setting.countdowns[id ?? '']; - } - - return [ - { - label: 'CONTROLS.CommonDelete', - icon: 'fa-solid fa-trash', - visible: element => { - return getCountdownFromElement(element)?.isOwner; - }, - onClick: (_, target) => { - getCountdownFromElement(target)?.delete(); - } - } - ]; - } } diff --git a/module/config/settingsConfig.mjs b/module/config/settingsConfig.mjs index 7aa1b0f7..50841084 100644 --- a/module/config/settingsConfig.mjs +++ b/module/config/settingsConfig.mjs @@ -27,10 +27,8 @@ export const menu = { }; export const gameSettings = { - /** @type {'Automation'} */ Automation: 'Automation', Metagaming: 'Metagaming', - /** @type {'Homebrew'} */ Homebrew: 'Homebrew', appearance: 'Appearance', GlobalOverrides: 'GlobalOverrides', @@ -39,7 +37,6 @@ export const gameSettings = { Fear: 'ResourcesFear' }, LevelTiers: 'LevelTiers', - /** @type {'Countdowns'} */ Countdowns: 'Countdowns', LastMigrationVersion: 'LastMigrationVersion', SpotlightRequestQueue: 'SpotlightRequestQueue', diff --git a/module/config/system.mjs b/module/config/system.mjs index de6630ec..31dba518 100644 --- a/module/config/system.mjs +++ b/module/config/system.mjs @@ -12,7 +12,6 @@ import * as HOOKS from './hooksConfig.mjs'; import * as TRIGGER from './triggerConfig.mjs'; import * as ITEMBROWSER from './itemBrowserConfig.mjs'; -/** @type {"daggerheart"} */ export const SYSTEM_ID = 'daggerheart'; export const SYSTEM = { diff --git a/module/data/_types.d.ts b/module/data/_types.d.ts deleted file mode 100644 index f0e2ade5..00000000 --- a/module/data/_types.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { DhCountdown } from './countdowns.mjs' - -declare module './countdowns.mjs' { - export default interface DhCountdowns { - countdowns: Record; - } -} \ No newline at end of file diff --git a/module/data/countdowns.mjs b/module/data/countdowns.mjs index 5fe01e20..ffe4d26b 100644 --- a/module/data/countdowns.mjs +++ b/module/data/countdowns.mjs @@ -1,4 +1,3 @@ -import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs'; import FormulaField from './fields/formulaField.mjs'; export default class DhCountdowns extends foundry.abstract.DataModel { @@ -15,36 +14,19 @@ export default class DhCountdowns extends foundry.abstract.DataModel { }; } - /** @inheritdoc */ - _initialize(options) { - super._initialize(options); - for (const [id, countdown] of Object.entries(this.countdowns)) { - countdown.id = id; - } - } - - async handleChange() { + handleChange() { const previousCountdowns = foundry.ui.countdowns.previousCountdownData; const changedCountdowns = Object.entries(this.countdowns).reduce((acc, [key, countdown]) => { - const previous = previousCountdowns[key]; - const currentChanged = !previous || (previous.progress.current !== countdown.progress.current); - if (currentChanged && previous?.progress.start === countdown.progress.start) { + const previousCountdown = previousCountdowns[key]; + if (!previousCountdown || (previousCountdown.progress.current !== countdown.progress.current)) { acc.push(key); } + return acc; }, []); - // Re-render countdowns applications. When the change is due to an actual update, resync the editor - if (!foundry.utils.equals(previousCountdowns, this.countdowns)) { - await foundry.ui.countdowns.render({ animate: changedCountdowns }); - for (const instance of game.system.api.applications.ui.CountdownEdit.instances()) { - instance.data = this; - await instance.render(); - } - } - - // Inform modules of updates - Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown }); + for (const countdownKey of changedCountdowns) + foundry.ui.countdowns.changedCountdownsForAnimation.add(countdownKey); } static migrateData(source) { @@ -178,14 +160,6 @@ export class DhCountdown extends foundry.abstract.DataModel { }, {}); } - /** - * A boolean indicator for whether the current game User has ownership rights for this countdown - * @returns {boolean} - */ - get isOwner() { - return this.getUserLevel(game.user) === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER; - } - /** @inheritDoc */ static migrateData(source) { if (source.progress.max) { @@ -196,29 +170,4 @@ export class DhCountdown extends foundry.abstract.DataModel { return super.migrateData(source); } - - /** - * Get the explicit permission level that a User has over this Document, a value in CONST.DOCUMENT_OWNERSHIP_LEVELS. - * Compendium content ignores the ownership field in favor of User role-based ownership. Otherwise, Documents use - * granular per-User ownership definitions and Embedded Documents defer to their parent ownership. - * - * @param {BaseUser} [user=game.user] The User being tested - * @returns {DocumentOwnershipNumber} A numeric permission level from {@link CONST.DOCUMENT_OWNERSHIP_LEVELS} - */ - getUserLevel(user) { - if (user.isGM) return CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER; - - const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); - const playerOwnership = this.ownership[user.id]; - return playerOwnership === undefined || playerOwnership === CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT - ? setting.defaultOwnership - : playerOwnership; - } - - async delete() { - const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns); - const data = foundry.utils.deepClone(setting._source); - delete data.countdowns[this.id]; - await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data); - } } diff --git a/module/dice/die/baseDie.mjs b/module/dice/die/baseDie.mjs index 4c684dd3..26ed6d7f 100644 --- a/module/dice/die/baseDie.mjs +++ b/module/dice/die/baseDie.mjs @@ -19,7 +19,7 @@ export default class BaseDie extends foundry.dice.terms.Die { this.results.splice(resultIndex, 0, rerolledResult); if (['c', 'cc'].some(x => this.modifiers.includes(x))) { - await this.handleComboDiceReroll(resultIndex, result); + await this.handleComboDiceReroll(resultIndex); } } @@ -66,7 +66,7 @@ export default class BaseDie extends foundry.dice.terms.Die { async rollComboDice(options) { const { maxIncreasesDiceSize, rerollStartIndex } = options; - const initialResultsLength = this.results.filter(x => x.active).length; + const initialResultsLength = this.results.length; const result = await this.continueCombo(maxIncreasesDiceSize); /* The flow of DiceSoNice has no way of knowing that some of the results of a Die should be a different denomination @@ -112,19 +112,15 @@ export default class BaseDie extends foundry.dice.terms.Die { async continueCombo(maxIncreasesDiceSize) { const activeResults = this.results.filter(x => x.active); const lastIndex = activeResults.length - 1; - const lastResult = activeResults[lastIndex]; /* The Combo only continues if the latest roll was higher or equal to the previous */ - if (lastResult.result < activeResults[lastIndex - 1].result) return false; + if (activeResults[lastIndex].result < activeResults[lastIndex - 1].result) return false; - const lastFaces = lastResult.denomination ? - Number(lastResult.denomination.slice(1)) : this.faces; - const currentDenomination = `d${lastFaces}` + const lastFaces = activeResults[lastIndex].denomination?.slice(1) ?? this.faces; const increaseDiceSize = maxIncreasesDiceSize && lastFaces < 12 && - lastResult.result === lastFaces; - - const denomination = increaseDiceSize ? adjustDice(currentDenomination, false) : currentDenomination; + activeResults[lastIndex].result === lastFaces; + const denomination = increaseDiceSize ? adjustDice(this.denomination, false) : this.denomination; const newRoll = await (new Roll(`1${denomination}`)).evaluate(); this.results.push({ result: newRoll.total, denomination: denomination, active: true }); @@ -133,9 +129,8 @@ export default class BaseDie extends foundry.dice.terms.Die { return this.continueCombo(maxIncreasesDiceSize); } - async handleComboDiceReroll(rerolledIndex, originalResult) { - /* Potentially sliced when correcting compound dice at (1) */ - let resultGroupingIndexes = this.results.map((x, index) => ({ index, active: x.active })) + async handleComboDiceReroll(rerolledIndex) { + const resultGroupingIndexes = this.results.map((x, index) => ({ index, active: x.active })) .filter(x => x.active).map(x => x.index); if (resultGroupingIndexes.length <= 1) return; @@ -148,29 +143,7 @@ export default class BaseDie extends foundry.dice.terms.Die { const nextIndex = resultGroupingIndexes[rerollGroupingIndex + 1]; const nextResult = this.results[nextIndex]; - const dropDice = preceeding => { - const cutoffIndex = preceeding ? resultGroupingIndexes[rerollGroupingIndex + 1] + 1 : rerolledIndex + 1; - this.results = this.results.slice(0, cutoffIndex); - this.number = this.results.filter(x => x.active).length; - }; - - /* (1) If subsequent size increases from the compound effect are now incorrect, drop subsequent dice */ - const lastFaces = Number((originalResult.denomination ?? this.denomination).slice(1)); - if ( - compoundCombo && - ( - (originalResult.result === lastFaces && - rerolledResult.result !== lastFaces) - || - (originalResult.result !== lastFaces && - rerolledResult.result === lastFaces) - ) - ) { - dropDice(false); - resultGroupingIndexes = resultGroupingIndexes.slice(0, this.number); - } - - /* (2) Rerolling any of the last two dice might introduce new results */ + /* Rerolling any of the last two dice might introduce new results */ if ( rerollGroupingIndex === resultGroupingIndexes.length - 1 && rerolledResult.result >= previousResult?.result @@ -188,13 +161,11 @@ export default class BaseDie extends foundry.dice.terms.Die { rerollStartIndex: rerollGroupingIndex }); } - /* (3) Rerolling a subsequent dice might invalidate later dice which should then be dropped */ + /* Rerolling a preceeding dice might invalidate later dice which should then be dropped */ else if (rerolledResult.result < previousResult?.result){ - dropDice(false); - } - /* (4) Rerolling a preceeding dice might invalidate later dice which should then be dropped */ - else if (rerolledResult.result >= nextResult?.result) { - dropDice(true); + const cutoffIndex = rerollGroupingIndex === 0 ? resultGroupingIndexes[1] + 1 : rerolledIndex + 1; + this.results = this.results.slice(0, cutoffIndex); + this.number = this.results.filter(x => x.active).length; } const fakeRollFaces = diff --git a/system.json b/system.json index 0aafe3f5..214ab2ee 100644 --- a/system.json +++ b/system.json @@ -5,7 +5,7 @@ "version": "2.5.4", "compatibility": { "minimum": "14.364", - "verified": "14.365", + "verified": "14.364", "maximum": "14" }, "url": "https://github.com/Foundryborne/daggerheart",