From 2d804e7aa2b564c948790c99eb300e57bf91f033 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 11 Feb 2025 23:54:59 -0700 Subject: [PATCH 001/164] Add support for the Good item type to represent all misc items in the game --- langs/en-ca.json | 1 + module/data/Item/Good.mjs | 56 +++++++++++++++++++++++++++++++++++++++ module/gameTerms.mjs | 1 + module/hooks/init.mjs | 2 ++ system.json | 1 + 5 files changed, 61 insertions(+) create mode 100644 module/data/Item/Good.mjs diff --git a/langs/en-ca.json b/langs/en-ca.json index e1f0484..737906c 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -7,6 +7,7 @@ "ammo": "Ammo", "armour": "Armour", "craft": "Craft", + "good": "Good", "shield": "Shield", "skill": "Skill", "weapon": "Weapon" diff --git a/module/data/Item/Good.mjs b/module/data/Item/Good.mjs new file mode 100644 index 0000000..3973cc1 --- /dev/null +++ b/module/data/Item/Good.mjs @@ -0,0 +1,56 @@ +import { CommonItemData } from "./Common.mjs"; + +const { fields } = foundry.data; + +export class GoodData extends CommonItemData { + // MARK: Schema + static defineSchema() { + const schema = super.defineSchema(); + + schema.description = new fields.HTMLField({ + blank: true, + nullable: false, + trim: true, + }); + + return schema; + }; + + // MARK: Base Data + prepareBaseData() { + super.prepareBaseData(); + }; + + // MARK: Derived Data + prepareDerivedData() { + super.prepareDerivedData(); + }; + + // #region Getters + // #endregion + + // #region Sheet Data + async getFormFields(_ctx) { + const fields = [ + { + id: `quantity`, + type: `integer`, + label: `RipCrypt.common.quantity`, + path: `system.quantity`, + value: this.quantity, + min: 0, + }, + { + id: `description`, + type: `prosemirror`, + label: `RipCrypt.common.description`, + path: `system.description`, + uuid: this.parent.uuid, + value: await TextEditor.enrichHTML(this.description), + collaborative: false, + }, + ]; + return fields; + }; + // #endregion +}; diff --git a/module/gameTerms.mjs b/module/gameTerms.mjs index f903878..de258d9 100644 --- a/module/gameTerms.mjs +++ b/module/gameTerms.mjs @@ -40,5 +40,6 @@ export const gameTerms = Object.preventExtensions({ `armour`, `weapon`, `shield`, + `good`, ]), }); diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 7ea36b7..50d4a6b 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -8,6 +8,7 @@ import { HeroSummaryCardV1 } from "../Apps/ActorSheets/HeroSummaryCardV1.mjs"; // Data Models import { AmmoData } from "../data/Item/Ammo.mjs"; import { CraftData } from "../data/Item/Craft.mjs"; +import { GoodData } from "../data/Item/Good.mjs"; import { HeroData } from "../data/Actor/Hero.mjs"; import { ProtectorData } from "../data/Item/Protector.mjs"; import { SkillData } from "../data/Item/Skill.mjs"; @@ -45,6 +46,7 @@ Hooks.once(`init`, () => { CONFIG.Item.dataModels.ammo = AmmoData, CONFIG.Item.dataModels.armour = ProtectorData; CONFIG.Item.dataModels.craft = CraftData; + CONFIG.Item.dataModels.good = GoodData; CONFIG.Item.dataModels.shield = ProtectorData; CONFIG.Item.dataModels.skill = SkillData; CONFIG.Item.dataModels.weapon = WeaponData; diff --git a/system.json b/system.json index b7f85ff..9924bfe 100644 --- a/system.json +++ b/system.json @@ -47,6 +47,7 @@ "ammo": {}, "armour": {}, "craft": {}, + "good": {}, "shield": {}, "skill": {}, "weapon": {} From ed93e9f92730a40d1eea5394686b52a1e7fb2853 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 13 Feb 2025 00:49:28 -0700 Subject: [PATCH 002/164] Add some resources to the relevant combat files --- eslint.config.mjs | 1 + module/Apps/sidebar/CombatTracker.mjs | 0 module/documents/combat.mjs | 26 ++++++++++++++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 module/Apps/sidebar/CombatTracker.mjs create mode 100644 module/documents/combat.mjs diff --git a/eslint.config.mjs b/eslint.config.mjs index 6fab321..272ca4a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -31,6 +31,7 @@ export default [ renderTemplate: `readonly`, TextEditor: `readonly`, fromUuid: `readonly`, + Combat: `readonly`, }, }, }, diff --git a/module/Apps/sidebar/CombatTracker.mjs b/module/Apps/sidebar/CombatTracker.mjs new file mode 100644 index 0000000..e69de29 diff --git a/module/documents/combat.mjs b/module/documents/combat.mjs new file mode 100644 index 0000000..2a4760c --- /dev/null +++ b/module/documents/combat.mjs @@ -0,0 +1,26 @@ +/* +Resources: +- Combat : https://github.com/foundryvtt/dnd5e/blob/4.3.x/module/documents/combat.mjs +- Combatant : https://github.com/foundryvtt/dnd5e/blob/4.3.x/module/documents/combatant.mjs +- CombatTracker : https://github.com/foundryvtt/dnd5e/blob/4.3.x/module/applications/combat/combat-tracker.mjs +*/ + +export class RipCryptCombat extends Combat { + /** + * @override + * Sorts combatants for the combat tracker in the following way: + * - Distance from the current fate ordinal. (0 -> 3) + * - Coin Flip result (if disposition matches flip result, then 0, otherwise, 0.5) + */ + _sortCombatants(a, b) { + // The distance from fate + }; + + nextTurn() { + // Make it skip all combatants with the same initiative value + }; + + previousTurn() { + // Go back a step + }; +}; From 837c2012c56efc5e657f5dd7bb3906decc600205 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 13 Feb 2025 19:53:02 -0700 Subject: [PATCH 003/164] Add the caster silhouette SVG --- assets/_credit.txt | 1 + assets/caster-silhouette.v1.svg | 1 + 2 files changed, 2 insertions(+) create mode 100644 assets/caster-silhouette.v1.svg diff --git a/assets/_credit.txt b/assets/_credit.txt index bc24cdd..e3eb41a 100644 --- a/assets/_credit.txt +++ b/assets/_credit.txt @@ -1,5 +1,6 @@ Oliver Akins: - geist-silhouette.v2.svg : All rights reserved. + - caster-silhouette.v1.svg : All rights reserved. ARISO: - icons/hourglass.svg (https://thenounproject.com/icon/hourglass-7546736/) : Rights Purchased diff --git a/assets/caster-silhouette.v1.svg b/assets/caster-silhouette.v1.svg new file mode 100644 index 0000000..ed60c3e --- /dev/null +++ b/assets/caster-silhouette.v1.svg @@ -0,0 +1 @@ + \ No newline at end of file From 216816253001333d0268933123d22a562105bc9e Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 13 Feb 2025 19:56:45 -0700 Subject: [PATCH 004/164] =?UTF-8?q?Move=20K=C3=BDnan=20in=20the=20credits?= =?UTF-8?q?=20file=20higher=20up=20since=20I=20don't=20care=20that=20much?= =?UTF-8?q?=20about=20alphabetical=20ordering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/_credit.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/_credit.txt b/assets/_credit.txt index e3eb41a..39124c6 100644 --- a/assets/_credit.txt +++ b/assets/_credit.txt @@ -2,15 +2,15 @@ Oliver Akins: - geist-silhouette.v2.svg : All rights reserved. - caster-silhouette.v1.svg : All rights reserved. +Kýnan Antos (Gritsilk Games): + - hero-silhouette.svg : Licensed to Distribute and Modify within the bounds of the "Foundry-RipCrypt" system. + ARISO: - icons/hourglass.svg (https://thenounproject.com/icon/hourglass-7546736/) : Rights Purchased Abdulloh Fauzan: - icons/info-circle.svg (https://thenounproject.com/icon/information-4176576/) : Rights Purchased -Kýnan Antos (Gritsilk Games): - - hero-silhouette.svg : Licensed to Distribute and Modify within the bounds of the "Foundry-RipCrypt" system. - Soetarman Atmodjo: - icons/roll.svg (https://thenounproject.com/icon/dice-5195278/) : Rights Purchased From e302b56a4e9b2ddf293c49256a6c7a6272b5f060 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 14 Feb 2025 00:04:48 -0700 Subject: [PATCH 005/164] Get most of the custom Initiative sorting stuff through the door --- eslint.config.mjs | 2 + module/Apps/ActorSheets/HeroSummaryCardV1.mjs | 2 +- module/Apps/sidebar/CombatTracker.mjs | 27 ++++++++++++++ module/data/Actor/Hero.mjs | 2 +- module/documents/combat.mjs | 31 +++++++++++++--- module/documents/combatant.mjs | 37 +++++++++++++++++++ module/gameTerms.mjs | 12 +++--- module/hooks/init.mjs | 6 +++ module/settings/metaSettings.mjs | 10 +++++ module/utils/distanceBetweenFates.mjs | 27 ++++++++++++++ 10 files changed, 142 insertions(+), 14 deletions(-) create mode 100644 module/documents/combatant.mjs create mode 100644 module/utils/distanceBetweenFates.mjs diff --git a/eslint.config.mjs b/eslint.config.mjs index 272ca4a..16a335a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -16,6 +16,7 @@ export default [ languageOptions: { globals: { CONFIG: `writable`, + CONST: `readonly`, game: `readonly`, Handlebars: `readonly`, Hooks: `readonly`, @@ -32,6 +33,7 @@ export default [ TextEditor: `readonly`, fromUuid: `readonly`, Combat: `readonly`, + Combatant: `readonly`, }, }, }, diff --git a/module/Apps/ActorSheets/HeroSummaryCardV1.mjs b/module/Apps/ActorSheets/HeroSummaryCardV1.mjs index 16b9895..0dc19bd 100644 --- a/module/Apps/ActorSheets/HeroSummaryCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSummaryCardV1.mjs @@ -116,7 +116,7 @@ export class HeroSummaryCardV1 extends GenericAppMixin(HandlebarsApplicationMixi ctx.fate.selected = ctx.actor.system.fate; ctx.fate.options = [ { label: `RipCrypt.common.empty`, v: `` }, - ...gameTerms.FatePath + ...Object.values(gameTerms.FatePath) .map(v => ({ label: `RipCrypt.common.path.${v}`, value: v })), ]; return ctx; diff --git a/module/Apps/sidebar/CombatTracker.mjs b/module/Apps/sidebar/CombatTracker.mjs index e69de29..05f5f29 100644 --- a/module/Apps/sidebar/CombatTracker.mjs +++ b/module/Apps/sidebar/CombatTracker.mjs @@ -0,0 +1,27 @@ +import { Logger } from "../../utils/Logger.mjs"; + +const { CombatTracker } = foundry.applications.sidebar.tabs; + +export class RipCryptCombatTracker extends CombatTracker { + /** + * @override + */ + async _prepareTurnContext(combat, combatant, index) { + Logger.debug(`_prepareTurnContext`); + const turn = await super._prepareTurnContext(combat, combatant, index); + + // if ( !this.viewed ) return; + + // ! TODO: This is causing an error while the combat is not active, but is fine when it's active, this needs to be fixed + Logger.debug(turn, combatant); + const groupKey = combatant.groupKey; + if (groupKey) { + turn.active ||= combat.combatant.groupKey === groupKey; + if (turn.active && !turn.css.includes(`active`)) { + turn.css += `active`; + }; + }; + + return turn; + } +}; diff --git a/module/data/Actor/Hero.mjs b/module/data/Actor/Hero.mjs index 6f2e3ed..ab5d3d5 100644 --- a/module/data/Actor/Hero.mjs +++ b/module/data/Actor/Hero.mjs @@ -90,7 +90,7 @@ export class HeroData extends foundry.abstract.TypeDataModel { trim: true, nullable: false, choices: () => { - return gameTerms.FatePath.concat(``); + return Object.values(gameTerms.FatePath).concat(``); }, }), level: new fields.SchemaField({ diff --git a/module/documents/combat.mjs b/module/documents/combat.mjs index 2a4760c..6be6f22 100644 --- a/module/documents/combat.mjs +++ b/module/documents/combat.mjs @@ -6,6 +6,24 @@ Resources: */ export class RipCryptCombat extends Combat { + + get groups() { + let groups = new Map(); + + for (const combatant of this.combatants) { + const groupKey = combatant.groupKey; + if (!groupKey) { continue }; + + if (groups.has(groupKey)) { + groups.get(groupKey).push(combatant); + } else { + groups.set(groupKey, [combatant]); + }; + }; + + return groups; + }; + /** * @override * Sorts combatants for the combat tracker in the following way: @@ -14,13 +32,14 @@ export class RipCryptCombat extends Combat { */ _sortCombatants(a, b) { // The distance from fate + return super._sortCombatants(a, b) * -1; }; - nextTurn() { - // Make it skip all combatants with the same initiative value - }; + // nextTurn() { + // // Make it skip all combatants with the same initiative value + // }; - previousTurn() { - // Go back a step - }; + // previousTurn() { + // // Go back a step + // }; }; diff --git a/module/documents/combatant.mjs b/module/documents/combatant.mjs new file mode 100644 index 0000000..d925fc6 --- /dev/null +++ b/module/documents/combatant.mjs @@ -0,0 +1,37 @@ +import { distanceBetweenFates } from "../utils/distanceBetweenFates.mjs"; + +export class RipCryptCombatant extends Combatant { + + async _preCreate(data, options, user) { + const allowed = await super._preCreate(data, options, user); + if (allowed === false) { return false }; + + const start = game.settings.get(`ripcrypt`, `currentFate`); + const end = this.actor?.system?.fate || this.baseActor?.system?.fate; + const fateDistance = distanceBetweenFates(start, end); + + this.updateSource({ + initiative: fateDistance, + }); + }; + + get groupKey() { + const path = this.token?.actor?.system?.fate; + + // Disallow grouping things that don't have a fate path + if (!path) { return null }; + + // Token Disposition (group into: friendlies, unknown, hostiles) + let disposition = `unknown`; + switch (this.token.disposition) { + case CONST.TOKEN_DISPOSITIONS.HOSTILE: + disposition = `hostile`; + break; + case CONST.TOKEN_DISPOSITIONS.FRIENDLY: + disposition = `friendly`; + break; + }; + + return `${path}:${disposition}`; + }; +}; diff --git a/module/gameTerms.mjs b/module/gameTerms.mjs index de258d9..3e3bfff 100644 --- a/module/gameTerms.mjs +++ b/module/gameTerms.mjs @@ -11,12 +11,12 @@ export const gameTerms = Object.preventExtensions({ FLECT: `flect`, FRACT: `fract`, }), - FatePath: [ - `North`, - `East`, - `South`, - `West`, - ], + FatePath: Object.freeze({ + NORTH: `North`, + EAST: `East`, + SOUTH: `South`, + WEST: `West`, + }), Access: [ `Common`, `Uncommon`, diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 50d4a6b..a6cbf58 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -28,6 +28,9 @@ import { registerDevSettings } from "../settings/devSettings.mjs"; import { registerMetaSettings } from "../settings/metaSettings.mjs"; import { registerUserSettings } from "../settings/userSettings.mjs"; import { registerWorldSettings } from "../settings/worldSettings.mjs"; +import { RipCryptCombat } from "../documents/combat.mjs"; +import { RipCryptCombatant } from "../documents/combatant.mjs"; +import { RipCryptCombatTracker } from "../Apps/sidebar/CombatTracker.mjs"; Hooks.once(`init`, () => { Logger.log(`Initializing`); @@ -53,6 +56,9 @@ Hooks.once(`init`, () => { // #endregion // #region Class Changes + CONFIG.ui.combat = RipCryptCombatTracker; + CONFIG.Combat.documentClass = RipCryptCombat; + CONFIG.Combatant.documentClass = RipCryptCombatant; CONFIG.Item.documentClass = RipCryptItem; CONFIG.Dice.terms.d = CryptDie; // #endregion diff --git a/module/settings/metaSettings.mjs b/module/settings/metaSettings.mjs index 57580b9..1f2abaf 100644 --- a/module/settings/metaSettings.mjs +++ b/module/settings/metaSettings.mjs @@ -8,4 +8,14 @@ export function registerMetaSettings() { ui.crypt.render({ parts: [ `delveConditions` ]}); }, }); + + game.settings.register(`ripcrypt`, `currentFate`, { + scope: `world`, + type: String, + config: false, + requiresReload: false, + onChange: () => { + ui.crypt.render({ parts: [ `fate` ] }); + }, + }); }; diff --git a/module/utils/distanceBetweenFates.mjs b/module/utils/distanceBetweenFates.mjs new file mode 100644 index 0000000..78a603a --- /dev/null +++ b/module/utils/distanceBetweenFates.mjs @@ -0,0 +1,27 @@ +import { gameTerms } from "../gameTerms.mjs"; +import { Logger } from "./Logger.mjs"; + +const { FatePath } = gameTerms; + +export function isOppositeFates(a, b) { + return a === FatePath.NORTH && b === FatePath.SOUTH + || a === FatePath.EAST && FatePath.WEST; +}; + +export function distanceBetweenFates(start, end) { + if (!start || !end) { + Logger.error(`Start and End must both have a defined value, given`, {start, end}); + return undefined; + }; + + if (isOppositeFates(start, end)) { + return 2; + }; + + let isForward = start === FatePath.SOUTH && end === FatePath.WEST; + isForward ||= start === FatePath.NORTH && end === FatePath.EAST; + if (isForward) { + return 1; + }; + return 3; +}; From c549a59c13ead58ac89ff82ddb5f317806b574c6 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 14 Feb 2025 23:23:53 -0700 Subject: [PATCH 006/164] More progress * Make it so that the initiative isn't actually saved to the database * Add .25 for unknown disposition entities * Add .5 for entities that lose the coinflip --- eslint.config.mjs | 1 + module/Apps/sidebar/CombatTracker.mjs | 22 ++++-- module/documents/combat.mjs | 103 ++++++++++++++++++++++++-- module/documents/combatant.mjs | 31 ++++++-- module/hooks/init.mjs | 1 + module/settings/metaSettings.mjs | 11 +++ 6 files changed, 146 insertions(+), 23 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 16a335a..f47a550 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -34,6 +34,7 @@ export default [ fromUuid: `readonly`, Combat: `readonly`, Combatant: `readonly`, + canvas: `readonly`, }, }, }, diff --git a/module/Apps/sidebar/CombatTracker.mjs b/module/Apps/sidebar/CombatTracker.mjs index 05f5f29..483cdd6 100644 --- a/module/Apps/sidebar/CombatTracker.mjs +++ b/module/Apps/sidebar/CombatTracker.mjs @@ -1,5 +1,3 @@ -import { Logger } from "../../utils/Logger.mjs"; - const { CombatTracker } = foundry.applications.sidebar.tabs; export class RipCryptCombatTracker extends CombatTracker { @@ -7,15 +5,13 @@ export class RipCryptCombatTracker extends CombatTracker { * @override */ async _prepareTurnContext(combat, combatant, index) { - Logger.debug(`_prepareTurnContext`); const turn = await super._prepareTurnContext(combat, combatant, index); - // if ( !this.viewed ) return; + turn.hasDecimals = true; + turn.initiative = combatant.dynamicInitiative; - // ! TODO: This is causing an error while the combat is not active, but is fine when it's active, this needs to be fixed - Logger.debug(turn, combatant); const groupKey = combatant.groupKey; - if (groupKey) { + if (groupKey && combat.started) { turn.active ||= combat.combatant.groupKey === groupKey; if (turn.active && !turn.css.includes(`active`)) { turn.css += `active`; @@ -23,5 +19,15 @@ export class RipCryptCombatTracker extends CombatTracker { }; return turn; - } + }; + + async _onRender(...args) { + await super._onRender(...args); + + // Purge the combat controls that I don't want to exist because they don't + // make sense in the system. + this.element.querySelector(`[data-action="resetAll"]`)?.remove(); + this.element.querySelector(`[data-action="rollNPC"]`)?.remove(); + this.element.querySelector(`[data-action="rollAll"]`)?.remove(); + }; }; diff --git a/module/documents/combat.mjs b/module/documents/combat.mjs index 6be6f22..acfedbe 100644 --- a/module/documents/combat.mjs +++ b/module/documents/combat.mjs @@ -31,15 +31,102 @@ export class RipCryptCombat extends Combat { * - Coin Flip result (if disposition matches flip result, then 0, otherwise, 0.5) */ _sortCombatants(a, b) { - // The distance from fate - return super._sortCombatants(a, b) * -1; + const ia = Number.isNumeric(a.dynamicInitiative) ? a.dynamicInitiative : -Infinity; + const ib = Number.isNumeric(b.dynamicInitiative) ? b.dynamicInitiative : -Infinity; + + const delta = ia - ib; + if (Math.sign(delta) !== 0) { + return delta; + }; + + // fallback to alphabetical sort + return a.name < b.name ? -1 : 1; }; - // nextTurn() { - // // Make it skip all combatants with the same initiative value - // }; + async nextTurn() { + if ( this.round === 0 ) {return this.nextRound()} - // previousTurn() { - // // Go back a step - // }; + const turn = this.turn ?? -1; + + const groupKey = this.combatant.groupKey; + + // Determine the next turn number + let nextTurn = null; + for (let i = turn + 1; i < this.turns.length; i++) { + const combatant = this.turns[i]; + if (combatant.groupKey !== groupKey) { + nextTurn = i; + break; + }; + }; + + // Maybe advance to the next round + if ( (nextTurn === null) || (nextTurn >= this.turns.length) ) {return this.nextRound()} + + const advanceTime = this.getTimeDelta(this.round, this.turn, this.round, nextTurn); + + // Update the document, passing data through a hook first + const updateData = {round: this.round, turn: nextTurn}; + const updateOptions = {direction: 1, worldTime: {delta: advanceTime}}; + Hooks.callAll(`combatTurn`, this, updateData, updateOptions); + await this.update(updateData, updateOptions); + return this; + }; + + async previousTurn() { + if (this.round === 0) { return this } + if ((this.turn === 0) || (this.turns.length === 0)) {return this.previousRound()} + + const currentTurn = (this.turn ?? this.turns.length) - 1; + let previousTurn = null; + const groupKey = this.combatant.groupKey; + for (let i = currentTurn; i >= 0; i--) { + const combatant = this.turns[i]; + if (combatant.groupKey !== groupKey) { + previousTurn = i; + break; + } + } + + const advanceTime = this.getTimeDelta(this.round, this.turn, this.round, previousTurn); + + // Update the document, passing data through a hook first + const updateData = {round: this.round, turn: previousTurn}; + const updateOptions = {direction: -1, worldTime: {delta: advanceTime}}; + Hooks.callAll(`combatTurn`, this, updateData, updateOptions); + await this.update(updateData, updateOptions); + return this; + }; + + /** + * Update display of Token combat turn markers. + * @protected + * @internal + */ + _updateTurnMarkers() { + return super._updateTurnMarkers(); + /* eslint-disable no-unreachable */ + if (!canvas.ready) { return }; + + const tokenGroup = this.combatant?.groupKey; + for (const token of canvas.tokens.turnMarkers) { + const actor = token.actor ?? token.baseActor; + if (actor?.groupKey !== tokenGroup) { + token.renderFlags.set({refreshTurnMarker: true}); + } + } + + if (!this.active) { return }; + const currentToken = this.combatant?.token?._object; + console.log(this.combatant.token._object) + if (!tokenGroup && currentToken) { + currentToken.renderFlags.set({refreshTurnMarker: true}); + } else { + for (const combatant of this.groups.get(tokenGroup)) { + console.log(combatant.token._object); + combatant.token._object.renderFlags.set({ refreshTurnMarker: true }); + } + } + /* eslint-enable no-unreachable */ + } }; diff --git a/module/documents/combatant.mjs b/module/documents/combatant.mjs index d925fc6..648a7aa 100644 --- a/module/documents/combatant.mjs +++ b/module/documents/combatant.mjs @@ -2,17 +2,34 @@ import { distanceBetweenFates } from "../utils/distanceBetweenFates.mjs"; export class RipCryptCombatant extends Combatant { - async _preCreate(data, options, user) { - const allowed = await super._preCreate(data, options, user); - if (allowed === false) { return false }; + get disposition() { + switch (this.token.disposition) { + case CONST.TOKEN_DISPOSITIONS.HOSTILE: + return `hostile`; + case CONST.TOKEN_DISPOSITIONS.FRIENDLY: + return `friendly`; + }; + return `unknown`; + }; + + get dynamicInitiative() { + let total = 0; const start = game.settings.get(`ripcrypt`, `currentFate`); const end = this.actor?.system?.fate || this.baseActor?.system?.fate; - const fateDistance = distanceBetweenFates(start, end); + total += distanceBetweenFates(start, end); - this.updateSource({ - initiative: fateDistance, - }); + const whoFirst = game.settings.get(`ripcrypt`, `whoFirst`); + const disposition = this.disposition; + if (whoFirst) { + if (disposition === `unknown`) { + total += 0.25; + } else if (whoFirst !== disposition) { + total += 0.5; + }; + } + + return total; }; get groupKey() { diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index a6cbf58..2577eb2 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -35,6 +35,7 @@ import { RipCryptCombatTracker } from "../Apps/sidebar/CombatTracker.mjs"; Hooks.once(`init`, () => { Logger.log(`Initializing`); + CONFIG.Combat.initiative.decimals = 1; CONFIG.ui.crypt = DelveTourApp; // #region Settings diff --git a/module/settings/metaSettings.mjs b/module/settings/metaSettings.mjs index 1f2abaf..9e6ec4d 100644 --- a/module/settings/metaSettings.mjs +++ b/module/settings/metaSettings.mjs @@ -18,4 +18,15 @@ export function registerMetaSettings() { ui.crypt.render({ parts: [ `fate` ] }); }, }); + game.settings.register(`ripcrypt`, `whoFirst`, { + scope: `world`, + type: String, + config: false, + requiresReload: false, + initial: `friendly`, + onChange: async () => { + await game.combat.setupTurns(); + await ui.combat.render({ parts: [ `tracker` ] }); + }, + }); }; From e1be6675e0ffb7b02db3278ad7f0bd660052f4c3 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 15 Feb 2025 01:43:25 -0700 Subject: [PATCH 007/164] Make the multi-turn indicator work fully --- eslint.config.mjs | 1 + module/Apps/sidebar/CombatTracker.mjs | 4 +-- module/documents/combat.mjs | 28 +++++++++++++------ module/documents/combatant.mjs | 40 +++++++++++++++------------ module/documents/token.mjs | 27 ++++++++++++++++++ module/hooks/init.mjs | 4 ++- 6 files changed, 76 insertions(+), 28 deletions(-) create mode 100644 module/documents/token.mjs diff --git a/eslint.config.mjs b/eslint.config.mjs index f47a550..aabd56c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -35,6 +35,7 @@ export default [ Combat: `readonly`, Combatant: `readonly`, canvas: `readonly`, + Token: `readonly`, }, }, }, diff --git a/module/Apps/sidebar/CombatTracker.mjs b/module/Apps/sidebar/CombatTracker.mjs index 483cdd6..bbef58f 100644 --- a/module/Apps/sidebar/CombatTracker.mjs +++ b/module/Apps/sidebar/CombatTracker.mjs @@ -10,9 +10,9 @@ export class RipCryptCombatTracker extends CombatTracker { turn.hasDecimals = true; turn.initiative = combatant.dynamicInitiative; - const groupKey = combatant.groupKey; + const groupKey = combatant?.groupKey; if (groupKey && combat.started) { - turn.active ||= combat.combatant.groupKey === groupKey; + turn.active ||= combat.combatant?.groupKey === groupKey; if (turn.active && !turn.css.includes(`active`)) { turn.css += `active`; }; diff --git a/module/documents/combat.mjs b/module/documents/combat.mjs index acfedbe..bca1afa 100644 --- a/module/documents/combat.mjs +++ b/module/documents/combat.mjs @@ -48,7 +48,7 @@ export class RipCryptCombat extends Combat { const turn = this.turn ?? -1; - const groupKey = this.combatant.groupKey; + const groupKey = this.turns[turn]?.groupKey; // Determine the next turn number let nextTurn = null; @@ -88,6 +88,14 @@ export class RipCryptCombat extends Combat { } } + if (previousTurn < 0) { + if (this.round === 1) { + this.round = 0; + return this; + }; + return this.previousRound() + } + const advanceTime = this.getTimeDelta(this.round, this.turn, this.round, previousTurn); // Update the document, passing data through a hook first @@ -104,8 +112,6 @@ export class RipCryptCombat extends Combat { * @internal */ _updateTurnMarkers() { - return super._updateTurnMarkers(); - /* eslint-disable no-unreachable */ if (!canvas.ready) { return }; const tokenGroup = this.combatant?.groupKey; @@ -118,15 +124,21 @@ export class RipCryptCombat extends Combat { if (!this.active) { return }; const currentToken = this.combatant?.token?._object; - console.log(this.combatant.token._object) if (!tokenGroup && currentToken) { currentToken.renderFlags.set({refreshTurnMarker: true}); } else { - for (const combatant of this.groups.get(tokenGroup)) { - console.log(combatant.token._object); - combatant.token._object.renderFlags.set({ refreshTurnMarker: true }); + const group = this.groups.get(tokenGroup) ?? []; + for (const combatant of group) { + combatant.token?._object?.renderFlags.set({ refreshTurnMarker: true }); } } - /* eslint-enable no-unreachable */ } + + async _manageTurnEvents() { + try { + await super._manageTurnEvents(); + } catch { + this._updateTurnMarkers(); + }; + }; }; diff --git a/module/documents/combatant.mjs b/module/documents/combatant.mjs index 648a7aa..21e1ba8 100644 --- a/module/documents/combatant.mjs +++ b/module/documents/combatant.mjs @@ -3,7 +3,7 @@ import { distanceBetweenFates } from "../utils/distanceBetweenFates.mjs"; export class RipCryptCombatant extends Combatant { get disposition() { - switch (this.token.disposition) { + switch (this.token?.disposition) { case CONST.TOKEN_DISPOSITIONS.HOSTILE: return `hostile`; case CONST.TOKEN_DISPOSITIONS.FRIENDLY: @@ -12,6 +12,10 @@ export class RipCryptCombatant extends Combatant { return `unknown`; }; + /** + * Used by the Combat tracker to order combatants according to their + * fate path and the coin flip. + */ get dynamicInitiative() { let total = 0; @@ -21,13 +25,11 @@ export class RipCryptCombatant extends Combatant { const whoFirst = game.settings.get(`ripcrypt`, `whoFirst`); const disposition = this.disposition; - if (whoFirst) { - if (disposition === `unknown`) { - total += 0.25; - } else if (whoFirst !== disposition) { - total += 0.5; - }; - } + if (disposition === `unknown`) { + total += 0.25; + } else if (whoFirst && whoFirst !== disposition) { + total += 0.5; + }; return total; }; @@ -39,16 +41,20 @@ export class RipCryptCombatant extends Combatant { if (!path) { return null }; // Token Disposition (group into: friendlies, unknown, hostiles) - let disposition = `unknown`; - switch (this.token.disposition) { - case CONST.TOKEN_DISPOSITIONS.HOSTILE: - disposition = `hostile`; - break; - case CONST.TOKEN_DISPOSITIONS.FRIENDLY: - disposition = `friendly`; - break; - }; + let disposition = this.disposition; return `${path}:${disposition}`; }; + + _onCreate() { + if (this.token) { + this.token._object._refreshTurnMarker(); + }; + }; + + _onDelete() { + if (this.token) { + this.token._object._refreshTurnMarker(); + }; + }; }; diff --git a/module/documents/token.mjs b/module/documents/token.mjs new file mode 100644 index 0000000..2b66aac --- /dev/null +++ b/module/documents/token.mjs @@ -0,0 +1,27 @@ +const { TokenTurnMarker } = foundry.canvas.placeables.tokens; + +export class RipCryptToken extends Token { + _refreshTurnMarker() { + // Should a Turn Marker be active? + const {turnMarker} = this.document; + const markersEnabled = CONFIG.Combat.settings.turnMarker.enabled + && (turnMarker.mode !== CONST.TOKEN_TURN_MARKER_MODES.DISABLED); + const combatant = game.combat?.active ? game.combat.combatant : null; + const isTurn = combatant && (combatant.groupKey === this.combatant?.groupKey); + const isDefeated = combatant && combatant.isDefeated; + const markerActive = markersEnabled && isTurn && !isDefeated; + + // Activate a Turn Marker + if ( markerActive ) { + if ( !this.turnMarker ) { + this.turnMarker = this.addChildAt(new TokenTurnMarker(this), 0); + }; + canvas.tokens.turnMarkers.add(this); + this.turnMarker.draw(); + } else if ( this.turnMarker ) { + canvas.tokens.turnMarkers.delete(this); + this.turnMarker.destroy(); + this.turnMarker = null; + } + } +}; diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 2577eb2..212626a 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -31,11 +31,12 @@ import { registerWorldSettings } from "../settings/worldSettings.mjs"; import { RipCryptCombat } from "../documents/combat.mjs"; import { RipCryptCombatant } from "../documents/combatant.mjs"; import { RipCryptCombatTracker } from "../Apps/sidebar/CombatTracker.mjs"; +import { RipCryptToken } from "../documents/token.mjs"; Hooks.once(`init`, () => { Logger.log(`Initializing`); - CONFIG.Combat.initiative.decimals = 1; + CONFIG.Combat.initiative.decimals = 2; CONFIG.ui.crypt = DelveTourApp; // #region Settings @@ -60,6 +61,7 @@ Hooks.once(`init`, () => { CONFIG.ui.combat = RipCryptCombatTracker; CONFIG.Combat.documentClass = RipCryptCombat; CONFIG.Combatant.documentClass = RipCryptCombatant; + CONFIG.Token.objectClass = RipCryptToken; CONFIG.Item.documentClass = RipCryptItem; CONFIG.Dice.terms.d = CryptDie; // #endregion From 064e2fda7ed683cda0263b2c96c1046ca5db88f3 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 15 Feb 2025 01:44:28 -0700 Subject: [PATCH 008/164] Lint and cleanup --- module/documents/combat.mjs | 2 +- module/hooks/init.mjs | 8 ++++---- module/settings/metaSettings.mjs | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/module/documents/combat.mjs b/module/documents/combat.mjs index bca1afa..18cfc48 100644 --- a/module/documents/combat.mjs +++ b/module/documents/combat.mjs @@ -93,7 +93,7 @@ export class RipCryptCombat extends Combat { this.round = 0; return this; }; - return this.previousRound() + return this.previousRound(); } const advanceTime = this.getTimeDelta(this.round, this.turn, this.round, previousTurn); diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 212626a..9c61730 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -4,6 +4,7 @@ import { CombinedHeroSheet } from "../Apps/ActorSheets/CombinedHeroSheet.mjs"; import { DelveTourApp } from "../Apps/DelveTourApp.mjs"; import { HeroSkillsCardV1 } from "../Apps/ActorSheets/HeroSkillsCardV1.mjs"; import { HeroSummaryCardV1 } from "../Apps/ActorSheets/HeroSummaryCardV1.mjs"; +import { RipCryptCombatTracker } from "../Apps/sidebar/CombatTracker.mjs"; // Data Models import { AmmoData } from "../data/Item/Ammo.mjs"; @@ -18,7 +19,10 @@ import { WeaponData } from "../data/Item/Weapon.mjs"; import { CryptDie } from "../dice/CryptDie.mjs"; // Documents +import { RipCryptCombat } from "../documents/combat.mjs"; +import { RipCryptCombatant } from "../documents/combatant.mjs"; import { RipCryptItem } from "../documents/item.mjs"; +import { RipCryptToken } from "../documents/token.mjs"; // Misc import helpers from "../handlebarHelpers/_index.mjs"; @@ -28,10 +32,6 @@ import { registerDevSettings } from "../settings/devSettings.mjs"; import { registerMetaSettings } from "../settings/metaSettings.mjs"; import { registerUserSettings } from "../settings/userSettings.mjs"; import { registerWorldSettings } from "../settings/worldSettings.mjs"; -import { RipCryptCombat } from "../documents/combat.mjs"; -import { RipCryptCombatant } from "../documents/combatant.mjs"; -import { RipCryptCombatTracker } from "../Apps/sidebar/CombatTracker.mjs"; -import { RipCryptToken } from "../documents/token.mjs"; Hooks.once(`init`, () => { Logger.log(`Initializing`); diff --git a/module/settings/metaSettings.mjs b/module/settings/metaSettings.mjs index 9e6ec4d..24eed63 100644 --- a/module/settings/metaSettings.mjs +++ b/module/settings/metaSettings.mjs @@ -18,6 +18,7 @@ export function registerMetaSettings() { ui.crypt.render({ parts: [ `fate` ] }); }, }); + game.settings.register(`ripcrypt`, `whoFirst`, { scope: `world`, type: String, From 463b0c45538cef8b63de744d74649220bf1b0b15 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 15 Feb 2025 01:53:51 -0700 Subject: [PATCH 009/164] Make the logger bind the function with the prefixed args rather than returning an anon function --- module/utils/Logger.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/utils/Logger.mjs b/module/utils/Logger.mjs index fc1b51c..70c6481 100644 --- a/module/utils/Logger.mjs +++ b/module/utils/Logger.mjs @@ -15,7 +15,7 @@ const augmentedProps = new Set([ export const Logger = new Proxy(console, { get(target, prop, _receiver) { if (augmentedProps.has(prop)) { - return (...args) => target[prop](game.system.id, `|`, ...args); + return target[prop].bind(target, game.system.id, `|`); }; return target[prop]; }, From 26534ec0cab6c8d6586ec9771479348a6f4f3825 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 15 Feb 2025 02:10:32 -0700 Subject: [PATCH 010/164] Update the turn marker settings on first load --- assets/turn-marker.png | Bin 0 -> 236185 bytes module/hooks/init.mjs | 1 + module/hooks/ready.mjs | 12 ++++++++++++ module/settings/metaSettings.mjs | 7 +++++++ 4 files changed, 20 insertions(+) create mode 100644 assets/turn-marker.png diff --git a/assets/turn-marker.png b/assets/turn-marker.png new file mode 100644 index 0000000000000000000000000000000000000000..fa707499e290858d06b4ebd80b1be507d2719638 GIT binary patch literal 236185 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́z>E71nh~^e?X7gy=xn? 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ù~s?6Bd31A%AhaqReb{gu)NSLf2r?e>)X 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

3sbDHuODU_cnDq;U$CMyqWKTO`kYefFCkQ{%;1_ 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 z z)9`Jy$hqx)ff=qJT6lyL_IW*|bR+|1(?QK&$BYxtNpkYg;Ry~zc@70Hf149FPJTir z3dp6r=*UMO%mjI#k?HL7ypXg!G2Q}r$zwh$4v}y`@)yrF5ALPl;NX=cWPmU+^k?-? zx&w!d(PiBrr;2oD%c}xqjd(R~a>RE+gfn19aO0PLXb_FK{|4;yO#7t6*X+w*+)NL? zo%DiJUvwIxEafEh{xt8JJF%Vfalgy%f<`+X1AHCr7I^b> zRbA)&@mhh9-BlG_oH=f975~Ihi@H9_u8tog|4Vll09Wbecipia+FC`@UwruUcIcOW{VexQ+MSp*r}Y&x?*;A zniJ8?j4#k(>}Y;mASdfoRxhIs@*&hYw!y=_Sfq7OprY^HrAsP5oh24=4wP$jQ@7dxy?C zw%8zR<%rKbjSm{Zw9j~SCvx(iG81+ZV4K#KLUE>zOY<6d?eYFue9?vh>jJ#>W)Z_~ zy%>?vfP4AKiI?p{CWXsvoesVkN&}*6i|+hS8>!3=<&=c>K}uUWh2;c#FY~?R<}ij( zoQH92I$_qn;E9Y*BBs8Qs^f(6jUnZN=V1A~Kh`I*Y1>BC$7P_5YD*)!wLK#PR+^}J z?-#ndgtmn(b@;#X$+I>bU~3(uAK0F1KQf_NE){Xbz8YXC-VnLjPZ={DPqLVhZr^15 zi+;uq`>F?SWsxW296GGJhqTd*V-R)VUOH<}v)g=S^7k+hOWOC!&E8eJpKX23{J7TD zxbb|R+YF#NMhiOgI(^fw#;+3|sc;Yc^|imCU)^;qB+u_ISc%UkOlO|^OF7y|Yy_F* zaf5AO9*VOOxHa=j8tP{>3_n`nX?C5t&%*EVp_ePHs_*LLX_@%!th!q@Hr#$>;UZ2r zPSeIY;DOJaIL(m;KJfXju`@@~ZDZMj2j_N{&#Ur9IGlN&VWA@{ZU%j~!i-;#W(Pw- zIw3f}o$QAm+zCi$gEw2?<^^x~QYoYfGf+`p?Qj54SGL_irME4ibRS2^D`dcsAH6BW zD99YxsM8Dp_F#EPOUtuxZLS@V*5~!nr7d37OtR=#j@-oHO|k5fwfFt-}+fLz#gqde-f#w{e`(37`zwxf+wE z)@kkoxHx)*s$!MT{Z)Qk(sHjCrqT?(y@w!r$iPthy^2eJAw7XMQCfKIcV6-+MCwTrnj@(A5Gxt4o;dS`$v2rr)bMjWrCDQhXoY+LhhHa1P2~K5=qc+=3 zZpqtMl<^(-p?*6Vsw@bfi@RO#3S2qcc)pvAer4M!@_HMSjXpDvQWZNBr3}%9Eddlg z=DEDKIk2chQaWoQBrKHq8%j504FIMC0ha*^Tu!p8SAFE`z@x+Oidr3gf8K$@!Jwg; z=Ujoxjaw1&kNh%tSU*l)3@Ny>F?wTtk`~X^Vvq`GSMrk}$X6)N^olS2EZj<4;^vk7 z2CjL>4)f!ph@0Il=V(ZIR6{%;H<4Dogj2q@ZK|R#sHH4Rbm4w_v~u3tF9h0$K+CG^ zs-pLxI``AXCtL*8cMw+m$b03h9g}TF;394oqN3l#k|(L%bmtu*3|ji)&{3Uf z|EK7+&j_lDPD5efx5V~;)`5LamcipaQXcdcscZ%Zk$?>137w=H%Afit%pyQ1E{~US zAbBb~;!$lIrGKZ7yD)IE;26U=b)SXGRpPEK9Oo`F#yD=&*?YjvRe63X4Z0`Ob>^vA=iw%G$FW17PThkg=)6qFc=$c|)Pr2! zFZUTc>3n#^j)4tX)uSgLs-fd_I~Nt(^LlKwoa)Jztu}g?iV49Z!ssVR$M$}rGo1|u z9<)pL=pqNr=$SATFI)qsPR%Ts&VWnU{6e!F zWg?8jREJ2fDlsmvY3b_oezhKm23BPQHOobMC+|Ujrr?0yXObICwITH|bRAN8(7r>A zr)4g$bwdzci|oGc{j@4;XTu$yMc3|4QxdS#t>C>34St@!+245nSY4!k#yve-6%tP=50QK40@?Sj&%IIqulc<<+L&Q}(xw5PIO)56ZZv z@%PQ$CjWQD9kV|o6zaSiiaUay@!Z57WRV8WRr&{|S>NWl3*P}}Zh=m5ikD>XxgIhS?$9cca%GYP;Z_>g(!@2rf>2W&n-(zN~ zue8w$I%^%hKd%}GOy_un?%)Q^Q=dYdJn=ZicR{#Kj;zng{Z%2sUwpf$v&V*R1*8+A zODCvv_llh1c;>1T&9*&dd#r5n@}M&(;-O`50GDkwNJ5@#!|!wb3>5Io7d&;Cfy7Zk zU54sY2Q~%B+o6hUI~yOQ1hK&~#UKLfsl4D52bXetzbv21DeJTq zezWhOPIRM+gX0b;P@Vc<;OIV|CvUdv=73$$#mo-4!g--Fl2o)hli;*g@1f8=ML z{se0}1UmA5COZ8M;48>JWYXX-x}B>n(B@!3+#Q1ca$7q07##BY8BY7$J6k^Hl>wAI z@+zE816*y$ffw(yI03o&JP|u8A06_EGh9HYpA`c-v@y@U>ELsVU491$>w$O&r4AC& zY5wTRg^t)`2;_9K_rO_{Jm#Ywt{K8>+Dlc2u~QytA_egWKptTPPZC9pP(BA*p)yGn znZ)vsd}B-bjv}`!VvRzJl3lBE*Rkg6G z3!HD{ocjk@7{l`TkACNGn&)*j#x0%s#g8MAF;ZQAuS19v#}`xZFg zm(&USjL5!l&qmM6{uWL*zXi;S+}}(WU7?bC7r=t{=u_`sM}sQQ@`>Okx9pupLkv&F<&Pv z=*h%DLBVn%qs=Meuq8C`!NHq>Nxe**;=qj~qimcOWfUKNapNM`>{mbK$^a|V$X~v^ zJB(~V0rN8<4-rvG6Q;hDAuc2=!$8hKyy=Z~`#*5WcPc;+cu6Ve;5Tj&%{%-xV$d_F zj<`X~W_{#shLKmwWBIJRQBUF`Zw6jVy{jj7Ecw;0NkinR(EQbt!aY@DV_5RtmpILi zGwrG!Dr>xX?Auj(d24&qr59g4r5hF&TE!%-W!TStN3?ki6Jpy{+aA&UOsYGF9InkP zYcYm29{9p<(X9^XnHZQ6^g45$xT4x^=m)nP7cVo`-D50sweIeKPTViP`Hhc$4ekx| zaa;o&i{FRmms|4S|0D9yS)T=xTaWS{V0S0?JL1$GSe|d_je2~%_ARCr5Mt8h@Khozu7b&})%YhpA^{VE2x*fL?2~^3AN$3UHlA!2nc_l!B0cqX_o3MiRZlpr`$1G~qL!8Va91Xi zCo)alJdv6*3u@+B+-YUYy;#Z+{u)>#8dljC2wNUSv|--r8eYGjrf`2!#iHd8@PcrVcplDqhP;QvTF) zG9?b(X!^25`#@=iT%p;&MUuU57 z)mzUDIS%>3m~JRR&3Ph27WDZo4DB&{zwzOh;G7WTD{$!a`8z`<8F3N!vE?;*{)Tv+ zc~7#h&(0NDLFcZD;{|D2Xo&v$1JT*N9ozDc|LzgPB064daBz7sz?+rZbxe8e1V5u; zzD~n=4TpP&2JUIhe*?bH*#B=D@B6H}?}59?0IQRM_1ps*yes<4Y>oa0LciOd?R5A& zOm~y?6&OE*&!1(zja-}$`ZHMT%-~JOvqOmo>@0EG$mtB+5y;1B=r}&t_IKEQZXQQ& z=d%}(m(EfBd4k=<|R~-=R-g9S|H)wB7QfBZHY~^H;z5sIxpoCy18^$ize) zmJu+mZ5{BOfXFUOZG6(ZK^9~h(E15VU0%?{$0p**j&uj=*e}x_6d!qotQ!)hJJE`W zc5}^okU{>2)JGm`$OojHTXefG13yq_2B{l$sQVCn0bLOvdK5cN<)LgQZV2tmo@T}g z{3=)@PB4w!R$k+$@yWi>XI-uQL)TPEB=LzDd8%sQ4As@0nVf@x9{u3U*ZhXqR3v`# zh}9mh;ArI8-D+P@*#2Z693d%~`Lq9&rekN!aN_7eHyyZJ>u!%f{1jW^b+^zl16|{8wq0j)^`i7d#l8UrECzow=)ZZF|K&54b8XyakWHp5_VoYlP{fc2socV=~~U zBZt@L={o@Lb$AM}oC?PfkC0z@a=yy+F?jR2zPb9<;h(DS`6W1@KA(^qhUo`yP;MlR z%U|PpOQ*#O-1pBvkzk_|Y(O;PD>$DkH2Oc{!2MOVx47JHzfDVi1zG#U75FaG(A`eb znfoj>TjHes`q8U2ygSgZ!haDxuF`!+Q>T~}JE1dQXRdxu)^@5xzVI6&+=C3}bgFze z9@%Vnp-)D)w6ysGol@{*yEARy4-G#w2XQ;3{Bas#gvSmh-3~2TUF2_`Ss5xbAcKs& z){AoHAzi_y4K-fC4p*9)>=0EhlK>_inUW7%QqRz8Uj{8$vRa`2G8r+C0Y)=HQYJ=& zz^>RpIyh|<2dK}Tj@Tbzks-mnh0W7c%2zV<$PK*grYqi}MlJu8HQUs&%w7Uv^bTAH z^wf!RT-{x%^88xfdcV{opJ4%gt8*_p`R*`ol$28)wQC9xIl#Y?55|X{Roy||tMJM^ z@CQ(<#BOn9u0X?bn?6*IXXRV<7_Z#W+4`IK@;8*fG6R?T7!74k0Ob*f-q}{{J6;Q} zMXAc2wyFFeq|KS;ij71=vyXC32M&LB+>|3P`)>}>I*1v^pYcIZ zA!DRt%6;O#a^@EMD~k<2E7zeHbmks%Q=N9d#^qlpurau` zbBjiLlLH$%Vmm}XW+1bNG;TuoFr6!S9f?2A;;J+6pQGano#4^v5cdgn_O7PgQo4(y zyoQ{!-8bO7%*r7VX6D93aHLJd1-r1B5-$#{<@*A$g`(YMXNK*|GBey7_^N-2|yK!-95Ewp_|^ z2^}PxuKlTtFvw&CsGMo8@+xawD6Z+^8m4ZsLoi6bqwhpU8`UL|EA&16p*gvBgP!lk{j~k?~6zu729UatJ8}?-6-A7G55K6PjBwoa!OiF9Gr#$s9 z%}a$sL*tceD6TN%LOO*W{pBY#y|$r4_g(hL(SIhd`soYV653>|G`-G8RLvUh5E12``_8$2V{jl`hxKzc)a%E z0BW)vBj-oM-X#yV*Vlz!eMFkUhe({IXDWQIBcn2(>oP3HpSWRkg8MWgXPo~62V|%C zfa{pFKeze@jn~h>mQzQ5)PT@0E9u~~GDn|rpReoe?Pxxme`vl;2-cq>Oh;4pO?Yv_euFuk zSDVq1!)`>=@uB4#&G73W#O3RA=4YBKlK~wISDpi_n@i`Std2T^1-kR>Tv_RKn8=G~ z!V<^svweDbCMDWJ9cAz_#MT_HA)jvL&((zaP>0!$x6IZ7`By!B>6iF&MHz4W7ycXWa}azIu}1uunU!2X|?l@c1#GthQz1Y8`jyBKd>& z+(YWVnVb@)u1}#&^kQp9t0i5VN3qFxN*#_ogV6PcM>AJ zgobM##X+vzYTrWM{=z$<=>E9?~8J#*RT?6R_blLOWgJ$gALBhwr$(D zPcJz8gZ-$yX>;}$@aZdtrj=J#e$zm7i8JrESr-WQU-$?DE)aZQ+{J>wRqjrrj$Cl3 z&9Uz$*Ig&<=)0ml;Iku1+^=m4b>=@nS61oh%|eNc+;$gyH0ckpyu+nq{UF$Fg?^-s z0MEE59KpZBP@{tmtmI=7ej)CYC$u{CRYi6K7Kgt>dvOx3^qy~+H<@BQW-v9qb3;F; zk-kDhbi3VIU^>ZC=FePZ+Zb%PH0;|rG(R@umXqIQ_t)3#1%CwpU(>)pVIcQawVO0@ zKb+t5 zSKXAiU#ROD0%@Kp$e|hPs*`|x{mhAgG;O!eP@XNw+Mun&e(N)KX*aC?%Yc-`CfmVG za>{DQSBJdXR~YpW9psrj)U)!C+`sRsgX%aVlrMFz4(mu89QY__9J#XIW7E=!H?%%X z=fgd;lQUtIL({iPIfXk6r_dJkGqOb@^{D_hta5u*xM+WRw6gp4-gdiX%zrWrPX#0x zZ@S@X*b+}WQU(Zmp-mkd`H4<>(OcDxthy>R>z2X%RBr!B#)Qf_Mi9oS+cwo}-(+F% z=$|0bU+1C&z5ddgpK87BqJgo-_sQP?@9Sl5*u9M%E___A|DJaJs`+y{w3xR}ypR<- z3AfmF=05IJ_UGW-X8(}u#1=cWMMcVT3~moH-oxh@KgarG^!*)|3a*Q{uJ+&vI`y^s zi#?7PAxR~_K+v0Im=lfOXlHL%X?36BWZt)tFCblq@2~&Z_;0eh{%1I4x0Bpf_k?D) z*jY8d$x8HZ9msWD@VcGqzoWDZF-I)9OC=I&sU6eKz{& z(|N;VR?AEyYf`qL@RZ;8iN^ODE1X}j{hhzcps7Tx}vFZpHgvp%^LIl(t%VJ+*g6U zUW5KQdAJMtHu?GZ(J{_)`tzy2J>+9Zb$oC1O>zn7)Lk?=UhaQP1_wN!l^+v79(}5^ zgc;9w+Qo*OET~@JV_z3^iAzVdu+lcn3-zBH{sKc9!ZD5LT^eJY-d=gV(eKfKywVXE zkpEX2<{cWY?^b%iP4F<>jW#qIv#+)}SpR~{Q}Lee^m&srR=-Mj2j~BY&SPg;owiO| zr{2}MD}79Z(SHE)59k~^@k`_!L#DC`t;<9Bf!oP<;frq5g+VLJs&n#6 zG{0V?rJaybXOV+0D!il>&%d@o)4;5^LuJ~>MF~QLw%s^t`>FlPyutY_JN;7UZ676c z2ko(antteY;S(+v0QO<~V}9<`ey1M$xBMN8{xeT2aUs4^5%s?dAM{md%>c5rn;zJt2F{`8-Ha;5T@zRK{ zQwMIvyLxxK=_fRDKOg7`ZhwU>juglKkWTa0&_01ryes!x;QZZmzbX93w^_y0X#Gl( z;Ct_%yGnys$6d%OeWa(ehv>8WyEMptKiMz3*{S8pY7jja{u)NMj>(Uci#Sy3 z=RA`eomU-h^nz0ts^ahBsCB61?W#6BwG*9ry@s@>@wLreg}Gw@Sw)f2KJrTWwVAYa zkW92~;GT@K(DuQ5Jj!WLcOX??jMd;?${F8-tk#|Ip(CpNR~yvpRR}{9BRquLV@XoJ z!m6{lv%*tk<23f<4ylfbtBxQ>T_L}cR&{ZrD`I6_f=cp)&9*lJqJo zO<3fcRF5m?WOqm=g76I5%nN*-dD}$Viuy)AsZZnM%Bos@Ls!!D@78-JhK>{9vO_?; z&#xnfT=J){rk!iFjveQn6}b)`oXt&Ac+872BXT-_cv2Q7?UDOG>Du;XXujE#iM2X) z-YOZ_|4aY5g5vkcJ1g{2MmToC%o%Td#Q2)}koN~yK4~8s+&4xYxTColI|w5Hi*Q ze}~3-j|T3cIosgh;&i@`^YoeeTg3gyf+sewx@`@gtI>aQ77e@y&aEon(R9XYL%;nO zqr;(Z9UR=wx7u~o`Po6@sD}xDB60rc%8w+_z1IO1%RAR-c#psM0P-RDJbaRDT7H5dCiZ(khfcN`DCe+6ZOd??}#T&JP=Oz&~Qs7n?2OVWBw-yL&#v1A#7*y z$qIeiH)Kz!ANo`^qH<0su37EqlWPc;J_SeQ1b-El(R6XE5yh`KSk<>vuF;pWBPDP7 z4G%!%!6zJfOj+^BRezmr%p3Q=wg$K8_b{gXl?lnVFxo@fi+u-}c|%LP9(LHy(uYiA zM<$a*b-0nYOgdZj*!Nv&JKmTFZB2)5Jbeh7V~+HE{#n~@KvN!YReyo;NWDJtQ{Npf zC-MD_@;GBi9)7c*e@xIHKOq|Uv9iD~(Gz}btGvHy?rXxDQP1>XHq&+wOJjKCgFG>Nn!E>V?nFmk-dOGu!Uim=&|DVOPcIpNDTiWtDBC1@G!Q zPMqyEPtKqJHEAwiHFLYpRl3jIB9A?;n)~%L2bH`=2d_F&7hO8rteoqh?fl@vfDQ)= zowd>YLx1QatsV!sIA?n?2%UBKk@K)wUa7<|1 zPHfSfz>I9ksdJDGR@}iX26E-Ac<$*T!m4*)Y10iCBh;6BAT5n!OE;f^+KiUxDNwl+ zdR4?_UZsWCwm9%Rc9h?+_kQ4Fr|s81K>M}b=K!1IhjI8bk+p5xrs-q7!cQ{yTz!K2 zhrXOo8Sy+PM?Sj2*-o>IC64}=|;AhPKnQF90SyMpwQ=!A2DWmxB0t%;hgEhMq3)E1RLQm;W<`-^iORt@c7j^ z8?GQKolwUGTnF)mYK+=SU~H;t2yW5&LGkcFAM2i}7FA#o=P z4my-669V(oNJI5PvuHW|RvLKg#=$T|%K$Bf6opI<#z_~fFUn@&b84PwbR(AmN_lwA zD+MDkj3YJlf*y7LOJtLE1t!1}wE6<#mw4&Hk;tlFq-7z0^YA_i2D#6c3A;0~7_`Wb z^4AV!_Ca}d?!cOE8Dv=FtH8>?;!laMMP6A{Q`*RCB`2PHDc>GL@Xun$D!0VveKQyE ztNyHf(JlMBl3sZftB&yG=7>EV(gslKZPN7q@|pRxDCW_&<9(NB-Y?_O~-RU;7cd zoG{o>?elT^zk-h)InU8tWZZCq{wfYVKa>Km&$S)LK8D5XJuV%vp!%QOw<`@7R#C*NwA$&cI7rNyc5T|QFuq!$& z19>cWmVTWrsx0BPWZ+tk2BnyUkS z%BL#xAUp(fFkXq)4Og~55kzI|$}@d!Cf`g{%#UaWQ9^AI!oNjN{6C9U=GE4eWe6pB z76nyA*t(CML-DGAVLcHw??A(m+{d445^{>lKY2umHymKJI>md{sb#VWecmVUx%@*` zx$r2n@(p_OGA_NmBVCpx5jKx(hY#hsug1BQjm?B<<2p>+I?2e5Fn!CoZC2vYHH}6l za>_!FW0p_;wk_M2a;90qpZ}ETuSPqz{nJk_;-oKlj)pdu{sS)C^z^rfIPdOkJ;M3> z4T^l!iZDY7GH;WI2i9U2!9DulKPSwaK7=~s(u@i@%tp8#k`tfj;^HZXjz8P#=zfQI zUK1vqD{~ES3aGX6`1eR@%rrip|fkiZT0+Gkuz%e z)M-Zd(cu7hm3(vh-4(u$a;(%n2xmIZ7Lj~b)`MD19)H`eAti`x3heC=-l~X z7&?1?TU}dxUQT{G(fsTy?L%iaxApCiI((h1w&;k_W1ZxSbVRb!C!GOKe)6(B*|v7# zA+zm{eA2)W5>WJzAv+;E3aJw>6RzyQI^D)e^acH zg{BVC>dCzzzS^|1hEclwgD~?KDX51!D?VudiATtG8fPxAH<7w6t0D^atCSb%ArXe;GRY)&@T^rK8Un@B?t*v(3ji;Y$AU|3`?v`;I&I7Zw1Sj6?TC z``g+BeSTjZyIra0+vO z7&z-&FnQg?wxC+aUde|bJcwj_J+d2@s-8vukj@tXw+>P-Kyki85!whEuBybvsgDL& zo)s7VBbt+q>rI@dTTga~TR2Mk^tsn*%zi80uh!)^(Ot39A@kFNI8r}9`yPC5iTO)p z4rsm#cb|LY$7TF(yUsG7W+a_2%CU9rSDs+YiroV-uJF&(d41;V^X_C$K5@9nd%*62 zPL$5>VKtwxIq*Cr?YrO(8gYb#`3jn=W*wKA$PZ7xAx|;~al||DQ0@-M8KAMzEk#%O z?k42@TQFaCs^x9jl+L^R@G`}*eVQJbwSl2pf zxmFS5`+0_59V$8vW+o#)(kbGwBf%nHXI`h)d<_#P4Eso@Euo~7NqIu|UJS@L8Ad!4 zzYz~F%(>^_YL|IO97OV!z*U)CAz#oW8TKnwl_8lbPiVmB& zP$!X9f;2Cb>g>Am&cU{U&v)>((-l2=nO|1mREC3fpI2u4R6WYMTG3epPE6wPow!&= z%H%{Ps~&j^=$(T`3P%I(;CMVbcDrv;EIL z=j7gY{*cFR`4|;6-;Pm7#s;?I%|APAEEMjsxKQ6~$i0OwSLD~o#`nex3?)hDr={Ub zg=y0QI`Fq?|GxvTLw^riSLS8QK{xn5lE&rloy#X2fiIYKlMRm>k+j*?@wH+H!$sFK z@gG$3!XUSbdtr+66$@fR`YMg04Ka&h|0R%rAwx?e0N24}p2Y-RI=~%G(t> zqpz}E<@y|&-~i)RnXBP9koye$YdHSL^u#zO($38Nfft>dpO^KSyO*o<2j3?jaM@BP z%=31fvd`3n4mk4oNWg(cz2be|?iJ@w+8>O0$Y7Gw?eOMfHKhI3H@E6@0LMXVHMZa@SVl6F+!oTH;wS6dO#LW~Q8Rt~f#CIy7vOH;z*t!M4K{{@#lbR~q4azNUub7vg-Gb=y(x+*go&$jD`9 z_G38t%&0Uw9{I>02d*=;LxXov$@6zY%)uZ+crQw0qL6K7bh=%q-t+Hpsb6M!2``!_ zlLXUlrz?|F%IYv*2T96iKI-+9EodUp)MJ7&`9TdQ5Lt1HS035m)x#xg22JWQeB_nU zS^~Ub7`%z2M;2uTWv%Yeg76FSGi}qC9EY>tq@#)qE0{$H;)>OI%#zQ|}(n zNbbltpXL!gLy`4-DwIcty^O2#Oxb79opP+YQK)X~JAjVZ%RSA+k(<)hs)V!?dDqMP zDcDZDiGaIFB)9uGbfyg8yDOuLw7&(XjQMZETOtnB=+K>ZW&1@gPTf!$0R^qAv{~A! z{VDxG<+fMT2xP^s9&v#7RF69H$jSZ(GP^!F@jN~gXCH8tt^P3~mgdAYPMZF%EsiBB z_G4C#AL&zW$AkCFZ`!YOw;r4icpx^QG1?2bGIy1JnepJhb!{Jp_7<|P!qx9Hb>Au1 z>3<@g%g2X0^L>Yn1R1iWPVfOdhJ_>b0j-BEb?W~wGyZ-VnNtmT{s}J~`ZsnDZOqr{ zP=8Nnc%M#tmlf)LoT5(E=U{hnzJEdZ4y$gRX-;I*3;j`;OYq(r1CGz!U9I~Sxyyt) zNuP6H!qMMDK2DX@yDL_=>Dzfc_>xuZZ$?Px^@MppPT@)*qYiwofaA;wg@-uxi%b+A zy-fnA?tr7+8I)xxV2hBg^DVkLH|%q;bC5F+2g?kY+KWu|Q2q><^0-pbS-WbtO(Hu# z!hxNL2bX8~Ce&v^y-YX0I(P6P`%jk286W(ii!TJx#3r8<_zvu|f}gPM#8e@yYMZW( zI_EI*Z!g=eNGOoct8&#-xzuSB2g2I0+N3V=!L5W12Azks|mv7usDqO?1mdM3PKBrZ;op+E;Y zzkOgkwY}39;*fQg;8IR$_7Q@B?Oz1F)O*1%qE ze_1sz<#uWyjkIn0I(7Up4c;GcaqHYKF}+HMyvM5dHsPx{*$2>_VS6(579Gv)^?Sp) zx`k$E6?FWLaQP?=9qpbcTOBBv6g>VRAx_v8_64`+(dp_q&&tu|Y~F1=f9)+RYC2$6 zH-v%*-FznJ&&|r?s<~V3c`ol@Bc437x^cx%nG$!vz{U(p+G_sl1)tYwu-jF4RzT*D zY*yvkPTG_3{hD01K;h2{A9{8dsH5o82J=;(D<9WE?{o~>;Wg@MhM)qa8y4i~ z=7DXg6J(-F8f{Rgnlc%upSs_j-7cBbt3KOmga+@DPbarsJ?Esl{R8J4-L^;hg&~*k zvb&AW3i`sQ@ZBIjX!pp+)%Qim6Y_rm&wctv#+G4gzoo9qJU53=oD+SWwN82;>a;(A z@F7<})>6Ey^L!?|p=Ad$A)x%i@d3RXCI1htsO!`Z(H7B{(WlqIIg0_LtiHM!cW{bk_YAo%Ax#%3kLZ`tz%+Gmk@u_Nos2T`rv{ z_tHVd0m1__d9{ts^%x!pnu|E|9F!rC%=*dC6Y{QvygdD$!DDpHhpw7go%=qky0U_I z(9(f2Ab5~20}`QS=|Gef40**Cd4RlDHwGgI!Z>#F&q^7)9qgTOxCI}lqm5%=P}X=J zA;97QA31R0LDsypx>BcU%b-o@oGZeSe+KfQ8x9z@D_g-MK7w7E@@Ig04_%7X39aI> zqli7(L;PiJ*u1pA_BJffp-`UWJ?gmWL%jPqM7GRU!kQ`Z>C^^K?F@d@VapdstGxHm zd)9krTwe7Jn(|YJIOPEKrvl`!oN=zSv+~LZU7UHP9ojx@cgorJjAKJ=5odcE`H-R! zLZh$sQGofb^u$NL)nC1~%e;1GKU1%LLpuTUbCR8Y%NUVeG5X&{`kp_e@<7LKIgk@F zi6^}D7sxQ#^>-sKTNd?U#Q3pr!yBksb1(-yI;pV z0-rQ-<@`lULHw5h9r;UV{v|5zq==5-C4AjAVB@rjCOk&_-*UZYX9MzeC7jePI$~D0 zBBkk^2|lCKzD1|MgX8oS6gzQ#pO|~)`|G~)=0Ku@e+W)T>*_uO2Q*jV`T822ECU)7 z196X8xo4FQzY~CbxMt|;;Hd#N2?q&J&Sq5wBB85f@{MzLwQUmHGB}f01`~L*qOv^b z&H$%PUdq_<%~M^1d??EEa_v05!($cPh<`;iqnV#unzh&6ZlSH-|B7D&8bXFK0Eps{2EOe z2cizugPK2w3LcE18WGrk?nhx*zo- zPWjw>bwwG7=0|j`>%^69J0l*)P9E6~mt7t5cmdfh5~ogZnGBFlogt?^Y4??}t->3} zYu~6`WmJQswm-Q_&!Z~aGyU90X?b-MowiAB@=cS6^g~~5eZZJuU%yZMb#V57x7a;w zdk<&r;Z@;};Qjg1Kj7fqdcUX~p?%)_;V+>1t7!Ys)wp<_v<~}2X}01BI>`hV2?f>uQDsGwsF4$&|+aZZZAN|z+;+rbzzyA-W{faI} z9=^_c8ApB-T~6Q~7v#B1zi@02@?k0Z|7+;^3?9K%xf6U3!1;5~?z;J#69LNxI35-J zCVN-tI`TT}qt*$GI&Ra7i8d{tt9Nk)X{5XQ?zq@=*P+W= zVyF2f9o^HU*X+!6VjcPwoTLuhF(wDz?5N=L%Sd!W{hM3k4m|#ZYd)juJ!G5CgKU@o zEgkGfT%%X{JpC?{f=m)ndWX(0&S%)_aJFw|&fx3V9Vc7~#IZxmM`W-ghwp~1{%V^} zI0x~-IUw74od@K^z4{zbxZ;={FlEpo6GRsgb@(h$dy}7K!#)Qz^UcZ#S@Q{~!^sI@ z^f*9CkQZAsV4K!Xcru{NKpn*qQ!Z)QMS#B{*i9e2u~URFbQsM*ZvMu%;7L7?bc5QL zN-453OFDXn&WT?0(B5XY$j8P#{FFZ}BL66l@~Ip18u>*`p7M>T?IQWDd@?sp*m{rN zfsg)C-;6Z&pkIJ9gvSp zxzKH}<^w|ADEN(g{%?Jxj4=i|?La1zTjF_zlt*LHqh0Xj zc{=rB_UcX_ats*#$X_wLWZ$qq(649eKydrpx{^ z{K_@J4SWgTNDa)!+D>3caK`Iw_~+I;SDrmFqZT}!`jAfQ%sr=H>6pH{;t#U?ijMyl zw2yqQPUm&I?l(MFdzDL`2Y*G#Dqirt@EnXIFCAul9eL*U7nbr@;B$2@bgNN5!AXVu z4&vnro>_f(96Vj!yMn;}Y~^G3x)q*5TDt`XZCCMG#W+x5cMi#spDXpO_8qJkK!k45 zJ|ykHsa#G3kHOl3QXP3`7rKLDo<*WNj@ojlOL^ka0beE+1DBN$xQtGNmsG@d97SoB zQ}=q2OdbPQl04#@-VdqT9l0ewKnEW1K3m_0(ii#ML;1m_9ndy(dOBUthJ@GlIr7tv zOzh;h?U^1rjprVz&xhO&j(-@%A^{Vw}1B+Q+36p5?SZC~F=-;?ONZY(~D1 zmelJHt>jrb{W)z|eQ$&NJ~Xz_qyMkA_(jWy{)hCv`-}&U4?dE!pZIRMAB6c=l;Jiy zb>@Nx;gWd_2rAFsZwxJS#m;p^+|buX1SbO@F)+M>vwgq{J#vPE$v%(+-f2 zKL}&~I&4?!89Ru_;mX9|aY?=C6S8_IKL;EKtqhFB)#2(R&)d=C?t!cBIAxtT&ONJe zqREeetg98%(T99iHG{|c>IY&nKw_Mem9Er$9+}g?)LTEy5eK4e+Jt?oj!o#!U=J>% zlX)4Jfh*-0={N&%@XSG6zRD76PtpW}GLa+R)o*cYxXMJ(f9h*uMvB<<>GXC;Cb)td z@g&ZQ50$m&Ne){qb-byad9_UYypv%eSVe*|sec>OsM`=IgZFgjqK#y-Fz|KAZLhOT zQLz1so!%QJbdPP9PUun*guPGRe|faQdktQja^Hrbxwp0{d35^fqD|R%GBH*^c%N-& zm8?!>hfK<;{o>>S9|up=Fq2VqrSI5onV3J~((X(Um>4_J&Ig*3gZ?{{_VU;&KdqWs8beq zAbbw~h)Ioyc^ur#D?c8?1SZeX9q=4U2-O|?NXA*1p*qnK8|CvVG)=jL4Dyz7OKt|E zBjn;C#!<^;y$oJ?T&b;asuNt9&WfWn^#YH&s=l~YG_*3T1mdyHe+#UvmU}v-!LyTw z9`Su03qCrO^&SwUzO8@ni)}Id!3a+bWm+!b!q; z`lb`)^bh(^@|H&1R;7@MjBOvB&R#n*!NAdw$kFu><(yeHUyPW&hE{emm2bJB%6annZ&-n7Zw7ZWF8b>LDR z3%^Y;|8G0bZ?k--jkx@>&l(j858C4$0Bu#$rxj5+Rv~AS!$`?Jhmql0V zMx6x^$%21z^zhm?IvI?!HV-e$K${F87hU|*Zu8d6IAGg6dNSdZhVIsbp=~vNkTw~- z3Mrp5nW)N7ewh%k>*Z@|I`kYuRd)2X@O%o1Jp81q+v=WG$s;oO^J!NL$Mqh8`u(95 zK_`EQ_)YrYAKb|zEzgG%0iV?A%>6#QpMCw2a+KX?>mLx6&v6ElqS1~Q0aMSrO zvOWuQhV6F#H9D{0!MVrwSn`Z>#t~npQ|Chz3`|!TT>Px7uY|q!5Ac!pt5bfs;tG?3 z3pm+4OGl?pEYIOpM~BzJ=am}dZmWyI$H6Gi*vZH3dk3bhIQ}L1c!16ANAP~1I;#+L z#t}QnVMlNdnB?zC{l`30>`%RBMF!15Tj!r$2nII?7q|C?$4o-Z5?zrL1|OD*qBOFxHLc*{`MgymE5h|m0NS6rE-S!Z>K@^Y^-@K@e@Jj&E( zK5@*8&iwe!a46{~%cZj=ma78vZIv#Vp)`-jS1>^Ti-`U?mRuBAGsy& zfl&LIlV|OHh$FwkWc$($*r*#%xO=e8UrrO;UVj5!-Cp0YIB4>5H&FT?;C&B*GH%O% zij4)g+l78;#xbPZ?s=8XG7WGna*SHXq|)}nIskh>gunLhsAJu$jc+Z<&3OsmSPe{v zZr`&bxLWoQiyg`XC;ouT`*ggI=(s(AqBZR_4aF6rr-_E6Q#NV%F8Tc@@H+QD;!LmL zAYFN%|Cq%yHuR?y{RoAf-9hU%1BoB2Fn*s<=j>58JkX4vr@W8lho=Lsvf1*)j!X=&IRidnUVB5{=aF^nhO0CpF{><0~^O_AdTyUUD$mV=tu?gHd==z?gWfHk`M8etMxEw%EuW= zH_p|amguvt*80-1llDHKYFnM^(2>g(Bypi-sgv>Ec$wy2uPf!`p^l+jrRdaJNn21x`)JP( zaoBO-)`7YO>CklM`GF%GwySn0(Du6vbV{B5qyL>M!ioDD+7D_6VZZ*CaM<}7vi&Bz zD{d#PzKiaMXHqWY`dxM~0Uh{HhY`M%eW$T<8|V5{2S!rce!7t zzQ!6jPI>sg&=N6oxfX5+;7SIzUqAr<@_~ydx&No6x4MMTW(!rFwsdesAPb_fn~Lg zJy}^Z0J(zKsp`D_*o|9%0R}7IkB__|gQo+Ot7#pohkrWAd0CkPa^Z0TfSq}@jTO00L(swaaUS3I?Eu~tS{#IUZQ;tNXC08$N&c2IgLlh8 zG6Sz^FLirAZsk z|2qzKH?V0rj<@>gD*TBM=I17^n3wTyu?D!&FX0=l0iF76_#e0 z%>mI_KZAp->c4gHI>n@pD<#*dx6_HY<9QO^XX8O3(<^gi9MP27}qVfX?zy}6yq^Jyd&3EJzygJ02I3U;6rCY+WsXPr8Gp;>Aot4Ry&%8*q ztnyf{$XF-UV@O#nfMrTtGWe9Ax-%Zrhb_^m?UvIF!3Rg(Cx3Nu>D1E(aAuh-&?aJo z_XB$6W0M)E$9sq!wi(0JgGxs}#FAHR(-F%jOcTxa<`6tL13`rdk;H6pJg&!=ahiOa`1926;+TkCPU zr1KszlOzXtX*vm4lxgnR6Fw(V$T*quLpOOB7@ZEn4({^gCoIwDU@AVZFCibD@}ReQ zWRRARtN7rw0Tpq4(1Js@p(ve=a&czx2Mj|bH11_xsjTUS@(rDFOxmj5mSNbRmG01I ze%h%1*gxX)^(=J75h6dglr689GU_zHA!j`G2hDs5wmL8(pZr|eMIjnTIPh6T8<&^- zY%jSVIyIb^pEs?%+iF(fJH+GqRfS)4iLcIU4U=MJC0faa@ceo8v;pAUwfaQ}d`&(IC) z%#YCh1)*b=i;2Vs5zTUX?Uo_)#d zYv2VP_FREugQwzMJd`JI&sZ+NM}MY;qY-YY?f*W`=5z{d?)-p`Q}|z^zxEn9N_OA;^beSt*`QVz7DgY@DnZvif++)5;og< zIOqW3O$G(QUx@RWoIm@jQ@@H+_7k!W7+${P{wh4K&V_8FGsyWP64G7WE5zNapzH~hxQSN&PZVq;$YVFr?C zt_-Xhbjdecr0TR>lrP(+)(7@@U@kg`-oz~WR$E>~ zrUMe`O;g8^jRO`n_^NN>p$XBay+*j=c$5)W80nZe@=3X&i&IoNulcNcIHJrlm5~l} z%8Jv|cIzv`@~VU@^_TievRYd(Ri!)t7rYa5?LsE#^;q z$u5C)=DmX_PFaU8Kldjfzcg*ap42I6b)+0Jk$1{qJJ1f>En(>9MHzDv-7-1Rw9Lu? zDI;=u1Y_C7J24Q5yBnq2^SqMg^YF-ncOt997W|&O6K2cf+Zz|)eZXWmb;^YHAr9Uh zy_?wL7vbD8Z~v{+?n(9a`S=H@_$6}Q=sWZWACcW*QjslpZ3QwuSjJP}2-}ZubzZ! z``5w1R2_O(=;CzhI`cYral%^+o(?KLi}kmcb?6Rm{tA?E1-j1M59BaL#RUVR%Yo{QfDg+tF4!8Jk$J_mJq$ya;g zuo)QRbO_E9*BSX9d-+}f9;kzI#sNw* z&b^Bs>lWRuXT{=Nb)w{_EtbFeBvbVP(n8l6Yv2Fh-q|eKaa?!4(VsvABuGM{2*~Dy z7YauZ+A+crUMOM~dIjh)CSsldjTe~3?EDJGE6h#{$4rd98IdE8M9H)*P$CVHAc1al zH@Y#u-~Z+*}zC8FTZ$=~tq+`H! zB9VWv&g~dN>!^>ENbq*wz0Z>Ou>Q_ zN5rO=PTT2R@R<>|F; z-oj~@p`a!Me6s+~c0{hYYA?(4XMCkq98nIzdv9`o%tL)rFX98%Tsv=`5hR|;bV`&T zD>|LP))~pdO=PuWYJBPf54U(`Z3x5k`tou6=9=wld)P57kX5ecd=D=EGZ*+ygOzjY z>@sDzH7)gO31d+IX{&#R=8@+yozz{*c0Rq&S9}-ww_CY)T6@>EwB0S}b<`p@$HkRy zJdS2c`&upF3pKg#x&_7$?ij!;eGas{mqz(2Ph|Bas~4;fo9KIZCsgx)&b5s}yyoG? z@wJ`Jp?607G7qkGE^s^ao-a139C&_K@pn-m8_2W^jq{f-z;S`x6F~r_q(!b0cV5+% z@3G2IFfND-=mOX-kaHM4VBvGg4_y=hbN_i(q7x@w6f1c=Db6AJ00<;HK|tN;LZ&6H z_y$DzFow!j#fxm_2~Y^o$RT)TK^rm{R>`ZN13)lLhk&{SWURZwNk=$z(3)Rf@%*zK z`RLa*{BsRmStpK6n8~^aoZziUVPw3ti6CsbhHVr0CakaAmSMbd>*K1d@Q{vQcp282 zr>*#$G`0dnrAC$?w z%E2!~_VDzc**Ws`!q8a9*6xI6j=1Erj3x)(Om@o5efNGT3khj+Whogg+cf>gD>5sW zJc+9j3Oj=twV@q`G}3dv0{LB2l6dO^t)Cti#Qj2|Yc@Q)ZHb&tnw={){VU+6#2wrZeyY#dw)mwHyC3Rq2e?We$it+4_gM4~L|^I0L{FIY+a_ zOSVO1-nI4;CS&(k!*KnJ-X$*#rbkcVE}qdUw}2=Vd>7OCsYd7V&`ojrn<$B3HR&#< z(+c%EbM7iqoWt1xQvB>WN1>^hwNe~rTqU0!1Gl*4AR82s3xXVa_cXd}VND8PCmxW@`Dp!WXl^y;84a2yUC10=WM;>`vZa`3(NV8b!vh|F(IPOE_ zGTtE9`UMK<7kNM<>KMeJIpKT|cmZ&b^ zsEpRdG?~iVcJ*GhcAW`9ocWBGo}ZV_L1!w&c-xtKm3vHi0a-4~%FexPc_N=>^Q(n% z@>k~J9$5hLWVqZk%djn)%z37)myOCV`5QeknfvzHBx*K%uMeL*M?SM!@6^%f&*#m$ z*kR3=)1Z9}?Hix*IMMNRs!B%L8NzezSJYO&M<153pGLjHwVh>Me=v;O({7P=mo$gi z{ovF4l#xSXhz~vf=hP#s*rC_I&F>EMh8@ZKa7OyvKS_0a#%ZOG$`~1^PuG8z7VwTu z?$6l*;}@d8?g4#}k#{hv}oxmPzy%-Ze zzRsR{fb$_r>#G9GvPz5^PDJ0{&Xe;0e-P?Nl!lV#sXC)<V;kB0SW4;x&y0SN_r7sBJJFuKUW?#nvCsZ%M6KRm-)q6WT;jR* zLGVD7bm-n-lI&>3y8PwuV-@?J-oWpZbx1$-$ccUD`VULNHe!u7)W)@^l? zxA!qI^|IdON>J~Cyt0%sDCJP`1?F`<_J2eK`YA5pt-|2v>US5Xsf_M(?a-iBzT0vh zpjEWBR+s#9H zQLHLN0f9UL2g=F3xJnFdyq~=(c12ofwS`xOWrb|FPThBdwJh% zZ|E{_4F7-}ylj_r6!3GOlmQ<3dLR5Ehh-5hxTfCThbE`(nJoP2rw!#x-gMR)zg!b9 zp8{nw412p<9+FeOZDPHZC1p@f+KfB_a~bJcUEE5Ug_yR9`Kk?lo(OAMrxQv$cm{*@ zUZVrB2Ok%CDytn7-wzLL%6H8v%XjOywkhS?IsVf6G*|Db?`^%i^wp7 zD=_c3tr}nY!SGhA+ZJJi{)!F3-j!8eexEjl&qNFOVomPP*#hI2V|-n*4c2;l|K?sg z<9tc0mu)72yj)r6<;OaQK8I5jQS-H~UD2*mcK-aDVV;u*D4-|^3M<7CTKGI9&Eep3 z0d+1m$m@!DoolUB6ykmqKNod4jZyhR8~!f5{6@Q5;vCL)AFm49i~$-M2Qw2y zNm!qXeXw-3Q1%3XTr4J<=|v3%-Xdz1sB#Pt?= zhFBdzke+z5>7Y^ZXO5UM zBMbEj2%4vO!egkfZD+imf6xU)0r2FvzWg%1AgI$ewnsWXgtI0A&-U;qkJQPqJODE1 zk<%ebArQ-taN1is?g`#(PF`T7ojpq+b%ZW!DU=h&);ITAUeq@%Qy|HdV_5^=;_W>* zpKV|Ue_m5YIw?jOXMGwkX{8B0emWTDflgWd5K&g3Q=UAA4v%F>XU7WKCp?!M{GJfM zMtS?+C*HdFypqqe9Uc?D$aCSymftDYI@h-yeoGtX-Gi$0Qweh zR+P>uB-T1Fce4VWFDSpy+gkJHg8TcV{{jUNiwG}6f6$>HhPiZtab=17nZ{Zqm*My> z{Qr5s0(%FGsBO_m(vuXcRh+z4qE7sxwd2GD3+sJ*GSUhzNb%mxpcsZ*Ab;P$YH!yXjG2CA{ZWq~VmKwm&MU4gNv$R3~&9J8$n z#heep2nY>C?M&ngZpzG@L>BT@JNTX%uM(Fxd<4J&a48_bNwT0C*=&ne|IiusN4zA; z5|AWs!sVMt8wz}x%N2ZDnpU7?r?b)c4B$_jXLNzQf^o`}k7X)9`7%syeGLn!q{Uh% zE&xv50TLanJ>baYLdUeZ0NgXxyTeG%|eN6RsTp7rv& z(Uz>zD}Op$(4-?po}50%z15kPA$#tdWjVdG^fqzibHpTVfamk#b2^MzAV)@2==^!C z@g9Kg)RP}zu|v4>Yj{~FI)qy7m*KTzdm`sHa_!(>gWiLhcHq6qwc(&Eyl+?^u5aKu zA0BY0T{8TR%r1m;*xkhyF!S+WgKaX+-=fz7FVthFNiDSqLL>6%(Z|I z-sH|>3-m8ntoQz9te5ppi}Zz0FI`2*i|lcaDhFTo4_fVA8Ef16Q~mADxmoLy!feOm>~bEfWmji39|Rl0l7mJ{?fn0JU5@EvwvZ#0)j)F2k(#$Pm74fN1USpQThmyp>wTn!2~{8@K(y* zVN%L5b55>P0YjHhydq?Vz>y)=Gk!oHd90IPle4^Z0+f$>$AU+8&+QICBWLy>w|is0 z0FkmANB&sB@Jyh)cwIb~Gd0I7A9KZ(XCKw1FR45k~e zOjZcqIbPT(+t2!t9{#2imfSq)T+mLrFLq)8$QlJnTRB9qFc_@uTCOd_zTHm&n z&UEfoEO6uh0~+``f@Fs@9~_dFPNjLZv=zxkgc(ht4t730i{1sE`#eMPyCe~5E(ZMz z0N(W|ZZFd^*Xq8-&yMx$@OFiKeN}DPt_FR5ue`vVsC3x#?jfHG>U{P-Y|pXj+OXgz5)XC4&Ao^4Xl1wia&z%#VS6bn-Mp${MGQ||Ch;z$b^117P?0eI); z1CZtuILMQEbgyZbfC_a}0cWTi8i$`_#WNQl3zMjTxoxC_CVTMZVO|wC^^N6CebX^e zM#@eW>LI_S*Bd=vMiaOyci(^7guDTA%k{prHYJp{ z32@tH-rMGzrt>-$yZ6;FzHvG`UUnpmGcD5#wj1$wC}O?0dortBvIXRoLlDh+2Kky# z-tsEna**|q(VUgf@Vzz<^|kH-^B(*?_$c+GT;ra6if`vH4LU2co?#DA@>zBJndz?s z@b2o+T6Rn3E@io5UFZAt3+j>iZraX;al7Py0&m};EOg^K_Z82%be$t<`8p%_qpx=o z+n-?VdwGLSMLxBP96jxA_`Cb1x9t63)G=-VcW8Pe;+QN`(x0ydcmO8%QnWw-o%c2F zPP?zZzH(w`IWX>(fex@&-e zN=tqoK%=p;j;})WTkwXp*vHD!tw}0G>H0M31QQmuR zLj#z()h*kw1Po|1bShAxcryfUT~U|H^Zl61 zKd!ZwQ--ohi;GNJ*7vwD&blG1pYoN)&zCxco@c_kk(NVakjK*wrKwRPx1(vXo4&5R z-z#v?qz6!^rTB^3Lw7ozL+X6 zL40^gTbgm@hMqkU1;|nQD4*<+1r#z&Px=7ngv;_TJ{Een(L|weZ3p22%2fp>ZaFM3 zK4|R=%$DPSl7$ngAQ8124{R7)RGBI<+Mz|)w}@@XtVMec{0>a zxz1TY1Tj_}dxCP`k<dem#{8*1VO{_ z3@`G`9yEN*lE39DM|1Ml8DGDYM|?URq~%&!Y(v^Q3-oMb!lq@nf%UI8^nO~7?Wio~ zk-uSRh>m4#yOb=Eiv;hztKPjw2lzf4*3E^PcEW6XX|rIHa4c_V)`>H02htCy*m*qU zxmo4;(!#bKw=Ec#ZJLx5=Q9g!xh#I;S@a#SIh`y28-96rT3czU$JvgA1@}2%Z*+}n z?!EL`yQXgEnY>PWe1c53;c=(s(c0hTzP$#GUxvHm^V#H+&Y~fi0K6La-1wu1;@rqR z33^lu3`6fl;LE8S-|C9@B`-jiPNm_ul{a|V*RW3AhVuc6sRwfDdT0JyEkdQFD+@#A zA;{cEd0s&gYN0z4=zMw>0O6O5G6kGnEu1~NzJQ&FjW@b?5R{8;+% z)si|usp{5PEax8ge60$)M_86l5!Bi8(&T7b;#S_~0#MIBJ;BT}P;!1KCWqYokZXs} zk;S3|lB8?N`=zYVLmO*eKvAi4E%*z*=|~V2NBuIS3|;bBCl$Z^4Ev+gA<(5GK%H{t zuE42Wrqh-V%jZmSIv|2wfKlP#n~nwTk@hqnb<8k2d0j8ybc!y+*_O?4aykNZx|BV0 z<@Ubrb8WjKiyZ`i)d|YHJbZG%kM~GWv`+W|Rr3jQ%5Qo8l+W_?dsk`3+itcWuFB4b zb&OXA?r-MO?a(1tt@uxSktb!_20SmZ&TVh_X0)OB>J#t%bF@R>H5mKk@ub24x%J>D zcsmci$Tmy2EHq|oESJHTHoFiW+uXWWDcTw63@?Nt3<&eRJYq}c2ayY%_!)Gba z-XpJlmui|UfNuNRM*eVR$s5j4J#DV@#5iU67@3p5(a%!p9Kh#>_l)PpH7=XTdE5Ka zTw~+aB2b^^(5s=&oX9l_3UrV3brj+Tim{(Jyr)9OUFYX$!3N4yOUk2SOS!F5 z21(iHD_}eTjP{V-v#T*uvQK^kK2H!JtCoq^nNNANuA(dX6YQ3eA^HtJE9g;-7qG+v zTF|;MZiQZnUtNnOO}d~{AXipufCt)ujUxjJLw(KTaKA(9(#F!oH=H46!OIFEgrL3j zYku#}WN?prrV5PzBR|?qeu8*F6EEeVtOIWP(4-Rp&n)-3oG&Ho~1&uPN!$3Issy(Dp zUUd{5yi8?qKa_8oXTa@;S#d5M&W=j*OHcjML9#&VCYWa}fV2X*&w;$TOxx3Tjzp}% z*YXX7_Poe9(vMzkM44D3%}BO9tGqd((Z0W%{mvK1)N|W+S%pom{<5@>WsF$Y5SAU z&#V57HtL7oA+voZ@NR8)K0NN&_nLI@b|t%W-fOmna#6+#Ww=$+w9KDVUuk?UoiD$H%+lJa)cK8a z>p6U^2s)k5o*;gmpNrpWA!$9!zEisThI!sMd~VXF|!y-&a|Pi4yw{#SGc zHUM?^1go^m?Ne7jh9MvXzy%Azk33rF4()oBryKHJ9liWReuAnNokPILzx*~~+YI_F zIKwl4EJDj7oFR3B0eX2;XMrf7QvgL=tVZ&<@_y-G_~Dj+H1jPZL;29hIu~dG-(`SK zAQof;(9|V+;2VG# z8UfsnO`a9nHO_lLTAa@e*8z3PcLYSBPkF>k>d<;RGiESCk9Kg>!^4F#8bZ5ksVi@G zCUBW8iRL9fWk7%UG4!;X7PoPc4_T!3=TWQv+X15fuIX_5 zq^suF@U_NsfAMmPoApCRPf7G(r7sA-0Uz5g4qgIuS?ICEl?msFR^BJlmCe=Ky$fBI|UsZ)lx-zA;=8BKsT`_ai4-~#9bWMz((OtcI9o+$SZxv&Fa zr^F6}@@n;4XY$yFTKfUrdyNJF%ZP*Z6U-V=We%M-%BX@55P8$lAuiJe@T{$7);n>4 ziXEHGYts$^bnC>=&WFxXj^SE*?=59!J0TsmTCC{x3U#;RV&_XIP}`aD6gTta{B+vk zfV!=758OWH>2FTy2;2_iXD0+{wuNUJIE~Y9OWP!mYcz5u0y5rrl)$#e4bK&PT&U@Y z!5;Dp==Y$hPUz49Apa0Y{s1=gKCfEiA5xbco*#E~2&^^CE^zy<6o9Aj6aJ*n);03_ zTsU%8j-)uF20*6{uCljd`R~YYSv{yjI)VAD+0MbgFU{m$h88&Kr8E6GP|pj_3tTUF z|MK_4=|?rtB4wlHAtuy4rTt5 za8?bYJb!|ccDV7nWfo8TC{QbA8vZZ%tpWy{0Ic)&S-?fImYn;HWBoPjGk{Azw`mTR>a}AP?vmm}N--K?%CQJ43+m&yX>`N?!1@gCHvyH&!|2 zn@{js{tP<7SN_Oy0N@^Ay{&&A8m;T^5e*Q@CsP2t%dWP!9yy*A*6k^UH&JO|-`Yrh@fJrwMf-D#U$@;F7a4b2y%JIwBP1*E${JqNHh0(YG}A3?iG z`)tyd>A<$MfHrx3ejQaB)(jA=g=(KeYlrqPl#4p7QD5_YAWh>#@K{jaf^N&k!}*@w zF(40qf6vb(yoIi0+@Kw|Y2VDbo8^!!jpxFQQ{)+K0Uxx?}*5B?@ z#vt4!B`9$B+M+B2<8DZSGV869WIEw53w%`6$fEVqD;0;*JF(7W<@7p1u?Ff#Y+ys}ohfbI~iz~i-`8jF;$$9Ssn3FcFng82%+0~EXq z#bVW=3<+RdR#6G(9jAn0%=?X0qw!Fl05P&KLT{0;%n6CyFx6 zllBnI@57I_bw#^v>ejN92Yse1xD?b^=}1WHltbp*sguvdD&^{Skk#W`yQY4-VQ)-PKZ@9 zw+!RoM{zyimjg6VDq}a~?G%OzOCGM^O(%hTH^>+(9R=%|*BR<;3R9(e55=bi|0QA1 zs8>Oj;940fTb&9u$`jvJ$5$Mp1w1bC+~B#4NtAG^?T^~R;d}vwla`-*~M;-w2^fi!|%T9tg@$L zu>3LQ0p=OnCtX@QA-WaHj(oIRj=e=j!9Oegk=;3HJ09sISXb+BgTlv=13L|6ol=EN zcVxvn@v+8{+o=QF%=vNUa;V(4ldp@}Op8TM+#XDFEUhx*Qiw-kK|S z%b7steOQOa17GByP89b~YkrS3T~0`hptJe$_uzk*_*k!vCWO%VjC!h}^W;8{ZcCF^ z=QEvWo45cc<=r%%Ux9Qz``Nl?I(#ahJjSl_ReZfq6csm!Hw22ZB^RM>$InhmtvPY- z{Y_-}8NbP$*A{3m#d#Hfl6zFX>8AsSE|+fVoNurMwAm_oV9tYSRPi`0gv0_SP&pe!!h; zfxhBRc3V-tsqQmh&%HjfIt8Wye= z>IK0Z5EV$5f5(sfmSNn=C;Z@@q4M95|9_M3|B(J$ept2_@Rxsq|F8T2(p7+Q>DTzg zM;_{G2Oz+U%v#NMYBIkM(Ah4ogx5Jb5NBQdX=&3A0=@h2W6dL5Iun$Wtz@J-cW=F) zemwixIax)vC$=F!htmDo)`GWS?iuHCwkOY=GG$bSNblVxEt+M{xv{r^6dQ zBT!!kn69F5ufqRt0SOhRO7$-EHzkrOKNV4FqNKO95A<=d(&2Ftko)p7poRk52Y^;l zVo|nD;bvttX_?cdT!+*ha(C!Fbd;l|x5`h;RNy?gO1i)WuxWK`jb`W@r7jTW``+Xi z*xa`s8ae>6Tm?R@M?fuezk-+7$df~Opxg5uMWz)x2iZ~uUpoy{XJ%y!=G4i#`zN%; ziogpfx;;xv*lQQ)l_l+QqYk@YpfjM{kLY+f6;X4@SIFmx$dmtz_hZtyjAai006FPd z^-o=6xg%rd;*miM*}dzTrhx9kzmfJm;;3gnFN@Hz&XpHA@@)McAAonr+-VB|*=>ng zIEUPx6<}TM5LjLo4%*KA0^+16EN^JMHy$YD{&5$&Wdw!x^PUUr4l8G>0eTM^TKggN zzvVU{`)lx`Y4T0-ZNj!io*m+JclqV{qKrIS(0opr@~r1S4>|35llUPnxo3a%toyEv z-LROg=Qkp%{C)J8l{6>0|BigVbHCySXC=OMd6v%(e-G*>v76G*L<{hcPwu5?feXw_ z`dj3BYuxanm(|$8Rj_>C`?$JR8TnIT`P0?4;i6Nq^vb2;rRn2)Fn4HE3)~^ht0?pw z-HNgaXq&Nf4soh*%PKc17FCm}NTWRAp|;)tq&>bf00O8Fi{cuwfa0oYkMq=b$lc-g z48fZZWgTl1g{TFoC7dC6hP6F8EW_=9Sgcy(015$i={JN0uO^!odMsz;T5eYAApp4X zF96sl@RYw`j&t~3OH}LKZF2|E#c~ypj8|4b(S=?PKf8k6J?>5m*cp&^jUn`m7GQ}# zMvjAz06ai+{R(Nyd!0P^u6VZd@(`e3`d5OqfdFrvwf0RDSS5RkUmc=DWVVh=zk?TG zy+Vh4=V32)RJ>~}tFEOl+D1C#-$km}1n~evBg+0=lyVO3Fb$=%$n}=F%II!aU@64PmyQQY z`z8uq1+68mH5{NOpS}yE1Q%u51Nc_3(p@NJCG>RZ2;j>?;BobNcmNuHp)04W5!MCV zk=hl+P8FCYtuTE5oi1whZ^YA*twmaB3VxURv=IrAJb3eX(_kWK-({q2(A$#0kN zKD6#%cK`H6z|)n|Pj4U-V4gGYDchlPt!lUBc}(sm`PS%s>>y_r%W<8z#wcI0#HT3K^;J$&q}JfQ({KECyUj~xjYsO8i>@_Qpcse;;d(5e$6RveIoA=75dO)03{5o~I zW~ZvP?+xg8`Pl*3=l;3P&HM8P*ABP4NKI!)@xA8XOw8=>7M{;qJ!3xa1=Laq%%8XP zO3N@jorFAx(D=;%96_&9-arGN4@s+QdFdcc=cz9v3CH}IXMr2u{iIXgmPGor;{sRo zb2?%H-4t~uTHuJgekNp_g!rynpe%Ix*9-eX%0e&90ea80l#$9x=XJnb?Ta#h4PdUWv z9C{a&J@PZd@c0hTZwq7qr9-qut3{ZhZ}L3Fy7W8QT9>8^_JUYet^;xo>1Ozzx@8L) z>9rSJ@XA6o@?WB?oD+)Y7CYxNMW&rM04CZ(t2V3iX^&0H5&X2^v%(tL9@17BG8ZIu z0QCBG@^Zb49P9A-46uC@-wuMb8_2W`|7@d!$74DJtMFX`{C4lnIuu_~zYTu6!?*dP z%uFFzcjPp@3V6FR-?IFK?ULUlQ3p7En-E54q`bV#X3?|Oa}kC@MOj7>BC z>pH7Kz5?jH|1GXqRa#w7{<&#^_R^dic_%?%L<=l>Vf(mm0`lg6dI3$LqCZLjDZEPW zIi-aXt)nXi+$$MflTDPR_0}!QAGZzeaI46GaDIn7<8#@HfAsZK92QXqq zGTdBq^Uj~kKZnEsGW);5Dztw5GVDpZpyFQXE3|_{!>+WyLR;(t@B$RUSjJbN5!75T z=)x@LignKC+Bw)ou2stNqa)H?CjOED#}B;Dbr$te&IYo$9zd&GpcVMc<3c&uXXL`E z1sC^uLS9BGsMqRSB+>!Ms&>lR{~_t{*NPTo1$sL-&b_bDhOR`)zoxe}px98NLs+#wh!jC~_BEY4L56?uuufL)dPM(`m780tWlgImVu0c|gdpBpdL^ zFfTlkUpmUPV<1QfRt`aXsL38cj{rK)85t1u1~Nd;QsD6sQ3N!5&$!3 z?L@g9&`)|^K~8Q%dJ%%Bhw*4J3)WirkDy=Ueyz}^yOinK=z?=TCyMWoxSgeoSl&&$N8L7PKY=_8-QaT(nuoN< z2JPjDMlB%B{&MnMw0!(H%5qQo9<+9pyn!x4^ZB!vI{J#MD5^d8{uocO-vr2e0GG|!os>SNpRUhr3)I7YW(AxC`xCdo0L(9< zT$R#sPUZOyN?d*HoU+56ZvsfKp}Bs_PhgJn?3k1VUQxM*?&tgj_78ccwYaP(DN9kD=(>j zv+!fU&1sB>{OljBQ#Th9T8YPYT!?p3oI~t(9NbD4E0}Wj7yz(Jr0@w@=w$;(I>?@N&JSY~1)cD?0rioGa|J{P*z8w-JzEHwNiE%h2iK zK_{u$c}OoFdx-2uS|SLMmre6IkY4(kd=f_ORS%(d8^BiGjw6-+!UT&v$jU9M`^!uK3_t?^xGT&$*5 z?@^%-p%q+o@AGr+{0ro882&nG4gKSSq`J^CJTw2d7v z>zAQ*Xq~rTvx9-$%HkqF+kG8g)=vxEcozq{CD6`7@#Nxo&~( z={VQ*n1uf>TA)`1@>dy*<4#p>r-%j~TRwX#c${X6hE)dh@OLoXq+mvRpbRMWj78g=pra!nIXlR>Q>y+~V;9cYA$!mh8E`Uy* z)3KqQoMu>WU>_Rlo%>At>YP@&g1j4)@da|d0llXI3htjmvx$7J4{)@=&Va}B+DVCp z4&XZ)k`4_t-V=WwzM~V^1M=|Lv|Q32@N>2Q25BA_Ti+|M_Vw8ryLzs+MrotVnKj-$ z1Jwa-&*<>1cl;N{4aq(K##VaW_`R$6aatdrjP1|-N2T?2Q}|rBK>skF>xxXmzn~U~ z;!ub1Xs7_jisaiC^BhmhDh0Yox%LHyPQXJkk7|uopSh0}p4B*Nt71U#)14vJ??mq``5fO=z&{cLiiX%>z=28bjYtu&9^* z9XdJy&T(hN06D*+GvK~!EntVFuk-Vq{A!l~e3Q4pIG~O^kA5)g$Ozm5s2z)gHgqod zYMt)^=vmx|Eb^+)dFH55cx=+%mytiy5wzE9$gzhN?)8f8Xq|8u0sejBT*Q`Xfk(s% z{G>Ue;^IN~mv7L4adgG}j=UUDem>q4E=(^VXMwHHK^=2?i#9D+&q_Tz#zT6nvx$z$ z_uR7%`4DH`llV7!ytw{dl#i~D^Sa^tgs*DV zQ~o2743NBnCG1u!zy9Tr{58PvF}!m)3}Iz)D_Hh`(;4wvV7Bui5PNK_GP~E_Rd4y$ zH`lSwX>D<*}68J^8K)v|#?O*;reo>h{ZJ*xfE1Ev8ryYj}dJh#T zkY1#i$7yvfXzFePVggkb-T?%z^v5r5_j5CW(U!#@BlT_|Qb4*)Dbf}?d`?yuhkq@h~}Xm(DKe<|xCifQ-LMyerGKsxR3=pgh}U7;bNjAKC%Ht<#ZkmAOvq+?DRi z_XxhZhVM4{1zLX|q~jqi);HEaWx4h2iX9L-2hPPi9Uw?MRgt-R!JatxiOVAg%mC0@ z>aJ$bqCCYA^%h@pi(F$(kqBQ zyhxTxUzB!KT$N+`T>et9rGWLfx@UQ8p{D=$u_zJRn@N}qLx8_hhaam*st!2Ie_^;8)aN(ebW&B8R*Hw#u0LG`Vu@ zTdRxp@Dx9fs@Y$<|Ra1eOMfGePuvhTMa^?b&FYuX|p?(-B) zA19xHCY=B~7cQ7{8laac=>VWdygtT)6+D;T#&_r&fVvDA2DoL+^{1pehrVdYKfFkH z7+hB!jVjM{!MyL(3^FowE$Hg}GMSx;a(>oT5cO0&htQSFEo#=u59(w+K+Ag%x*O2> zVVq0{aQ&E$fpvG)z4d#S`|=Jyr$Jl@_&4xVR^|L(>4? zJi{t|)YJdR4-XmW3j9+v*n>WrP~5!<9sJwFXFKvu74MXccAg^(@rXGN`L8F zVEmN6^c9{e_#L!BDVbimR9qEj+M+nqkBYCny+6>Z_fl>77=e!qvZkA>b6%aYIH zH{t}a%(0{R17;nQqV3C+MpuAdU^vb@qndRV%(CSlU{gcfhHGJ(aXRP5U09~|xh4n# zVvZQtY31AaUF9N0ufw`;8tb}L(eLbGV&O)}$&ymC9QnN5l z>zi_%zt;jDEPB$NYu`ggI~5My+d-*Li{R|mH?3|L`|ZQS18iJdzL*f-B9X^(p^#%y^R!gu_> z6p!Kg4_PnxbDXA#SFZ)y3-sz$e5&yaY=N=zIZEmDbfusaqvHFx6U7@V)6*zeEwsOD zO0YoQg6d>B!vU!g^By>)DOUjQ^-|Q;7+?dpbV|?8a^j9=OE2+jYruIj&E4#}*liw)~>gH?H2V^2h1@wc(S>AJb3Q=e7m zlw#`REAHEf-t$9i%`54x(X(CFSzn+4TVP#&pMFxf&(nvG%cnK$wkh|0JIt(e$XT~{ z4eOSnb@!3G;46otaP53>Ke8a8xu4zn>O;UWd*|Vog=fT-#VxS+ofXSCt*cRn`0y@E zTr1fxgq5p$Ysovj{yqtAfwPn1h)9hz_tIL>@?c~39SDW#ssUXm7=0R1Jo zk!Q9VW96ivi{id@1xm>c%GhC9K;fSNggcG{bOe+Adc;hOb;B&6%Oejzp zho8$tgy87Bu|vu^J+6VEz~|vMSt(C?0eL{RW8{G-{|!r3XIlMcF#xJU;{;Y^Y?rgh zZ~5QX*%kOzROJ0ZTox;%FbEq8Wx&TKQ971X$A z0O!v`a2&C?NZYtY&`yu;G8TS@-|<|;xCPH!_!jAJ>swU(TV$o=Z{_B|TTZFVNdFdGXK}7uQ{*lO-Vg0USFv0>4A; zev$18BM&=_R^-qdIxg--_n?^^-^xAJqW>%MUIg?#`~CsEenKbVF_!$z%S%iD1$yhf z1)a`kM6hp{v&hr^pQiIUJd3bgMBhLA6)yiMDSDnTMgD_@yeohIZdBalzj7@w0s1R< z%l`xwUZm8j*yGZQ<|KbmcAbI_UlpG&iX6qCIa`1MWjN=G&d71NuFLN;_F!fF7+4Fy zdSI;lW3Vrpo~L`J`{PI}Z}0c?#up#MRYv6KItE2od2Ju-SAb`F?|PmU?){p#?q#j? zo{#dlYJafW>AX1K??{9exgTDM1AvzRZLgS}&bz11V@bXN2_q%(x`x5uo z6YTs_+&uxlJrI8T+=FMapP;_ z%k^m$SAOyHy6CEW!BZfw>&m0w=kvPiVYqiyuQ7d3Q{m##=UF6EEzEmPF2NQscEO)# z>H8Vf9YFji0CuefNLkQ4VCuvpUh{z)e5VrJUZ~X{{&WoLbURnXJCC3J@9;jV)6(kU z^V2_1RlSq^%D3qGtF$qXG3_}0sB7grD(_hKqv(#}J;lFNEl_XeOI4|NAy!;9vE4|X{x~CcQ zuC$^p-}qzPTpL-J-i^!e>$at^&5h+Y+69TuqgO|xk;V%jKv{79Pgv8gg5L&!ogeow z838$K2#9|V5dRtRmeJSIu=4hu8RK4%>z~=vJVVFP_h~Okys+uz`mV}&k@8NwzS9AY@kT$jF`hq?=p<;LY3FK=B(mTTjy&WRZP6|YhBEDu)rV}{M0 zE8hXU-I|s|Yyfyyv3rQk-(k6r%am8iQ+%Nr*VCj$ah2sLzuISLn+1fYp)p(0EnqPh zRb`!)elGLqqIenoBKVEiPnTz}1s1&_XHUvWqF1{GR7@{QAuUpJeO%wtRp!Ftq_^17Pon3{5IiL!2DwXzAao-=0V0PV}ak(jq5qiU+Jff?~ALv##R1B zai;h2UVoSF=`E%q+`4$eV!H2AhKcu|yaoP0rxg{3mxJ}500000NkvXXu0mjfV(@_Y literal 0 HcmV?d00001 diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 9c61730..35c434d 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -103,6 +103,7 @@ Hooks.once(`init`, () => { // #region Token Attrs CONFIG.Actor.trackableAttributes.hero = HeroData.trackableAttributes; + // #endregion registerCustomComponents(); Handlebars.registerHelper(helpers); diff --git a/module/hooks/ready.mjs b/module/hooks/ready.mjs index 919f869..c71bc25 100644 --- a/module/hooks/ready.mjs +++ b/module/hooks/ready.mjs @@ -1,3 +1,4 @@ +import { filePath } from "../consts.mjs"; import { Logger } from "../utils/Logger.mjs"; Hooks.once(`ready`, () => { @@ -21,4 +22,15 @@ Hooks.once(`ready`, () => { if (game.settings.get(`ripcrypt`, `showDelveTour`)) { ui.crypt.render({ force: true }); }; + + // MARK: 1-time updates + if (!game.settings.get(`ripcrypt`, `firstLoadFinished`)) { + // Update the turnMarker to be the RipCrypt defaults + const combatConfig = game.settings.get(`core`, `combatTrackerConfig`); + combatConfig.turnMarker.src = filePath(`assets/turn-marker.png`); + combatConfig.turnMarker.animation = `spinPulse`; + game.settings.set(`core`, `combatTrackerConfig`, combatConfig); + } + + game.settings.set(`ripcrypt`, `firstLoadFinished`, true); }); diff --git a/module/settings/metaSettings.mjs b/module/settings/metaSettings.mjs index 24eed63..d0b6798 100644 --- a/module/settings/metaSettings.mjs +++ b/module/settings/metaSettings.mjs @@ -30,4 +30,11 @@ export function registerMetaSettings() { await ui.combat.render({ parts: [ `tracker` ] }); }, }); + + game.settings.register(`ripcrypt`, `firstLoadFinished`, { + scope: `world`, + type: Boolean, + initial: false, + requiresReload: false, + }); }; From ec5ad470f8725aef4b02ef9e2b6035024072322d Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 15 Feb 2025 15:41:48 -0700 Subject: [PATCH 011/164] Apply PR feedback --- module/Apps/sidebar/CombatTracker.mjs | 12 ++++++---- module/documents/combat.mjs | 17 +++++--------- module/documents/combatant.mjs | 32 ++++++++++++++++++--------- module/documents/token.mjs | 13 ++++++++--- module/settings/metaSettings.mjs | 6 +++-- module/utils/distanceBetweenFates.mjs | 4 ++-- 6 files changed, 51 insertions(+), 33 deletions(-) diff --git a/module/Apps/sidebar/CombatTracker.mjs b/module/Apps/sidebar/CombatTracker.mjs index bbef58f..8cffd77 100644 --- a/module/Apps/sidebar/CombatTracker.mjs +++ b/module/Apps/sidebar/CombatTracker.mjs @@ -2,6 +2,10 @@ const { CombatTracker } = foundry.applications.sidebar.tabs; export class RipCryptCombatTracker extends CombatTracker { /** + * Changes the way the combat tracker renders combatant rows to account for + * multiple combatants being in the same combat "group", thus all going at the + * same time. + * * @override */ async _prepareTurnContext(combat, combatant, index) { @@ -14,7 +18,7 @@ export class RipCryptCombatTracker extends CombatTracker { if (groupKey && combat.started) { turn.active ||= combat.combatant?.groupKey === groupKey; if (turn.active && !turn.css.includes(`active`)) { - turn.css += `active`; + turn.css += ` active`; }; }; @@ -26,8 +30,8 @@ export class RipCryptCombatTracker extends CombatTracker { // Purge the combat controls that I don't want to exist because they don't // make sense in the system. - this.element.querySelector(`[data-action="resetAll"]`)?.remove(); - this.element.querySelector(`[data-action="rollNPC"]`)?.remove(); - this.element.querySelector(`[data-action="rollAll"]`)?.remove(); + this.element?.querySelector(`[data-action="resetAll"]`)?.remove(); + this.element?.querySelector(`[data-action="rollNPC"]`)?.remove(); + this.element?.querySelector(`[data-action="rollAll"]`)?.remove(); }; }; diff --git a/module/documents/combat.mjs b/module/documents/combat.mjs index 18cfc48..fa76361 100644 --- a/module/documents/combat.mjs +++ b/module/documents/combat.mjs @@ -44,7 +44,7 @@ export class RipCryptCombat extends Combat { }; async nextTurn() { - if ( this.round === 0 ) {return this.nextRound()} + if (this.round === 0) {return this.nextRound()} const turn = this.turn ?? -1; @@ -61,7 +61,7 @@ export class RipCryptCombat extends Combat { }; // Maybe advance to the next round - if ( (nextTurn === null) || (nextTurn >= this.turns.length) ) {return this.nextRound()} + if ((nextTurn === null) || (nextTurn >= this.turns.length)) {return this.nextRound()} const advanceTime = this.getTimeDelta(this.round, this.turn, this.round, nextTurn); @@ -107,9 +107,12 @@ export class RipCryptCombat extends Combat { }; /** - * Update display of Token combat turn markers. + * Overridden to make it so that there can be multiple tokens with turn markers + * visible at the same time. + * * @protected * @internal + * @override */ _updateTurnMarkers() { if (!canvas.ready) { return }; @@ -133,12 +136,4 @@ export class RipCryptCombat extends Combat { } } } - - async _manageTurnEvents() { - try { - await super._manageTurnEvents(); - } catch { - this._updateTurnMarkers(); - }; - }; }; diff --git a/module/documents/combatant.mjs b/module/documents/combatant.mjs index 21e1ba8..6de765b 100644 --- a/module/documents/combatant.mjs +++ b/module/documents/combatant.mjs @@ -24,11 +24,13 @@ export class RipCryptCombatant extends Combatant { total += distanceBetweenFates(start, end); const whoFirst = game.settings.get(`ripcrypt`, `whoFirst`); - const disposition = this.disposition; - if (disposition === `unknown`) { - total += 0.25; - } else if (whoFirst && whoFirst !== disposition) { - total += 0.5; + if (whoFirst) { + const disposition = this.disposition; + if (disposition === `unknown`) { + total += 0.25; + } else if (whoFirst !== disposition) { + total += 0.5; + }; }; return total; @@ -46,15 +48,23 @@ export class RipCryptCombatant extends Combatant { return `${path}:${disposition}`; }; + /** + * Used to create the turn marker when the combatant is added if they're in + * the group whose turn it is. + * + * @override + */ _onCreate() { - if (this.token) { - this.token._object._refreshTurnMarker(); - }; + this.token?._object?._refreshTurnMarker(); }; + /** + * Used to remove the turn marker when the combatant is removed from combat + * if they had it visible so that it doesn't stick around infinitely. + * + * @override + */ _onDelete() { - if (this.token) { - this.token._object._refreshTurnMarker(); - }; + this.token?._object?._refreshTurnMarker(); }; }; diff --git a/module/documents/token.mjs b/module/documents/token.mjs index 2b66aac..458230e 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -1,6 +1,13 @@ const { TokenTurnMarker } = foundry.canvas.placeables.tokens; export class RipCryptToken extends Token { + /** + * Overridden using a slightly modified implementation in order to make it so + * that the turn marker shows up on tokens if they're in the same group as the + * currently active combatant + * + * @override + */ _refreshTurnMarker() { // Should a Turn Marker be active? const {turnMarker} = this.document; @@ -12,13 +19,13 @@ export class RipCryptToken extends Token { const markerActive = markersEnabled && isTurn && !isDefeated; // Activate a Turn Marker - if ( markerActive ) { - if ( !this.turnMarker ) { + if (markerActive) { + if (!this.turnMarker) { this.turnMarker = this.addChildAt(new TokenTurnMarker(this), 0); }; canvas.tokens.turnMarkers.add(this); this.turnMarker.draw(); - } else if ( this.turnMarker ) { + } else if (this.turnMarker) { canvas.tokens.turnMarkers.delete(this); this.turnMarker.destroy(); this.turnMarker = null; diff --git a/module/settings/metaSettings.mjs b/module/settings/metaSettings.mjs index d0b6798..7694088 100644 --- a/module/settings/metaSettings.mjs +++ b/module/settings/metaSettings.mjs @@ -14,8 +14,10 @@ export function registerMetaSettings() { type: String, config: false, requiresReload: false, - onChange: () => { - ui.crypt.render({ parts: [ `fate` ] }); + onChange: async () => { + await ui.crypt.render({ parts: [ `fate` ] }); + await game.combat.setupTurns(); + await ui.combat.render({ parts: [ `tracker` ] }); }, }); diff --git a/module/utils/distanceBetweenFates.mjs b/module/utils/distanceBetweenFates.mjs index 78a603a..4eaaddd 100644 --- a/module/utils/distanceBetweenFates.mjs +++ b/module/utils/distanceBetweenFates.mjs @@ -4,8 +4,8 @@ import { Logger } from "./Logger.mjs"; const { FatePath } = gameTerms; export function isOppositeFates(a, b) { - return a === FatePath.NORTH && b === FatePath.SOUTH - || a === FatePath.EAST && FatePath.WEST; + return (a === FatePath.NORTH && b === FatePath.SOUTH) + || (a === FatePath.EAST && b === FatePath.WEST); }; export function distanceBetweenFates(start, end) { From 4ccfc03e593112f4f253ef51eb6917c5de15d931 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 16 Feb 2025 11:41:32 -0700 Subject: [PATCH 012/164] RC-110 | Prevent Armour/Shield Equipping based on slots --- langs/en-ca.json | 2 +- module/Apps/ItemSheets/AllItemSheetV1.mjs | 21 +++++++ .../data/Item/{Protector.mjs => Armour.mjs} | 61 +++++++++++++++---- module/data/Item/Shield.mjs | 19 ++++++ module/documents/item.mjs | 2 +- module/hooks/init.mjs | 7 ++- 6 files changed, 94 insertions(+), 18 deletions(-) rename module/data/Item/{Protector.mjs => Armour.mjs} (58%) create mode 100644 module/data/Item/Shield.mjs diff --git a/langs/en-ca.json b/langs/en-ca.json index 737906c..61a77e6 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -135,7 +135,7 @@ }, "notifs": { "error": { - "cannot-equip-not-embedded": "Cannot equip an {itemType} when it isn't within an Actor", + "cannot-equip": "Cannot equip the {itemType}, see console for more details.", "invalid-delta": "The delta for \"{name}\" is not a number, cannot finish processing the action." }, "warn": { diff --git a/module/Apps/ItemSheets/AllItemSheetV1.mjs b/module/Apps/ItemSheets/AllItemSheetV1.mjs index 0744e57..3ac26ba 100644 --- a/module/Apps/ItemSheets/AllItemSheetV1.mjs +++ b/module/Apps/ItemSheets/AllItemSheetV1.mjs @@ -43,6 +43,27 @@ export class AllItemSheetV1 extends GenericAppMixin(HandlebarsApplicationMixin(I Logger.debug(`Context:`, ctx); return ctx; }; + + async _onRender() { + // remove the flag if it exists when we render the sheet + delete this.document?.system?.forceRerender; + }; + + /** + * Used to make it so that items that don't get updated because of the + * _preUpdate hook removing/changing the data submitted, can still get + * re-rendered when the diff is empty. If the document does get updated, + * this rerendering does not happen. + * + * @override + */ + async _processSubmitData(...args) { + await super._processSubmitData(...args); + + if (this.document.system.forceRerender) { + await this.render(false); + }; + }; // #endregion // #region Actions diff --git a/module/data/Item/Protector.mjs b/module/data/Item/Armour.mjs similarity index 58% rename from module/data/Item/Protector.mjs rename to module/data/Item/Armour.mjs index 871c79e..34d964d 100644 --- a/module/data/Item/Protector.mjs +++ b/module/data/Item/Armour.mjs @@ -1,13 +1,14 @@ import { CommonItemData } from "./Common.mjs"; import { gameTerms } from "../../gameTerms.mjs"; import { localizer } from "../../utils/Localizer.mjs"; +import { Logger } from "../../utils/Logger.mjs"; import { requiredInteger } from "../helpers.mjs"; const { fields } = foundry.data; -const { hasProperty, mergeObject } = foundry.utils; +const { hasProperty, diffObject, mergeObject } = foundry.utils; /** Used for Armour and Shields */ -export class ProtectorData extends CommonItemData { +export class ArmourData extends CommonItemData { // MARK: Schema static defineSchema() { return { @@ -45,22 +46,56 @@ export class ProtectorData extends CommonItemData { // #region Lifecycle async _preUpdate(changes, options, user) { + // return false if (options.force && game.settings.get(`ripcrypt`, `devMode`)) { return }; - let valid = super._preUpdate(changes, options, user); - if (hasProperty(changes, `system.equipped`) && !this.parent.isEmbedded) { - ui.notifications.error(localizer( - `RipCrypt.notifs.error.cannot-equip-not-embedded`, - { itemType: `@TYPES.Item.${this.parent.type}` }, - )); - mergeObject( - changes, - { "-=system.equipped": null }, - { inplace: true, performDeletions: true }, + // Ensure changes is a diffed object + const diff = diffObject(this.parent._source, changes); + let valid = await super._preUpdate(changes, options, user); + + if (hasProperty(diff, `system.equipped`) && !this._canEquip()) { + ui.notifications.error( + localizer( + `RipCrypt.notifs.error.cannot-equip`, + { itemType: `@TYPES.Item.${this.parent.type}` }, + ), + { console: false }, ); + + // Don't stop the update, but don't allow changing the equipped status + mergeObject(changes, { + "system.equipped": false, + }); + + // Set a flag so that we can tell the sheet that it needs to rerender + this.forceRerender = true; + }; + + return valid; + }; + + /** Used to tell the preUpdate logic whether or not to prevent the */ + _canEquip() { + const parent = this.parent; + if (!parent.isEmbedded || !(parent.parent instanceof Actor)) { + Logger.error(`Unable to equip item when it's not embedded`); return false; }; - return valid; + + if (this.location.size === 0) { + Logger.error(`Unable to equip an item without any locations`); + return false; + }; + + const slots = parent.parent.system.equippedArmour ?? {}; + Logger.debug(`slots`, slots); + for (const locationTag of this.location) { + if (slots[locationTag.toLowerCase()] != null) { + Logger.error(`Unable to equip multiple items in the same slot`); + return false; + }; + }; + return true; }; // #endregion diff --git a/module/data/Item/Shield.mjs b/module/data/Item/Shield.mjs new file mode 100644 index 0000000..aed37b7 --- /dev/null +++ b/module/data/Item/Shield.mjs @@ -0,0 +1,19 @@ +import { ArmourData } from "./Armour.mjs"; +import { Logger } from "../../utils/Logger.mjs"; + +export class ShieldData extends ArmourData { + _canEquip() { + const parent = this.parent; + if (!parent.isEmbedded || !(parent.parent instanceof Actor)) { + Logger.error(`Unable to equip item when it's not embedded`); + return false; + }; + + const shield = parent.parent.system.equippedShield; + if (shield) { + Logger.error(`Unable to equip multiple shields`); + return false; + }; + return true; + }; +}; diff --git a/module/documents/item.mjs b/module/documents/item.mjs index db7c968..ac1d188 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -2,7 +2,7 @@ export class RipCryptItem extends Item { get quantifiedName() { if (this.system.quantity != null && this.system.quantity !== 1) { return `${this.name} (${this.system.quantity})`; - } + }; return this.name; }; }; diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 35c434d..812c6e2 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -8,10 +8,11 @@ import { RipCryptCombatTracker } from "../Apps/sidebar/CombatTracker.mjs"; // Data Models import { AmmoData } from "../data/Item/Ammo.mjs"; +import { ArmourData } from "../data/Item/Armour.mjs"; import { CraftData } from "../data/Item/Craft.mjs"; import { GoodData } from "../data/Item/Good.mjs"; import { HeroData } from "../data/Actor/Hero.mjs"; -import { ProtectorData } from "../data/Item/Protector.mjs"; +import { ShieldData } from "../data/Item/Shield.mjs"; import { SkillData } from "../data/Item/Skill.mjs"; import { WeaponData } from "../data/Item/Weapon.mjs"; @@ -49,10 +50,10 @@ Hooks.once(`init`, () => { // #region Datamodels CONFIG.Actor.dataModels.hero = HeroData; CONFIG.Item.dataModels.ammo = AmmoData, - CONFIG.Item.dataModels.armour = ProtectorData; + CONFIG.Item.dataModels.armour = ArmourData; CONFIG.Item.dataModels.craft = CraftData; CONFIG.Item.dataModels.good = GoodData; - CONFIG.Item.dataModels.shield = ProtectorData; + CONFIG.Item.dataModels.shield = ShieldData; CONFIG.Item.dataModels.skill = SkillData; CONFIG.Item.dataModels.weapon = WeaponData; // #endregion From 43fe433900cbc77bc4c09fb8ad8f3429a4dee283 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 16 Feb 2025 12:19:56 -0700 Subject: [PATCH 013/164] RC-103 | Add Access to the rest of the item types that require it --- module/data/Item/Ammo.mjs | 19 +++++++++++++++++++ module/data/Item/Armour.mjs | 18 ++++++++++++++++++ module/data/Item/Common.mjs | 9 +++++++++ module/data/Item/Good.mjs | 19 +++++++++++++++++++ module/data/Item/Weapon.mjs | 26 +++++++++++++++++++------- 5 files changed, 84 insertions(+), 7 deletions(-) diff --git a/module/data/Item/Ammo.mjs b/module/data/Item/Ammo.mjs index 8c937f7..79733cd 100644 --- a/module/data/Item/Ammo.mjs +++ b/module/data/Item/Ammo.mjs @@ -1,4 +1,5 @@ import { CommonItemData } from "./Common.mjs"; +import { gameTerms } from "../../gameTerms.mjs"; export class AmmoData extends CommonItemData { // MARK: Base Data @@ -25,6 +26,24 @@ export class AmmoData extends CommonItemData { value: this.quantity, min: 0, }, + { + id: `access`, + type: `dropdown`, + label: `Access`, + path: `system.access`, + value: this.access, + limited: false, + options: [ + { + label: `RipCrypt.common.empty`, + value: ``, + }, + ...gameTerms.Access.map(opt => ({ + label: `RipCrypt.common.access.${opt}`, + value: opt, + })), + ], + }, ]; return fields; }; diff --git a/module/data/Item/Armour.mjs b/module/data/Item/Armour.mjs index 34d964d..4883219 100644 --- a/module/data/Item/Armour.mjs +++ b/module/data/Item/Armour.mjs @@ -116,6 +116,24 @@ export class ArmourData extends CommonItemData { value: this.quantity, min: 0, }, + { + id: `access`, + type: `dropdown`, + label: `Access`, + path: `system.access`, + value: this.access, + limited: false, + options: [ + { + label: `RipCrypt.common.empty`, + value: ``, + }, + ...gameTerms.Access.map(opt => ({ + label: `RipCrypt.common.access.${opt}`, + value: opt, + })), + ], + }, { id: `location`, type: `string-set`, diff --git a/module/data/Item/Common.mjs b/module/data/Item/Common.mjs index d020cc4..fe60d96 100644 --- a/module/data/Item/Common.mjs +++ b/module/data/Item/Common.mjs @@ -1,10 +1,19 @@ +import { gameTerms } from "../../gameTerms.mjs"; import { requiredInteger } from "../helpers.mjs"; +const { fields } = foundry.data; + export class CommonItemData extends foundry.abstract.TypeDataModel { // MARK: Schema static defineSchema() { return { quantity: requiredInteger({ min: 0, initial: 1 }), + access: new fields.StringField({ + blank: true, + nullable: false, + trim: true, + choices: gameTerms.Access, + }), }; }; diff --git a/module/data/Item/Good.mjs b/module/data/Item/Good.mjs index 3973cc1..f5d9ead 100644 --- a/module/data/Item/Good.mjs +++ b/module/data/Item/Good.mjs @@ -1,4 +1,5 @@ import { CommonItemData } from "./Common.mjs"; +import { gameTerms } from "../../gameTerms.mjs"; const { fields } = foundry.data; @@ -40,6 +41,24 @@ export class GoodData extends CommonItemData { value: this.quantity, min: 0, }, + { + id: `access`, + type: `dropdown`, + label: `Access`, + path: `system.access`, + value: this.access, + limited: false, + options: [ + { + label: `RipCrypt.common.empty`, + value: ``, + }, + ...gameTerms.Access.map(opt => ({ + label: `RipCrypt.common.access.${opt}`, + value: opt, + })), + ], + }, { id: `description`, type: `prosemirror`, diff --git a/module/data/Item/Weapon.mjs b/module/data/Item/Weapon.mjs index 91c28ec..5f648a2 100644 --- a/module/data/Item/Weapon.mjs +++ b/module/data/Item/Weapon.mjs @@ -28,12 +28,6 @@ export class WeaponData extends CommonItemData { }), damage: requiredInteger({ min: 0, initial: 0 }), wear: barAttribute(0, 0, 4), - access: new fields.StringField({ - blank: true, - nullable: false, - trim: true, - choices: gameTerms.Access, - }), equipped: new fields.BooleanField({ initial: false, required: true, @@ -87,7 +81,7 @@ export class WeaponData extends CommonItemData { // #endregion // #region Sheet Data - getFormFields(_ctx) { + async getFormFields(_ctx) { const fields = [ { id: `quantity`, @@ -97,6 +91,24 @@ export class WeaponData extends CommonItemData { value: this.quantity, min: 0, }, + { + id: `access`, + type: `dropdown`, + label: `Access`, + path: `system.access`, + value: this.access, + limited: false, + options: [ + { + label: `RipCrypt.common.empty`, + value: ``, + }, + ...gameTerms.Access.map(opt => ({ + label: `RipCrypt.common.access.${opt}`, + value: opt, + })), + ], + }, { id: `traits`, type: `string-set`, From 40ba46fc6bb0bdbca6129a278608d7909abb4c3d Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 20 Feb 2025 00:29:02 -0700 Subject: [PATCH 014/164] Get the DicePool to support edge/drag modifications --- langs/en-ca.json | 2 + module/Apps/DicePool.mjs | 72 +++++++++++++++++++++++++++++-- templates/Apps/DicePool/drag.hbs | 28 ++++++++++++ templates/Apps/DicePool/edge.hbs | 28 ++++++++++++ templates/Apps/DicePool/style.css | 1 - 5 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 templates/Apps/DicePool/drag.hbs create mode 100644 templates/Apps/DicePool/edge.hbs diff --git a/langs/en-ca.json b/langs/en-ca.json index 61a77e6..134593a 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -64,7 +64,9 @@ "tough": "Tough", "hard": "Hard" }, + "drag": "Drag", "edit": "Edit", + "edge": "Edge", "empty": "---", "equipped": "Equipped", "fate": "Fate", diff --git a/module/Apps/DicePool.mjs b/module/Apps/DicePool.mjs index 13b0449..7418a3c 100644 --- a/module/Apps/DicePool.mjs +++ b/module/Apps/DicePool.mjs @@ -26,6 +26,8 @@ export class DicePool extends GenericAppMixin(HandlebarsApplicationMixin(Applica actions: { diceCountDelta: this.#diceCountDelta, targetDelta: this.#targetDelta, + edgeDelta: this.#edgeDelta, + dragDelta: this.#dragDelta, roll: this.#roll, }, }; @@ -37,6 +39,12 @@ export class DicePool extends GenericAppMixin(HandlebarsApplicationMixin(Applica target: { template: filePath(`templates/Apps/DicePool/target.hbs`), }, + drag: { + template: filePath(`templates/Apps/DicePool/drag.hbs`), + }, + edge: { + template: filePath(`templates/Apps/DicePool/edge.hbs`), + }, buttons: { template: filePath(`templates/Apps/DicePool/buttons.hbs`), }, @@ -46,15 +54,20 @@ export class DicePool extends GenericAppMixin(HandlebarsApplicationMixin(Applica // #region Instance Data _diceCount; _target; + _drag; + _edge; constructor({ diceCount = 1, target, + drag = 0, edge = 0, flavor = ``, ...opts } = {}) { super(opts); + this._drag = drag; + this._edge = edge; this._flavor = flavor; this._diceCount = diceCount; this._target = target ?? game.settings.get(`ripcrypt`, `dc`) ?? 1; @@ -74,11 +87,19 @@ export class DicePool extends GenericAppMixin(HandlebarsApplicationMixin(Applica switch (partId) { case `numberOfDice`: { - this._prepareNumberOfDice(ctx); + await this._prepareNumberOfDice(ctx); break; }; case `target`: { - this._prepareTarget(ctx); + await this._prepareTarget(ctx); + break; + }; + case `edge`: { + await this._prepareEdge(ctx); + break; + }; + case `drag`: { + await this._prepareDrag(ctx); break; }; case `buttons`: { @@ -92,7 +113,7 @@ export class DicePool extends GenericAppMixin(HandlebarsApplicationMixin(Applica async _prepareNumberOfDice(ctx) { ctx.numberOfDice = this._diceCount; - ctx.decrementDisabled = this._diceCount <= 0; + ctx.decrementDisabled = this._diceCount <= 1; }; async _prepareTarget(ctx) { @@ -100,6 +121,18 @@ export class DicePool extends GenericAppMixin(HandlebarsApplicationMixin(Applica ctx.incrementDisabled = this._target >= 8; ctx.decrementDisabled = this._target <= 1; }; + + async _prepareEdge(ctx) { + ctx.edge = this._edge; + ctx.incrementDisabled = false; + ctx.decrementDisabled = this._edge <= 0; + }; + + async _prepareDrag(ctx) { + ctx.drag = this._drag; + ctx.incrementDisabled = false; + ctx.decrementDisabled = this._drag <= 0; + }; // #endregion // #region Actions @@ -137,8 +170,39 @@ export class DicePool extends GenericAppMixin(HandlebarsApplicationMixin(Applica this.render({ parts: [`target`] }); }; + static async #edgeDelta(_event, element) { + const delta = parseInt(element.dataset.delta); + if (Number.isNaN(delta)) { + ui.notifications.error( + localizer(`RipCrypt.notifs.error.invalid-delta`, { name: `@RipCrypt.common.edge` }), + ); + return; + }; + + this._edge += delta; + this.render({ parts: [`edge`] }); + }; + + static async #dragDelta(_event, element) { + const delta = parseInt(element.dataset.delta); + if (Number.isNaN(delta)) { + ui.notifications.error( + localizer(`RipCrypt.notifs.error.invalid-delta`, { name: `@RipCrypt.common.drag` }), + ); + return; + }; + + this._drag += delta; + this.render({ parts: [`drag`] }); + }; + static async #roll() { - const formula = `${this._diceCount}d8rc${this._target}`; + let target = this._target; + target -= this._edge; + target += this._drag; + target = Math.max(target, 1); + + const formula = `${this._diceCount}d8rc${target}`; Logger.debug(`Attempting to roll formula: ${formula}`); let flavor = this._flavor; diff --git a/templates/Apps/DicePool/drag.hbs b/templates/Apps/DicePool/drag.hbs new file mode 100644 index 0000000..0fb4a0f --- /dev/null +++ b/templates/Apps/DicePool/drag.hbs @@ -0,0 +1,28 @@ +

diff --git a/templates/Apps/DicePool/edge.hbs b/templates/Apps/DicePool/edge.hbs new file mode 100644 index 0000000..9d6c885 --- /dev/null +++ b/templates/Apps/DicePool/edge.hbs @@ -0,0 +1,28 @@ +
+ +
+ {{ rc-i18n "RipCrypt.common.edge" }} +
+
+ + {{edge}} + +
+
+
diff --git a/templates/Apps/DicePool/style.css b/templates/Apps/DicePool/style.css index 5b65207..a7778c4 100644 --- a/templates/Apps/DicePool/style.css +++ b/templates/Apps/DicePool/style.css @@ -7,7 +7,6 @@ padding: 8px; gap: 8px; width: 250px; - height: 185px; } --button-background: var(--alt-row-background); From 329c45dad930565ad53ed96d124fe14ea9695d55 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 20 Feb 2025 20:36:27 -0700 Subject: [PATCH 015/164] Add weight ratings and localize the Access field (and add it to items that should but don't have it) --- langs/en-ca.json | 11 ++++-- module/data/Item/Ammo.mjs | 4 +-- module/data/Item/Armour.mjs | 27 +++++++++++++-- module/data/Item/Good.mjs | 4 +-- module/data/Item/Weapon.mjs | 45 ++++++++++++++----------- module/gameTerms.mjs | 5 +++ templates/Apps/AllItemSheetV1/style.css | 2 +- 7 files changed, 69 insertions(+), 29 deletions(-) diff --git a/langs/en-ca.json b/langs/en-ca.json index 134593a..ac5a6bb 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -29,7 +29,8 @@ "thin-glim": "Thin Glim" }, "ability": "Ability", - "access": { + "access": "Access", + "accessLevels": { "Common": "Common", "Uncommon": "Uncommon", "Rare": "Rare", @@ -100,7 +101,13 @@ "singular": "Weapon", "plural": "Weapons" }, - "wear": "Wear" + "wear": "Wear", + "weightRating": "Weight", + "weightRatings": { + "light": "Light", + "modest": "Modest", + "heavy": "Heavy" + } }, "setting": { "abbrAccess": { diff --git a/module/data/Item/Ammo.mjs b/module/data/Item/Ammo.mjs index 79733cd..a3aa54b 100644 --- a/module/data/Item/Ammo.mjs +++ b/module/data/Item/Ammo.mjs @@ -29,7 +29,7 @@ export class AmmoData extends CommonItemData { { id: `access`, type: `dropdown`, - label: `Access`, + label: `RipCrypt.common.access`, path: `system.access`, value: this.access, limited: false, @@ -39,7 +39,7 @@ export class AmmoData extends CommonItemData { value: ``, }, ...gameTerms.Access.map(opt => ({ - label: `RipCrypt.common.access.${opt}`, + label: `RipCrypt.common.accessLevels.${opt}`, value: opt, })), ], diff --git a/module/data/Item/Armour.mjs b/module/data/Item/Armour.mjs index 4883219..5d56510 100644 --- a/module/data/Item/Armour.mjs +++ b/module/data/Item/Armour.mjs @@ -31,6 +31,12 @@ export class ArmourData extends CommonItemData { required: true, nullable: false, }), + weight: new fields.StringField({ + blank: false, + nullable: true, + initial: null, + options: Object.values(gameTerms.WeightRatings), + }), }; }; @@ -119,7 +125,7 @@ export class ArmourData extends CommonItemData { { id: `access`, type: `dropdown`, - label: `Access`, + label: `RipCrypt.common.access`, path: `system.access`, value: this.access, limited: false, @@ -129,7 +135,24 @@ export class ArmourData extends CommonItemData { value: ``, }, ...gameTerms.Access.map(opt => ({ - label: `RipCrypt.common.access.${opt}`, + label: `RipCrypt.common.accessLevels.${opt}`, + value: opt, + })), + ], + }, + { + id: `weight`, + type: `dropdown`, + label: `RipCrypt.common.weightRating`, + path: `system.weight`, + value: this.weight, + options: [ + { + label: `RipCrypt.common.empty`, + value: null, + }, + ...Object.values(gameTerms.WeightRatings).map(opt => ({ + label: `RipCrypt.common.weightRatings.${opt}`, value: opt, })), ], diff --git a/module/data/Item/Good.mjs b/module/data/Item/Good.mjs index f5d9ead..0194609 100644 --- a/module/data/Item/Good.mjs +++ b/module/data/Item/Good.mjs @@ -44,7 +44,7 @@ export class GoodData extends CommonItemData { { id: `access`, type: `dropdown`, - label: `Access`, + label: `RipCrypt.common.access`, path: `system.access`, value: this.access, limited: false, @@ -54,7 +54,7 @@ export class GoodData extends CommonItemData { value: ``, }, ...gameTerms.Access.map(opt => ({ - label: `RipCrypt.common.access.${opt}`, + label: `RipCrypt.common.accessLevels.${opt}`, value: opt, })), ], diff --git a/module/data/Item/Weapon.mjs b/module/data/Item/Weapon.mjs index 5f648a2..5242f25 100644 --- a/module/data/Item/Weapon.mjs +++ b/module/data/Item/Weapon.mjs @@ -33,6 +33,12 @@ export class WeaponData extends CommonItemData { required: true, nullable: false, }), + weight: new fields.StringField({ + blank: false, + nullable: true, + initial: null, + options: Object.values(gameTerms.WeightRatings), + }), }; }; @@ -94,7 +100,7 @@ export class WeaponData extends CommonItemData { { id: `access`, type: `dropdown`, - label: `Access`, + label: `RipCrypt.common.access`, path: `system.access`, value: this.access, limited: false, @@ -104,7 +110,24 @@ export class WeaponData extends CommonItemData { value: ``, }, ...gameTerms.Access.map(opt => ({ - label: `RipCrypt.common.access.${opt}`, + label: `RipCrypt.common.accessLevels.${opt}`, + value: opt, + })), + ], + }, + { + id: `weight`, + type: `dropdown`, + label: `RipCrypt.common.weightRating`, + path: `system.weight`, + value: this.weight, + options: [ + { + label: `RipCrypt.common.empty`, + value: null, + }, + ...Object.values(gameTerms.WeightRatings).map(opt => ({ + label: `RipCrypt.common.weightRatings.${opt}`, value: opt, })), ], @@ -180,24 +203,6 @@ export class WeaponData extends CommonItemData { min: 0, }, }, - { - id: `access`, - type: `dropdown`, - label: `Access`, - path: `system.access`, - value: this.access, - limited: false, - options: [ - { - label: `RipCrypt.common.empty`, - value: ``, - }, - ...gameTerms.Access.map(opt => ({ - label: `RipCrypt.common.access.${opt}`, - value: opt, - })), - ], - }, ); if (this.parent.isEmbedded) { diff --git a/module/gameTerms.mjs b/module/gameTerms.mjs index 3e3bfff..5bcbe71 100644 --- a/module/gameTerms.mjs +++ b/module/gameTerms.mjs @@ -42,4 +42,9 @@ export const gameTerms = Object.preventExtensions({ `shield`, `good`, ]), + WeightRatings: Object.freeze({ + LIGHT: `light`, + MODEST: `modest`, + HEAVY: `heavy`, + }), }); diff --git a/templates/Apps/AllItemSheetV1/style.css b/templates/Apps/AllItemSheetV1/style.css index acac966..59089ef 100644 --- a/templates/Apps/AllItemSheetV1/style.css +++ b/templates/Apps/AllItemSheetV1/style.css @@ -23,7 +23,7 @@ grid-template-columns: auto 200px; column-gap: var(--col-gap); row-gap: var(--row-gap); - max-width: 300px; + max-width: 350px; padding: 8px; background: var(--base-background); From 4f138202ce2847996be9bac03be176006280578d Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 20 Feb 2025 20:36:39 -0700 Subject: [PATCH 016/164] Deprecation warning fix --- module/documents/token.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/module/documents/token.mjs b/module/documents/token.mjs index 458230e..c4913fe 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -1,3 +1,4 @@ +const { Token } = foundry.canvas.placeables; const { TokenTurnMarker } = foundry.canvas.placeables.tokens; export class RipCryptToken extends Token { From 5eedea5001e699c0c41e4ff23c65c04884b10418 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 20 Feb 2025 21:30:26 -0700 Subject: [PATCH 017/164] Add foundation for being able to create embedded items without requiring going through the sidebar --- module/Apps/GenericApp.mjs | 6 +++++- module/Apps/utils.mjs | 21 +++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/module/Apps/GenericApp.mjs b/module/Apps/GenericApp.mjs index 392cb95..781603d 100644 --- a/module/Apps/GenericApp.mjs +++ b/module/Apps/GenericApp.mjs @@ -1,4 +1,4 @@ -import { deleteItemFromElement, editItemFromElement } from "./utils.mjs"; +import { createItemFromElement, deleteItemFromElement, editItemFromElement } from "./utils.mjs"; import { DicePool } from "./DicePool.mjs"; import { RichEditor } from "./RichEditor.mjs"; import { toBoolean } from "../consts.mjs"; @@ -16,6 +16,10 @@ export function GenericAppMixin(HandlebarsApp) { ], actions: { roll: this.#rollDice, + createItem: (_event, target) => { + const parent = this.document; + createItemFromElement(target, { parent }); + }, editItem: (_event, target) => editItemFromElement(target), deleteItem: (_event, target) => deleteItemFromElement(target), openRichEditor: this.#openRichEditor, diff --git a/module/Apps/utils.mjs b/module/Apps/utils.mjs index ff3d84a..1baee18 100644 --- a/module/Apps/utils.mjs +++ b/module/Apps/utils.mjs @@ -3,7 +3,24 @@ This file contains utilities used by Applications in order to be DRYer */ /** - * @param {HTMLElement} target The element that gets + * @param {HTMLElement} target The element to operate on + */ +export async function createItemFromElement(target, { parent } = {}) { + const data = target.dataset; + const types = data.itemTypes?.split(`,`); + const type = data.defaultItemType; + await Item.createDialog( + { type }, + { parent }, + { + types, + folders: [], + }, + ); +}; + +/** + * @param {HTMLElement} target The element to operate on */ export async function editItemFromElement(target) { const itemEl = target.closest(`[data-item-id]`); @@ -15,7 +32,7 @@ export async function editItemFromElement(target) { }; /** - * @param {HTMLElement} target The element that gets + * @param {HTMLElement} target The element to operate on */ export async function deleteItemFromElement(target) { const itemEl = target.closest(`[data-item-id]`); From 564e27de01957721ddace01776733d74ff61fab2 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 20 Feb 2025 22:18:51 -0700 Subject: [PATCH 018/164] Make it so that item creation actually embeds if possible --- module/Apps/GenericApp.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/Apps/GenericApp.mjs b/module/Apps/GenericApp.mjs index 781603d..5f3a75b 100644 --- a/module/Apps/GenericApp.mjs +++ b/module/Apps/GenericApp.mjs @@ -16,7 +16,7 @@ export function GenericAppMixin(HandlebarsApp) { ], actions: { roll: this.#rollDice, - createItem: (_event, target) => { + createItem(_event, target) { // uses arrow-less function for "this" const parent = this.document; createItemFromElement(target, { parent }); }, From 8c5d7b64697076d10fe5336d3d0cecad611a34cb Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 22 Feb 2025 18:36:26 -0700 Subject: [PATCH 019/164] Add autocomplete for the rc-border element --- .vscode/ripcrypt.html-data.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.vscode/ripcrypt.html-data.json b/.vscode/ripcrypt.html-data.json index 628a94a..3efb728 100644 --- a/.vscode/ripcrypt.html-data.json +++ b/.vscode/ripcrypt.html-data.json @@ -26,6 +26,20 @@ { "name": "var:stroke-width", "description": "The stroke width of the icon, must be a valid CSS unit" }, { "name": "var:stroke-linejoin", "description": "The stroke linejoin of the icon, must be a valid CSS value" } ] + }, + { + "name": "rc-border", + "description": "Creates a stylized border in the same sort of design that the published RipCrypt book uses", + "attributes": [ + { "name": "var:vertical-displacement", "description": "How much vertical displacement the title receives, defaults to 12.5px" }, + { "name": "var:padding", "description": "How much padding the border container has" }, + { "name": "var:border-color", "description": "The CSS value that is used as the colour of the border" }, + { "name": "var:padding-top", "description": "How much padding the top of the border element has, if not provided, defaults to the value of vertical displacement plus 4px" }, + { "name": "var:margin-top", "description": "How much margin the top of the border element has, if not provided, defaults to the value of vertical displacement" }, + { "name": "var:border-mask", "description": "The CSS colour used to mask out the border element, if not provided defaults to the --base-background CSS variable"}, + { "name": "var:title-height", "description": "The CSS height for the title, defaults to 20px" }, + { "name": "var:title-background", "description": "The CSS colour to make the title element, defaults to var:border-color" } + ] } ] } \ No newline at end of file From de6ded9a3956ec3f34cabf6065ded2c2ff26e0ce Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 22 Feb 2025 18:38:07 -0700 Subject: [PATCH 020/164] Allow currency to be tracked on tokens if for some reason you want that --- module/data/Actor/Hero.mjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/module/data/Actor/Hero.mjs b/module/data/Actor/Hero.mjs index ab5d3d5..89dc9a1 100644 --- a/module/data/Actor/Hero.mjs +++ b/module/data/Actor/Hero.mjs @@ -19,6 +19,9 @@ export class HeroData extends foundry.abstract.TypeDataModel { `level.glory`, `level.step`, `level.rank`, + `coin.gold`, + `coin.silver`, + `coin.copper`, ], }; }; From 14b6f8513727b587f80287ce5b2381e6dd2eb905 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 22 Feb 2025 18:38:57 -0700 Subject: [PATCH 021/164] Make a helper function for token bars that only store the current value in the DB --- module/data/Actor/Hero.mjs | 10 ++-------- module/data/helpers.mjs | 11 +++++++++++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/module/data/Actor/Hero.mjs b/module/data/Actor/Hero.mjs index 89dc9a1..2f0dc15 100644 --- a/module/data/Actor/Hero.mjs +++ b/module/data/Actor/Hero.mjs @@ -1,3 +1,4 @@ +import { derivedMaximumBar } from "../helpers.mjs"; import { gameTerms } from "../../gameTerms.mjs"; import { sumReduce } from "../../utils/sumReduce.mjs"; @@ -59,14 +60,7 @@ export class HeroData extends foundry.abstract.TypeDataModel { nullable: false, }), }), - guts: new fields.SchemaField({ - value: new fields.NumberField({ - min: 0, - initial: 5, - integer: true, - nullable: false, - }), - }), + guts: derivedMaximumBar(0, 5), coin: new fields.SchemaField({ gold: new fields.NumberField({ initial: 5, diff --git a/module/data/helpers.mjs b/module/data/helpers.mjs index 2321503..e7c705f 100644 --- a/module/data/helpers.mjs +++ b/module/data/helpers.mjs @@ -19,6 +19,17 @@ export function barAttribute(min, initial, max = undefined) { }); }; +export function derivedMaximumBar(min, initial) { + return new fields.SchemaField({ + value: new fields.NumberField({ + min, + initial, + integer: true, + nullable: false, + }), + }); +}; + export function optionalInteger({min, initial = null, max} = {}) { return new fields.NumberField({ min, From dc5bf7aa0742205fbac62fc7c7673389cb0534e9 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 22 Feb 2025 19:21:09 -0700 Subject: [PATCH 022/164] Remove the v12 compatibility layer that isn't needed --- module/settings/userSettings.mjs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/module/settings/userSettings.mjs b/module/settings/userSettings.mjs index 6dca4c4..0470ec3 100644 --- a/module/settings/userSettings.mjs +++ b/module/settings/userSettings.mjs @@ -1,10 +1,8 @@ export function registerUserSettings() { - const userScope = game.release.generation >= 13 ? `user` : `client`; - game.settings.register(`ripcrypt`, `abbrAccess`, { name: `RipCrypt.setting.abbrAccess.name`, hint: `RipCrypt.setting.abbrAccess.hint`, - scope: userScope, + scope: `user`, type: Boolean, config: true, default: false, @@ -14,7 +12,7 @@ export function registerUserSettings() { game.settings.register(`ripcrypt`, `condensedRange`, { name: `RipCrypt.setting.condensedRange.name`, hint: `RipCrypt.setting.condensedRange.hint`, - scope: userScope, + scope: `user`, type: Boolean, config: true, default: true, From 00228d3aae8afb8ee709d5f8155476ac80d545f5 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 22 Feb 2025 19:21:35 -0700 Subject: [PATCH 023/164] Begin adding cost to items --- langs/en-ca.json | 1 + module/data/Item/Ammo.mjs | 8 +++++ module/data/Item/Common.mjs | 7 +++- module/handlebarHelpers/inputs/currency.mjs | 33 +++++++++++++++++++ module/handlebarHelpers/inputs/formFields.mjs | 2 ++ module/handlebarHelpers/inputs/groupInput.mjs | 2 +- templates/Apps/AllItemSheetV1/style.css | 5 +++ 7 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 module/handlebarHelpers/inputs/currency.mjs diff --git a/langs/en-ca.json b/langs/en-ca.json index ac5a6bb..a91a488 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -51,6 +51,7 @@ "fract": "Fract", "focus": "Focus" }, + "cost": "Cost", "currency": { "gold": "Gold", "silver": "Silver", diff --git a/module/data/Item/Ammo.mjs b/module/data/Item/Ammo.mjs index a3aa54b..566afc7 100644 --- a/module/data/Item/Ammo.mjs +++ b/module/data/Item/Ammo.mjs @@ -26,6 +26,14 @@ export class AmmoData extends CommonItemData { value: this.quantity, min: 0, }, + { + id: `cost`, + type: `cost`, + label: `RipCrypt.common.cost`, + gold: this.cost.gold, + silver: this.cost.silver, + copper: this.cost.copper, + }, { id: `access`, type: `dropdown`, diff --git a/module/data/Item/Common.mjs b/module/data/Item/Common.mjs index fe60d96..bf37c0e 100644 --- a/module/data/Item/Common.mjs +++ b/module/data/Item/Common.mjs @@ -1,5 +1,5 @@ +import { optionalInteger, requiredInteger } from "../helpers.mjs"; import { gameTerms } from "../../gameTerms.mjs"; -import { requiredInteger } from "../helpers.mjs"; const { fields } = foundry.data; @@ -14,6 +14,11 @@ export class CommonItemData extends foundry.abstract.TypeDataModel { trim: true, choices: gameTerms.Access, }), + cost: new fields.SchemaField({ + gold: optionalInteger(), + silver: optionalInteger(), + copper: optionalInteger(), + }), }; }; diff --git a/module/handlebarHelpers/inputs/currency.mjs b/module/handlebarHelpers/inputs/currency.mjs new file mode 100644 index 0000000..12b6ca8 --- /dev/null +++ b/module/handlebarHelpers/inputs/currency.mjs @@ -0,0 +1,33 @@ +import { groupInput } from "./groupInput.mjs"; + +export function costInput(input, data) { + return groupInput({ + title: input.label, + fields: [ + { + id: input.id + `-gold`, + type: `integer`, + label: `RipCrypt.common.currency.gold`, + value: input.gold, + path: `system.cost.gold`, + limited: input.limited, + }, + { + id: input.id + `-silver`, + type: `integer`, + label: `RipCrypt.common.currency.silver`, + value: input.silver, + path: `system.cost.silver`, + limited: input.limited, + }, + { + id: input.id + `-copper`, + type: `integer`, + label: `RipCrypt.common.currency.copper`, + value: input.copper, + path: `system.cost.copper`, + limited: input.limited, + }, + ], + }, data); +}; diff --git a/module/handlebarHelpers/inputs/formFields.mjs b/module/handlebarHelpers/inputs/formFields.mjs index 518b054..d8cd18e 100644 --- a/module/handlebarHelpers/inputs/formFields.mjs +++ b/module/handlebarHelpers/inputs/formFields.mjs @@ -1,5 +1,6 @@ import { barInput } from "./barInput.mjs"; import { booleanInput } from "./booleanInput.mjs"; +import { costInput } from "./currency.mjs"; import { dropdownInput } from "./dropdownInput.mjs"; import { groupInput } from "./groupInput.mjs"; import { numberInput } from "./numberInput.mjs"; @@ -18,6 +19,7 @@ const inputTypes = { boolean: booleanInput, group: groupInput, text: textInput, + cost: costInput, }; const typesToSanitize = new Set([ `string`, `number` ]); diff --git a/module/handlebarHelpers/inputs/groupInput.mjs b/module/handlebarHelpers/inputs/groupInput.mjs index d6a5c45..59f26e8 100644 --- a/module/handlebarHelpers/inputs/groupInput.mjs +++ b/module/handlebarHelpers/inputs/groupInput.mjs @@ -16,7 +16,7 @@ export function groupInput(input, data) { data-input-type="group" var:border-color="${input.borderColor ?? `var(--accent-1)`}" var:vertical-displacement="${input.verticalDisplacement ?? `12px`}" - var:padding-top="${input.paddingTop ?? `16px`}" + var:padding-top="${input.paddingTop ?? `20px`}" >
${title}
diff --git a/templates/Apps/AllItemSheetV1/style.css b/templates/Apps/AllItemSheetV1/style.css index 59089ef..2170d31 100644 --- a/templates/Apps/AllItemSheetV1/style.css +++ b/templates/Apps/AllItemSheetV1/style.css @@ -83,6 +83,11 @@ margin: 0 auto; } + hr:has(+ [data-input-type="group"]), + [data-input-type="group"] + hr { + display: none; + }; + label, .label { display: flex; align-items: center; From c7342b6402fcec6a822e1cb12d41ddf05873b5bc Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 27 Feb 2025 22:56:36 -0700 Subject: [PATCH 024/164] Begin work on the updated delve dice HUD that is better in every way than the other version --- module/Apps/DelveDiceHUD.mjs | 106 ++++++++++++++++++ module/hooks/init.mjs | 3 + module/hooks/ready.mjs | 2 + module/settings/metaSettings.mjs | 17 ++- templates/Apps/DelveDiceHUD/difficulty.hbs | 3 + templates/Apps/DelveDiceHUD/fateCompass.hbs | 3 + templates/Apps/DelveDiceHUD/style.css | 18 +++ templates/Apps/DelveDiceHUD/tour/current.hbs | 3 + templates/Apps/DelveDiceHUD/tour/next.hbs | 7 ++ templates/Apps/DelveDiceHUD/tour/previous.hbs | 7 ++ templates/Apps/apps.css | 1 + templates/css/elements/button.css | 2 +- templates/css/themes/dark.css | 4 + 13 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 module/Apps/DelveDiceHUD.mjs create mode 100644 templates/Apps/DelveDiceHUD/difficulty.hbs create mode 100644 templates/Apps/DelveDiceHUD/fateCompass.hbs create mode 100644 templates/Apps/DelveDiceHUD/style.css create mode 100644 templates/Apps/DelveDiceHUD/tour/current.hbs create mode 100644 templates/Apps/DelveDiceHUD/tour/next.hbs create mode 100644 templates/Apps/DelveDiceHUD/tour/previous.hbs diff --git a/module/Apps/DelveDiceHUD.mjs b/module/Apps/DelveDiceHUD.mjs new file mode 100644 index 0000000..0122aea --- /dev/null +++ b/module/Apps/DelveDiceHUD.mjs @@ -0,0 +1,106 @@ +import { filePath } from "../consts.mjs"; +import { Logger } from "../utils/Logger.mjs"; + +const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; + +const conditions = [ + { label: `RipCrypt.common.difficulties.easy`, value: 4 }, + { label: `RipCrypt.common.difficulties.normal`, value: 5 }, + { label: `RipCrypt.common.difficulties.tough`, value: 6 }, + { label: `RipCrypt.common.difficulties.hard`, value: 7 }, +]; + +export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { + // #region Options + static DEFAULT_OPTIONS = { + id: `ripcrypt-delve-dice`, + tag: `aside`, + classes: [ + `ripcrypt`, + `ripcrypt--DelveDiceHUD` + ], + window: { + frame: false, + positioned: false, + }, + actions: { + tourDelta: this.#tourDelta, + }, + }; + + static PARTS = { + previousTour: { + template: filePath(`templates/Apps/DelveDiceHUD/tour/previous.hbs`), + }, + difficulty: { + template: filePath(`templates/Apps/DelveDiceHUD/difficulty.hbs`), + }, + fateCompass: { + template: filePath(`templates/Apps/DelveDiceHUD/fateCompass.hbs`), + }, + currentTour: { + template: filePath(`templates/Apps/DelveDiceHUD/tour/current.hbs`), + }, + nextTour: { + template: filePath(`templates/Apps/DelveDiceHUD/tour/next.hbs`), + }, + }; + // #endregion + + // #region Lifecycle + /** + * Injects the element into the Foundry UI in the top middle + */ + _insertElement(element) { + const existing = document.getElementById(element.id); + if (existing) { + existing.replaceWith(element); + } else { + const parent = document.getElementById(`ui-top`); + parent.prepend(element); + }; + }; + + async _onRender(context, options) { + await super._onRender(context, options); + + // Shortcut because users can't edit + if (!game.user.isGM) { return }; + }; + + async _preparePartContext(partId, ctx, opts) { + ctx = await super._preparePartContext(partId, ctx, opts); + ctx.meta ??= {}; + + ctx.meta.editable = game.user.isGM; + + switch (partId) { + case `currentTour`: { + await this._prepareTourContext(ctx); + break; + }; + case `difficulty`: { + await this._prepareDifficultyContext(ctx); + break; + }; + }; + + Logger.log(`${partId} Context`, ctx); + return ctx; + }; + + async _prepareTourContext(ctx) { + ctx.tour = game.settings.get(`ripcrypt`, `sandsOfFate`); + }; + + async _prepareDifficultyContext(ctx) { + ctx.dc = game.settings.get(`ripcrypt`, `dc`); + } + // #endregion + + // #region Actions + static async #tourDelta() { + ui.notifications.info(`Button Clicked!`, { console: false }); + }; + // #endregion +}; diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 812c6e2..4005cc5 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -1,6 +1,7 @@ // Applications import { AllItemSheetV1 } from "../Apps/ItemSheets/AllItemSheetV1.mjs"; import { CombinedHeroSheet } from "../Apps/ActorSheets/CombinedHeroSheet.mjs"; +import { DelveDiceHUD } from "../Apps/DelveDiceHUD.mjs"; import { DelveTourApp } from "../Apps/DelveTourApp.mjs"; import { HeroSkillsCardV1 } from "../Apps/ActorSheets/HeroSkillsCardV1.mjs"; import { HeroSummaryCardV1 } from "../Apps/ActorSheets/HeroSummaryCardV1.mjs"; @@ -39,6 +40,8 @@ Hooks.once(`init`, () => { CONFIG.Combat.initiative.decimals = 2; CONFIG.ui.crypt = DelveTourApp; + CONFIG.ui.delveDice = DelveDiceHUD; + // globalThis.delveDice = new DelveDiceHUD(); // #region Settings registerMetaSettings(); diff --git a/module/hooks/ready.mjs b/module/hooks/ready.mjs index c71bc25..c6c0167 100644 --- a/module/hooks/ready.mjs +++ b/module/hooks/ready.mjs @@ -23,6 +23,8 @@ Hooks.once(`ready`, () => { ui.crypt.render({ force: true }); }; + ui.delveDice.render({ force: true }); + // MARK: 1-time updates if (!game.settings.get(`ripcrypt`, `firstLoadFinished`)) { // Update the turnMarker to be the RipCrypt defaults diff --git a/module/settings/metaSettings.mjs b/module/settings/metaSettings.mjs index 7694088..77bc3dc 100644 --- a/module/settings/metaSettings.mjs +++ b/module/settings/metaSettings.mjs @@ -5,20 +5,25 @@ export function registerMetaSettings() { config: false, requiresReload: false, onChange: () => { - ui.crypt.render({ parts: [ `delveConditions` ]}); + ui.delveDice.render({ parts: [`difficulty`] }); }, }); + game.settings.register(`ripcrypt`, `sandsOfFate`, { + scope: `world`, + type: Number, + initial: 8, + config: false, + requiresReload: false, + onChange: async () => {}, + }); + game.settings.register(`ripcrypt`, `currentFate`, { scope: `world`, type: String, config: false, requiresReload: false, - onChange: async () => { - await ui.crypt.render({ parts: [ `fate` ] }); - await game.combat.setupTurns(); - await ui.combat.render({ parts: [ `tracker` ] }); - }, + onChange: async () => {}, }); game.settings.register(`ripcrypt`, `whoFirst`, { diff --git a/templates/Apps/DelveDiceHUD/difficulty.hbs b/templates/Apps/DelveDiceHUD/difficulty.hbs new file mode 100644 index 0000000..29b591f --- /dev/null +++ b/templates/Apps/DelveDiceHUD/difficulty.hbs @@ -0,0 +1,3 @@ +
+ Difficulty: {{dc}} +
diff --git a/templates/Apps/DelveDiceHUD/fateCompass.hbs b/templates/Apps/DelveDiceHUD/fateCompass.hbs new file mode 100644 index 0000000..3ad015d --- /dev/null +++ b/templates/Apps/DelveDiceHUD/fateCompass.hbs @@ -0,0 +1,3 @@ +
+ North +
\ No newline at end of file diff --git a/templates/Apps/DelveDiceHUD/style.css b/templates/Apps/DelveDiceHUD/style.css new file mode 100644 index 0000000..a7ab971 --- /dev/null +++ b/templates/Apps/DelveDiceHUD/style.css @@ -0,0 +1,18 @@ +#ripcrypt-delve-dice { + display: grid; + grid-template-columns: max-content 1fr 1fr 1fr max-content; + gap: 8px; + padding: 4px 1.5rem; + background: var(--DelveDice-background); + align-items: center; + justify-items: center; + pointer-events: all; + + border-radius: 0 0 999px 999px; + + button { + &:hover { + cursor: pointer; + } + } +} diff --git a/templates/Apps/DelveDiceHUD/tour/current.hbs b/templates/Apps/DelveDiceHUD/tour/current.hbs new file mode 100644 index 0000000..12c795e --- /dev/null +++ b/templates/Apps/DelveDiceHUD/tour/current.hbs @@ -0,0 +1,3 @@ +
+ The Hourglass +
diff --git a/templates/Apps/DelveDiceHUD/tour/next.hbs b/templates/Apps/DelveDiceHUD/tour/next.hbs new file mode 100644 index 0000000..1e85007 --- /dev/null +++ b/templates/Apps/DelveDiceHUD/tour/next.hbs @@ -0,0 +1,7 @@ + diff --git a/templates/Apps/DelveDiceHUD/tour/previous.hbs b/templates/Apps/DelveDiceHUD/tour/previous.hbs new file mode 100644 index 0000000..d498a99 --- /dev/null +++ b/templates/Apps/DelveDiceHUD/tour/previous.hbs @@ -0,0 +1,7 @@ + diff --git a/templates/Apps/apps.css b/templates/Apps/apps.css index bcf722e..9f6a4f6 100644 --- a/templates/Apps/apps.css +++ b/templates/Apps/apps.css @@ -1,6 +1,7 @@ @import url("./AllItemSheetV1/style.css"); @import url("./CombinedHeroSheet/style.css"); @import url("./CryptApp/style.css"); +@import url("./DelveDiceHUD/style.css"); @import url("./DicePool/style.css"); @import url("./HeroSummaryCardV1/style.css"); @import url("./HeroSkillsCardV1/style.css"); diff --git a/templates/css/elements/button.css b/templates/css/elements/button.css index 2d0b6bb..c9e0bab 100644 --- a/templates/css/elements/button.css +++ b/templates/css/elements/button.css @@ -1,4 +1,4 @@ -.ripcrypt > .window-content button { +.ripcrypt button { all: revert; outline: none; border: none; diff --git a/templates/css/themes/dark.css b/templates/css/themes/dark.css index b33c069..aa4a8a4 100644 --- a/templates/css/themes/dark.css +++ b/templates/css/themes/dark.css @@ -41,4 +41,8 @@ --pill-input-background: var(--accent-2); --pill-input-disabled-text: white; --pill-input-disabled-background: black; + + /* Custom HUD Components */ + --DelveDice-background: var(--accent-1); + --DelveDice-text: white; } From 77979f55506071e1353ddc3092153b1476911c7f Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 28 Feb 2025 16:17:16 -0700 Subject: [PATCH 025/164] Add arrow icons for the HUD controls --- assets/_credit.txt | 5 +++++ assets/icons/arrow-compass.svg | 3 +++ assets/icons/arrow-left.svg | 3 +++ assets/icons/arrow-right.svg | 3 +++ 4 files changed, 14 insertions(+) create mode 100644 assets/icons/arrow-compass.svg create mode 100644 assets/icons/arrow-left.svg create mode 100644 assets/icons/arrow-right.svg diff --git a/assets/_credit.txt b/assets/_credit.txt index 39124c6..43cf3fb 100644 --- a/assets/_credit.txt +++ b/assets/_credit.txt @@ -11,6 +11,11 @@ ARISO: Abdulloh Fauzan: - icons/info-circle.svg (https://thenounproject.com/icon/information-4176576/) : Rights Purchased +QOLBIN SALIIM: + - icons/arrow-left.svg (https://thenounproject.com/icon/arrow-1933583/) : Rights Purchased + - icons/arrow-right.svg (https://thenounproject.com/icon/arrow-1933581/) : Rights Purchased + - icons/arrow-compass.svg (https://thenounproject.com/icon/arrow-2052607/) : Rights Purchased + Soetarman Atmodjo: - icons/roll.svg (https://thenounproject.com/icon/dice-5195278/) : Rights Purchased diff --git a/assets/icons/arrow-compass.svg b/assets/icons/arrow-compass.svg new file mode 100644 index 0000000..b1e8a40 --- /dev/null +++ b/assets/icons/arrow-compass.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/arrow-left.svg b/assets/icons/arrow-left.svg new file mode 100644 index 0000000..e1a347e --- /dev/null +++ b/assets/icons/arrow-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/arrow-right.svg b/assets/icons/arrow-right.svg new file mode 100644 index 0000000..a477835 --- /dev/null +++ b/assets/icons/arrow-right.svg @@ -0,0 +1,3 @@ + + + From 3d6710dd189e657251c254b8907b200aaf5d99e5 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 28 Feb 2025 16:18:31 -0700 Subject: [PATCH 026/164] Get the fate compass looking good --- templates/Apps/DelveDiceHUD/fateCompass.hbs | 17 ++++++++-- templates/Apps/DelveDiceHUD/style.css | 36 +++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/templates/Apps/DelveDiceHUD/fateCompass.hbs b/templates/Apps/DelveDiceHUD/fateCompass.hbs index 3ad015d..19ea83c 100644 --- a/templates/Apps/DelveDiceHUD/fateCompass.hbs +++ b/templates/Apps/DelveDiceHUD/fateCompass.hbs @@ -1,3 +1,16 @@
- North -
\ No newline at end of file +
+
+ N + W + + E + S +
+
+
diff --git a/templates/Apps/DelveDiceHUD/style.css b/templates/Apps/DelveDiceHUD/style.css index a7ab971..0e4a226 100644 --- a/templates/Apps/DelveDiceHUD/style.css +++ b/templates/Apps/DelveDiceHUD/style.css @@ -15,4 +15,40 @@ cursor: pointer; } } + + #fate-compass { + width: 100%; + height: 100%; + overflow: visible; + position: relative; + + .compass-container { + position: absolute; + background: var(--DelveDice-background); + border-radius: 0 0 999px 999px; + padding: 4px; + width: 100%; + } + + .compass { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-rows: repeat(3, minmax(0, 1fr)); + grid-template-areas: + ". N ." + "W A E" + ". S ."; + align-items: center; + justify-items: center; + background: var(--accent-2); + border-radius: 999px; + aspect-ratio: 1; + } + + .compass-pointer { + grid-area: A; + transition: 500ms transform; + transform: rotate(-90deg); /* North by default */ + } + } } From 6ae412c787a51cb0e72a2b44c354d1769d88bca3 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 28 Feb 2025 16:47:41 -0700 Subject: [PATCH 027/164] Get hourglass improved --- templates/Apps/DelveDiceHUD/style.css | 27 +++++++++++++++++++- templates/Apps/DelveDiceHUD/tour/current.hbs | 13 ++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/templates/Apps/DelveDiceHUD/style.css b/templates/Apps/DelveDiceHUD/style.css index 0e4a226..4b19a11 100644 --- a/templates/Apps/DelveDiceHUD/style.css +++ b/templates/Apps/DelveDiceHUD/style.css @@ -1,6 +1,6 @@ #ripcrypt-delve-dice { display: grid; - grid-template-columns: max-content 1fr 1fr 1fr max-content; + grid-template-columns: max-content 1fr 0.6fr 1fr max-content; gap: 8px; padding: 4px 1.5rem; background: var(--DelveDice-background); @@ -51,4 +51,29 @@ transform: rotate(-90deg); /* North by default */ } } + + #the-hourglass { + display: flex; + flex-direction: row; + position: relative; + + .hourglass-container { + position: absolute; + width: 34px; + display: flex; + flex-direction: column; + justify-content: center; + padding: 4px 0; + background: var(--accent-1); + border-radius: 8px; + + rc-svg { + inset: 4px; + } + } + + .hud-text { + margin-left: 38px; + } + } } diff --git a/templates/Apps/DelveDiceHUD/tour/current.hbs b/templates/Apps/DelveDiceHUD/tour/current.hbs index 12c795e..991b6f4 100644 --- a/templates/Apps/DelveDiceHUD/tour/current.hbs +++ b/templates/Apps/DelveDiceHUD/tour/current.hbs @@ -1,3 +1,12 @@ -
- The Hourglass +
+
+ +
+
+ The Hourglass +
From c9ed4142e6994089f582e2176549c349f3cb73ad Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 1 Mar 2025 00:34:25 -0700 Subject: [PATCH 028/164] Finalize the general layout of the HUD --- templates/Apps/DelveDiceHUD/difficulty.hbs | 15 ++++++++-- templates/Apps/DelveDiceHUD/style.css | 29 +++++++++++++------- templates/Apps/DelveDiceHUD/tour/current.hbs | 11 +++++--- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/templates/Apps/DelveDiceHUD/difficulty.hbs b/templates/Apps/DelveDiceHUD/difficulty.hbs index 29b591f..d65b06b 100644 --- a/templates/Apps/DelveDiceHUD/difficulty.hbs +++ b/templates/Apps/DelveDiceHUD/difficulty.hbs @@ -1,3 +1,14 @@ -
- Difficulty: {{dc}} +
+
+ 8 + +
diff --git a/templates/Apps/DelveDiceHUD/style.css b/templates/Apps/DelveDiceHUD/style.css index 4b19a11..55128c2 100644 --- a/templates/Apps/DelveDiceHUD/style.css +++ b/templates/Apps/DelveDiceHUD/style.css @@ -1,6 +1,6 @@ #ripcrypt-delve-dice { display: grid; - grid-template-columns: max-content 1fr 0.6fr 1fr max-content; + grid-template-columns: max-content 2rem 90px 2rem max-content; gap: 8px; padding: 4px 1.5rem; background: var(--DelveDice-background); @@ -52,28 +52,37 @@ } } - #the-hourglass { + #the-hourglass, + #delve-difficulty { + width: 100%; + height: 100%; display: flex; flex-direction: row; position: relative; - .hourglass-container { + .icon-container { position: absolute; width: 34px; - display: flex; - flex-direction: column; - justify-content: center; + display: grid; padding: 4px 0; background: var(--accent-1); border-radius: 8px; + > * { + grid-row: 1 / -1; + grid-column: 1 / -1; + } + + span { + font-size: 1.25rem; + z-index: 2; + align-self: center; + justify-self: center; + } + rc-svg { inset: 4px; } } - - .hud-text { - margin-left: 38px; - } } } diff --git a/templates/Apps/DelveDiceHUD/tour/current.hbs b/templates/Apps/DelveDiceHUD/tour/current.hbs index 991b6f4..9e3d423 100644 --- a/templates/Apps/DelveDiceHUD/tour/current.hbs +++ b/templates/Apps/DelveDiceHUD/tour/current.hbs @@ -1,12 +1,15 @@
-
+
+ + 8 +
-
- The Hourglass -
From 507913139f915af34b0421f5575676b3e2b7601c Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 1 Mar 2025 00:44:49 -0700 Subject: [PATCH 029/164] Begin working on HUD interactivity --- module/Apps/DelveDiceHUD.mjs | 14 +++++++++- templates/Apps/DelveDiceHUD/fateCompass.hbs | 8 +++--- templates/Apps/DelveDiceHUD/tour/next.hbs | 27 ++++++++++++++----- templates/Apps/DelveDiceHUD/tour/previous.hbs | 27 ++++++++++++++----- templates/css/elements/button.css | 8 ++++++ 5 files changed, 65 insertions(+), 19 deletions(-) diff --git a/module/Apps/DelveDiceHUD.mjs b/module/Apps/DelveDiceHUD.mjs index 0122aea..a487480 100644 --- a/module/Apps/DelveDiceHUD.mjs +++ b/module/Apps/DelveDiceHUD.mjs @@ -83,6 +83,10 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { await this._prepareDifficultyContext(ctx); break; }; + case `fateCompass`: { + await this._prepareFateCompassContext(ctx); + break; + }; }; Logger.log(`${partId} Context`, ctx); @@ -95,12 +99,20 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { async _prepareDifficultyContext(ctx) { ctx.dc = game.settings.get(`ripcrypt`, `dc`); + }; + + async _prepareFateCompassContext(ctx) { + ctx.direction = game.settings.get(`ripcrypt`, `currentFate`); } // #endregion // #region Actions static async #tourDelta() { - ui.notifications.info(`Button Clicked!`, { console: false }); + ui.notifications.info(`Delve Tour Changed`, { console: false }); + }; + + static async #setFate() { + ui.notifications.info(`Fate Set!`, { console: false }); }; // #endregion }; diff --git a/templates/Apps/DelveDiceHUD/fateCompass.hbs b/templates/Apps/DelveDiceHUD/fateCompass.hbs index 19ea83c..75c0ed5 100644 --- a/templates/Apps/DelveDiceHUD/fateCompass.hbs +++ b/templates/Apps/DelveDiceHUD/fateCompass.hbs @@ -1,16 +1,16 @@
- N - W + + - E - S + +
diff --git a/templates/Apps/DelveDiceHUD/tour/next.hbs b/templates/Apps/DelveDiceHUD/tour/next.hbs index 1e85007..28573c3 100644 --- a/templates/Apps/DelveDiceHUD/tour/next.hbs +++ b/templates/Apps/DelveDiceHUD/tour/next.hbs @@ -1,7 +1,20 @@ - +
+ {{!-- This is here to prevent height collapsing --}} + ​ + + {{#if meta.editable}} + + {{/if}} +
+ diff --git a/templates/Apps/DelveDiceHUD/tour/previous.hbs b/templates/Apps/DelveDiceHUD/tour/previous.hbs index d498a99..ac420ef 100644 --- a/templates/Apps/DelveDiceHUD/tour/previous.hbs +++ b/templates/Apps/DelveDiceHUD/tour/previous.hbs @@ -1,7 +1,20 @@ - +
+ {{!-- This is here to prevent height collapsing --}} + ​ + + {{#if meta.editable}} + + {{/if}} +
+ diff --git a/templates/css/elements/button.css b/templates/css/elements/button.css index c9e0bab..0493fe8 100644 --- a/templates/css/elements/button.css +++ b/templates/css/elements/button.css @@ -3,6 +3,8 @@ outline: none; border: none; padding: 2px 4px; + font-family: inherit; + font-size: inherit; background: var(--button-background); color: var(--button-text); @@ -23,4 +25,10 @@ width: 20px; height: 20px; } + + &.transparent { + background: inherit; + color: inherit; + padding: 0; + } } From 7c8d6a7208ef0890725d84ecdb07054cbfe612c4 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 1 Mar 2025 19:20:21 -0700 Subject: [PATCH 030/164] Make the distanceBetweenFates more situation-complete --- module/utils/distanceBetweenFates.mjs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/module/utils/distanceBetweenFates.mjs b/module/utils/distanceBetweenFates.mjs index 4eaaddd..8c57240 100644 --- a/module/utils/distanceBetweenFates.mjs +++ b/module/utils/distanceBetweenFates.mjs @@ -14,12 +14,18 @@ export function distanceBetweenFates(start, end) { return undefined; }; - if (isOppositeFates(start, end)) { + if (start === end) { + return 0; + }; + + if (isOppositeFates(start, end) || isOppositeFates(end, start)) { return 2; }; let isForward = start === FatePath.SOUTH && end === FatePath.WEST; isForward ||= start === FatePath.NORTH && end === FatePath.EAST; + isForward ||= start === FatePath.WEST && end === FatePath.NORTH; + isForward ||= start === FatePath.EAST && end === FatePath.SOUTH; if (isForward) { return 1; }; From 76399621300f4dd40325818b1da78f239034fecb Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 1 Mar 2025 19:22:02 -0700 Subject: [PATCH 031/164] Add animation and editing support for the fate compass --- module/Apps/DelveDiceHUD.mjs | 82 ++++++++++++++++----- templates/Apps/DelveDiceHUD/fateCompass.hbs | 56 +++++++++++++- 2 files changed, 115 insertions(+), 23 deletions(-) diff --git a/module/Apps/DelveDiceHUD.mjs b/module/Apps/DelveDiceHUD.mjs index a487480..4e0d358 100644 --- a/module/Apps/DelveDiceHUD.mjs +++ b/module/Apps/DelveDiceHUD.mjs @@ -1,7 +1,17 @@ +import { distanceBetweenFates } from "../utils/distanceBetweenFates.mjs"; import { filePath } from "../consts.mjs"; +import { gameTerms } from "../gameTerms.mjs"; import { Logger } from "../utils/Logger.mjs"; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; +const { FatePath } = gameTerms; + +const CompassRotations = { + [FatePath.NORTH]: -90, + [FatePath.EAST]: 0, + [FatePath.SOUTH]: 90, + [FatePath.WEST]: 180, +}; const conditions = [ { label: `RipCrypt.common.difficulties.easy`, value: 4 }, @@ -17,7 +27,7 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { tag: `aside`, classes: [ `ripcrypt`, - `ripcrypt--DelveDiceHUD` + `ripcrypt--DelveDiceHUD`, ], window: { frame: false, @@ -25,6 +35,7 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { }, actions: { tourDelta: this.#tourDelta, + setFate: this.#setFate, }, }; @@ -38,7 +49,7 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { fateCompass: { template: filePath(`templates/Apps/DelveDiceHUD/fateCompass.hbs`), }, - currentTour: { + sandsOfFate: { template: filePath(`templates/Apps/DelveDiceHUD/tour/current.hbs`), }, nextTour: { @@ -47,6 +58,24 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { }; // #endregion + // #region Instance Data + /** + * The current number of degrees the compass pointer should be rotated, this + * is not stored in the DB since we only care about the initial rotation on + * reload, which is derived from the current fate. + * @type {Number} + */ + _rotation; + + constructor(...args) { + super(...args); + this._sandsOfFate = game.settings.get(`ripcrypt`, `sandsOfFate`); + this._currentFate = game.settings.get(`ripcrypt`, `currentFate`); + this._rotation = CompassRotations[this._currentFate]; + this._difficulty = game.settings.get(`ripcrypt`, `dc`); + }; + // #endregion + // #region Lifecycle /** * Injects the element into the Foundry UI in the top middle @@ -75,16 +104,17 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { ctx.meta.editable = game.user.isGM; switch (partId) { - case `currentTour`: { - await this._prepareTourContext(ctx); + case `sandsOfFate`: { + ctx.sandsOfFate = this._sandsOfFate; break; }; case `difficulty`: { - await this._prepareDifficultyContext(ctx); + ctx.dc = this._difficulty; break; }; case `fateCompass`: { - await this._prepareFateCompassContext(ctx); + ctx.fate = this._currentFate; + ctx.rotation = `${this._rotation}deg`; break; }; }; @@ -93,26 +123,40 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { return ctx; }; - async _prepareTourContext(ctx) { - ctx.tour = game.settings.get(`ripcrypt`, `sandsOfFate`); + async animate({ parts = [] } = {}) { + if (parts.includes(`fateCompass`)) { + this.#animateCompassTo(); + }; + + if (parts.includes(`sandsOfFate`)) {}; }; - async _prepareDifficultyContext(ctx) { - ctx.dc = game.settings.get(`ripcrypt`, `dc`); - }; + #animateCompassTo(newFate) { + /** @type {HTMLElement|undefined} */ + const pointer = this.element.querySelector(`.compass-pointer`); + if (!pointer) { return }; - async _prepareFateCompassContext(ctx) { - ctx.direction = game.settings.get(`ripcrypt`, `currentFate`); - } + let distance = distanceBetweenFates(this._currentFate, newFate); + Logger.table({ newFate, fate: this._currentFate, distance, _rotation: this._rotation }); + if (distance === 3) { distance = -1 }; + + this._rotation += distance * 90; + + pointer.style.setProperty(`transform`, `rotate(${this._rotation}deg)`); + }; // #endregion // #region Actions - static async #tourDelta() { - ui.notifications.info(`Delve Tour Changed`, { console: false }); - }; + static async #tourDelta() {}; - static async #setFate() { - ui.notifications.info(`Fate Set!`, { console: false }); + /** @this {DelveDiceHUD} */ + static async #setFate(_event, element) { + const fate = element.dataset.toFate; + this.#animateCompassTo(fate); + + // must be done after animate, otherwise it won't change the rotation + this._currentFate = fate; + game.settings.set(`ripcrypt`, `currentFate`, fate); }; // #endregion }; diff --git a/templates/Apps/DelveDiceHUD/fateCompass.hbs b/templates/Apps/DelveDiceHUD/fateCompass.hbs index 75c0ed5..e4b74c0 100644 --- a/templates/Apps/DelveDiceHUD/fateCompass.hbs +++ b/templates/Apps/DelveDiceHUD/fateCompass.hbs @@ -1,16 +1,64 @@
- - - - + {{#if meta.editable}} + + + + + {{else}} + N + W + E + S + {{/if}}
From 110823a26b876252302edf62d6aa9ef79aff6522 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 1 Mar 2025 23:40:39 -0700 Subject: [PATCH 032/164] Get the delve tour incrementer changes working and affecting fate as well --- langs/en-ca.json | 17 +++++ module/Apps/DelveDiceHUD.mjs | 76 +++++++++++++++++-- module/api.mjs | 4 + module/settings/metaSettings.mjs | 19 ++++- module/settings/worldSettings.mjs | 43 ++++++++++- .../{distanceBetweenFates.mjs => fates.mjs} | 18 +++++ templates/Apps/DelveDiceHUD/tour/current.hbs | 7 +- templates/Apps/DelveDiceHUD/tour/next.hbs | 2 + templates/Apps/DelveDiceHUD/tour/previous.hbs | 2 + 9 files changed, 174 insertions(+), 14 deletions(-) rename module/utils/{distanceBetweenFates.mjs => fates.mjs} (69%) diff --git a/langs/en-ca.json b/langs/en-ca.json index a91a488..184e559 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -118,6 +118,20 @@ "condensedRange": { "name": "Condense Weapon Range Input", "hint": "With this enabled, the weapon range will be displayed as \"X / Y\" when editing a weapon. While disabled it will be as displayed as two different rows, one for Short Range and one for Long Range" + }, + "sandsOfFateInitial": { + "name": "Sands of Fate Initial", + "hint": "What value should The Hourglass reset to when a Cryptic Event occurs" + }, + "onCrypticEvent": { + "name": "Cryptic Event Alert", + "hint": "What happens when a cryptic event occurs by clicking the \"Next Delve Tour\" button in the HUD", + "options": { + "notif": "Notification", + "pause": "Pause Game", + "both": "Notification and Pause Game", + "nothing": "Do Nothing" + } } }, "Apps": { @@ -150,6 +164,9 @@ }, "warn": { "cannot-go-negative": "\"{name}\" is unable to be a negative number." + }, + "info": { + "cryptic-event-alert": "A Cryptic Event Has Occured!" } }, "tooltips": { diff --git a/module/Apps/DelveDiceHUD.mjs b/module/Apps/DelveDiceHUD.mjs index 4e0d358..960665e 100644 --- a/module/Apps/DelveDiceHUD.mjs +++ b/module/Apps/DelveDiceHUD.mjs @@ -1,6 +1,7 @@ -import { distanceBetweenFates } from "../utils/distanceBetweenFates.mjs"; +import { distanceBetweenFates, nextFate, previousFate } from "../utils/fates.mjs"; import { filePath } from "../consts.mjs"; import { gameTerms } from "../gameTerms.mjs"; +import { localizer } from "../utils/Localizer.mjs"; import { Logger } from "../utils/Logger.mjs"; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -128,35 +129,96 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) { this.#animateCompassTo(); }; - if (parts.includes(`sandsOfFate`)) {}; + if (parts.includes(`sandsOfFate`)) { + this.#animateSandsTo(); + }; }; #animateCompassTo(newFate) { + if (newFate === this._currentFate) { return }; + /** @type {HTMLElement|undefined} */ const pointer = this.element.querySelector(`.compass-pointer`); if (!pointer) { return }; + newFate ??= game.settings.get(`ripcrypt`, `currentFate`); + let distance = distanceBetweenFates(this._currentFate, newFate); - Logger.table({ newFate, fate: this._currentFate, distance, _rotation: this._rotation }); if (distance === 3) { distance = -1 }; this._rotation += distance * 90; pointer.style.setProperty(`transform`, `rotate(${this._rotation}deg)`); + this._currentFate = newFate; + }; + + #animateSandsTo(newSands) { + /** @type {HTMLElement|undefined} */ + const sands = this.element.querySelector(`.sands-value`); + if (!sands) { return }; + + newSands ??= game.settings.get(`ripcrypt`, `sandsOfFate`); + + sands.innerHTML = newSands; + this._sandsOfFate = newSands; }; // #endregion // #region Actions - static async #tourDelta() {}; + /** @this {DelveDiceHUD} */ + static async #tourDelta(_event, element) { + const delta = parseInt(element.dataset.delta); + const initial = game.settings.get(`ripcrypt`, `sandsOfFateInitial`); + let newSands = this._sandsOfFate + delta; + + if (newSands > initial) { + Logger.info(`Cannot go to a previous Delve Tour above the initial value`); + return; + }; + + if (newSands === 0) { + newSands = initial; + await this.alertCrypticEvent(); + }; + + switch (Math.sign(delta)) { + case -1: { + game.settings.set(`ripcrypt`, `currentFate`, nextFate(this._currentFate)); + break; + } + case 1: { + game.settings.set(`ripcrypt`, `currentFate`, previousFate(this._currentFate)); + break; + } + }; + + this.#animateSandsTo(newSands); + game.settings.set(`ripcrypt`, `sandsOfFate`, newSands); + }; /** @this {DelveDiceHUD} */ static async #setFate(_event, element) { const fate = element.dataset.toFate; this.#animateCompassTo(fate); - - // must be done after animate, otherwise it won't change the rotation - this._currentFate = fate; game.settings.set(`ripcrypt`, `currentFate`, fate); }; // #endregion + + // #region Public API + async alertCrypticEvent() { + const alertType = game.settings.get(`ripcrypt`, `onCrypticEvent`); + if (alertType === `nothing`) { return }; + + if ([`both`, `notif`].includes(alertType)) { + ui.notifications.info( + localizer(`RipCrypt.notifs.info.cryptic-event-alert`), + { console: false }, + ); + }; + + if ([`both`, `pause`].includes(alertType) && game.user.isGM) { + game.togglePause(true, { broadcast: true }); + }; + }; + // #endregion }; diff --git a/module/api.mjs b/module/api.mjs index 7b04ea8..d2e50ac 100644 --- a/module/api.mjs +++ b/module/api.mjs @@ -6,6 +6,7 @@ import { HeroSummaryCardV1 } from "./Apps/ActorSheets/HeroSummaryCardV1.mjs"; import { RichEditor } from "./Apps/RichEditor.mjs"; // Util imports +import { distanceBetweenFates, nextFate, previousFate } from "./utils/fates.mjs"; import { documentSorter } from "./consts.mjs"; const { deepFreeze } = foundry.utils; @@ -24,6 +25,9 @@ Object.defineProperty( }, utils: { documentSorter, + distanceBetweenFates, + nextFate, + previousFate, }, }), writable: false, diff --git a/module/settings/metaSettings.mjs b/module/settings/metaSettings.mjs index 77bc3dc..66fb669 100644 --- a/module/settings/metaSettings.mjs +++ b/module/settings/metaSettings.mjs @@ -1,3 +1,8 @@ +import { gameTerms } from "../gameTerms.mjs"; + +const { StringField } = foundry.data.fields; +const { FatePath } = gameTerms; + export function registerMetaSettings() { game.settings.register(`ripcrypt`, `dc`, { scope: `world`, @@ -15,15 +20,23 @@ export function registerMetaSettings() { initial: 8, config: false, requiresReload: false, - onChange: async () => {}, + onChange: async () => { + ui.delveDice.animate({ parts: [`sandsOfFate`] }); + }, }); game.settings.register(`ripcrypt`, `currentFate`, { scope: `world`, - type: String, + type: new StringField({ + blank: false, + nullable: false, + initial: FatePath.NORTH, + }), config: false, requiresReload: false, - onChange: async () => {}, + onChange: async () => { + ui.delveDice.animate({ parts: [`fateCompass`] }); + }, }); game.settings.register(`ripcrypt`, `whoFirst`, { diff --git a/module/settings/worldSettings.mjs b/module/settings/worldSettings.mjs index d7c426a..d193a32 100644 --- a/module/settings/worldSettings.mjs +++ b/module/settings/worldSettings.mjs @@ -1,10 +1,51 @@ +const { NumberField, StringField } = foundry.data.fields; + export function registerWorldSettings() { game.settings.register(`ripcrypt`, `showDelveTour`, { name: `Delve Tour Popup`, scope: `world`, type: Boolean, - config: true, + config: false, default: true, requiresReload: false, }); + + game.settings.register(`ripcrypt`, `sandsOfFateInitial`, { + name: `RipCrypt.setting.sandsOfFateInitial.name`, + hint: `RipCrypt.setting.sandsOfFateInitial.hint`, + scope: `world`, + config: true, + requiresReload: false, + type: new NumberField({ + required: true, + min: 1, + step: 1, + max: 10, + initial: 8, + }), + onChange: async (newInitialSands) => { + const currentSands = game.settings.get(`ripcrypt`, `sandsOfFate`); + if (newInitialSands <= currentSands) { + game.settings.set(`ripcrypt`, `sandsOfFate`, newInitialSands); + }; + }, + }); + + game.settings.register(`ripcrypt`, `onCrypticEvent`, { + name: `RipCrypt.setting.onCrypticEvent.name`, + hint: `RipCrypt.setting.onCrypticEvent.hint`, + scope: `world`, + config: true, + requiresReload: false, + type: new StringField({ + required: true, + initial: `notif`, + choices: { + "notif": `RipCrypt.setting.onCrypticEvent.options.notif`, + "pause": `RipCrypt.setting.onCrypticEvent.options.pause`, + "both": `RipCrypt.setting.onCrypticEvent.options.both`, + "nothing": `RipCrypt.setting.onCrypticEvent.options.nothing`, + }, + }), + }); }; diff --git a/module/utils/distanceBetweenFates.mjs b/module/utils/fates.mjs similarity index 69% rename from module/utils/distanceBetweenFates.mjs rename to module/utils/fates.mjs index 8c57240..d1fb6dd 100644 --- a/module/utils/distanceBetweenFates.mjs +++ b/module/utils/fates.mjs @@ -31,3 +31,21 @@ export function distanceBetweenFates(start, end) { }; return 3; }; + +const fateOrder = [ + FatePath.WEST, // to make the .find not integer overflow + FatePath.NORTH, + FatePath.EAST, + FatePath.SOUTH, + FatePath.WEST, +]; + +export function nextFate(fate) { + const fateIndex = fateOrder.findIndex(f => f === fate); + return fateOrder[fateIndex + 1]; +}; + +export function previousFate(fate) { + const fateIndex = fateOrder.lastIndexOf(fate); + return fateOrder[fateIndex - 1]; +}; diff --git a/templates/Apps/DelveDiceHUD/tour/current.hbs b/templates/Apps/DelveDiceHUD/tour/current.hbs index 9e3d423..18b98d9 100644 --- a/templates/Apps/DelveDiceHUD/tour/current.hbs +++ b/templates/Apps/DelveDiceHUD/tour/current.hbs @@ -1,12 +1,13 @@
- - 8 + + {{sandsOfFate}}
From 67753ce3e7bad8c140e5ca8c9380fcd779852bdd Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 6 Mar 2025 18:47:41 -0700 Subject: [PATCH 043/164] Add the Flect craft list (closes #21) --- templates/Apps/HeroCraftCardV1/content.hbs | 25 ++++++++++++++++++++++ templates/Apps/HeroCraftCardV1/style.css | 1 + 2 files changed, 26 insertions(+) diff --git a/templates/Apps/HeroCraftCardV1/content.hbs b/templates/Apps/HeroCraftCardV1/content.hbs index 44cc1d4..cc9fa23 100644 --- a/templates/Apps/HeroCraftCardV1/content.hbs +++ b/templates/Apps/HeroCraftCardV1/content.hbs @@ -23,4 +23,29 @@ {{/if}} {{/each}} + +
+ Flect + Details +
+
    + {{#each craft.flect as | craft |}} + {{#if craft}} +
  1. + {{ craft.name }} + {{#if craft.use}} + + {{/if}} +
  2. + {{else}} +
  3. + {{/if}} + {{/each}} +
diff --git a/templates/Apps/HeroCraftCardV1/style.css b/templates/Apps/HeroCraftCardV1/style.css index 2b81e63..a0dd159 100644 --- a/templates/Apps/HeroCraftCardV1/style.css +++ b/templates/Apps/HeroCraftCardV1/style.css @@ -44,6 +44,7 @@ } [data-aspect="focus"] { --row: 6; --col: 1; } + [data-aspect="flect"] { --row: 6; --col: 2; } [data-aspect] { &.aspect-header { From 5876d5fe980e00a094bb859225492d17009136ea Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 7 Mar 2025 19:47:18 -0700 Subject: [PATCH 044/164] Add system summary --- system.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system.json b/system.json index 9924bfe..68d0f25 100644 --- a/system.json +++ b/system.json @@ -1,7 +1,7 @@ { "id": "ripcrypt", "title": "RipCrypt", - "description": "", + "description": "A dungeon sprint RPG. Faster than an arrow to the eye. Smoother than a clean blade. Compact with consequences.", "version": "0.0.1", "compatibility": { "minimum": 13, From bd3c8d9accadfb2feecdda92399a6e2c994ed79f Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 7 Mar 2025 19:48:08 -0700 Subject: [PATCH 045/164] Remove unused CSS import --- templates/Apps/apps.css | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/Apps/apps.css b/templates/Apps/apps.css index 1a261a4..2ec33cc 100644 --- a/templates/Apps/apps.css +++ b/templates/Apps/apps.css @@ -1,6 +1,5 @@ @import url("./AllItemSheetV1/style.css"); @import url("./CombinedHeroSheet/style.css"); -@import url("./CryptApp/style.css"); @import url("./DelveDiceHUD/style.css"); @import url("./DicePool/style.css"); @import url("./HeroCraftCardV1/style.css"); From f1d9fe187c37d598dc9ffac8a104597b152dbf1c Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 7 Mar 2025 19:50:45 -0700 Subject: [PATCH 046/164] Add fract to the craft card (closes #22) --- templates/Apps/HeroCraftCardV1/content.hbs | 25 ++++++++++++++++++++++ templates/Apps/HeroCraftCardV1/style.css | 1 + 2 files changed, 26 insertions(+) diff --git a/templates/Apps/HeroCraftCardV1/content.hbs b/templates/Apps/HeroCraftCardV1/content.hbs index cc9fa23..8be66ce 100644 --- a/templates/Apps/HeroCraftCardV1/content.hbs +++ b/templates/Apps/HeroCraftCardV1/content.hbs @@ -48,4 +48,29 @@ {{/if}} {{/each}} + +
+ Fract + Details +
+
    + {{#each craft.fract as | craft |}} + {{#if craft}} +
  1. + {{ craft.name }} + {{#if craft.use}} + + {{/if}} +
  2. + {{else}} +
  3. + {{/if}} + {{/each}} +
diff --git a/templates/Apps/HeroCraftCardV1/style.css b/templates/Apps/HeroCraftCardV1/style.css index a0dd159..297844b 100644 --- a/templates/Apps/HeroCraftCardV1/style.css +++ b/templates/Apps/HeroCraftCardV1/style.css @@ -45,6 +45,7 @@ [data-aspect="focus"] { --row: 6; --col: 1; } [data-aspect="flect"] { --row: 6; --col: 2; } + [data-aspect="fract"] { --row: 11; --col: 1; } [data-aspect] { &.aspect-header { From a830adbd2da2dcd54a138b7b86a2ac608ec07d47 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 7 Mar 2025 22:30:12 -0700 Subject: [PATCH 047/164] Get most of the Aura display implemented added to the HTML --- assets/caster-silhouette.v1.svg | 2 +- templates/Apps/HeroCraftCardV1/content.hbs | 22 +++++++++++ templates/Apps/HeroCraftCardV1/style.css | 44 ++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/assets/caster-silhouette.v1.svg b/assets/caster-silhouette.v1.svg index ed60c3e..9b53fcc 100644 --- a/assets/caster-silhouette.v1.svg +++ b/assets/caster-silhouette.v1.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/templates/Apps/HeroCraftCardV1/content.hbs b/templates/Apps/HeroCraftCardV1/content.hbs index 8be66ce..c9254da 100644 --- a/templates/Apps/HeroCraftCardV1/content.hbs +++ b/templates/Apps/HeroCraftCardV1/content.hbs @@ -1,4 +1,26 @@
+
+ Glimcraft +
+
+
10
+
8
+
6
+
4
+ +
+
+ Aura + 4 + 6 +
+
+
+
Focus Details diff --git a/templates/Apps/HeroCraftCardV1/style.css b/templates/Apps/HeroCraftCardV1/style.css index 297844b..b30cd9f 100644 --- a/templates/Apps/HeroCraftCardV1/style.css +++ b/templates/Apps/HeroCraftCardV1/style.css @@ -29,6 +29,49 @@ font-weight: bold; } + .aura-container { + grid-column: 1 / -1; + grid-row: 2 / span 4; + display: grid; + grid-template-columns: repeat(7, minmax(0, 1fr)); + grid-template-rows: minmax(0, 1fr); + position: relative; + } + + .circle-fragment, .full-circle { + display: flex; + justify-content: center; + align-items: center; + } + + .circle-fragment { + border-top-left-radius: 24% 100%; + border-bottom-left-radius: 25% 100%; + border-left: 2px dashed var(--accent-3); + margin-right: -5%; + } + + .full-circle { + border: 2px dashed var(--accent-3); + flex-grow: 0; + border-radius: 999px; + width: 80%; + aspect-ratio: 1; + align-self: center; + justify-self: center; + grid-row: 1; + grid-column: 4; + } + + .caster-silhouette { + grid-column: 4 / span 4; + grid-row: 1; + position: absolute; + left: 2rem; + width: 70%; + bottom: -10px; + } + .craft-list { display: grid; grid-template-rows: subgrid; @@ -49,6 +92,7 @@ [data-aspect] { &.aspect-header { + z-index: 1; grid-row: var(--row); grid-column: var(--col); } From 89b51a01e6d09ace61fc48e3af1a539611d7d3a3 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 8 Mar 2025 22:39:07 -0700 Subject: [PATCH 048/164] Get the aura display finished for the Craft Card (closes #23) --- templates/Apps/HeroCraftCardV1/content.hbs | 9 ++++-- templates/Apps/HeroCraftCardV1/style.css | 33 ++++++++++++++++++++++ templates/css/elements/span.css | 7 +++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/templates/Apps/HeroCraftCardV1/content.hbs b/templates/Apps/HeroCraftCardV1/content.hbs index c9254da..a9348d6 100644 --- a/templates/Apps/HeroCraftCardV1/content.hbs +++ b/templates/Apps/HeroCraftCardV1/content.hbs @@ -10,13 +10,16 @@
Aura - 4 - 6 +
+ + + +
diff --git a/templates/Apps/HeroCraftCardV1/style.css b/templates/Apps/HeroCraftCardV1/style.css index b30cd9f..cff3266 100644 --- a/templates/Apps/HeroCraftCardV1/style.css +++ b/templates/Apps/HeroCraftCardV1/style.css @@ -72,6 +72,39 @@ bottom: -10px; } + .aura-values { + grid-row: 1; + grid-column: -3 / -1; + display: flex; + justify-content: center; + align-items: center; + z-index: 3; + + .dual-pill { + border-radius: 999px; + background: var(--accent-1); + display: flex; + flex-direction: row; + gap: 0.25rem; + align-items: center; + padding-left: 8px; + margin-left: 1rem; + margin-bottom: 1.2rem; + } + + .values { + border-radius: 999px; + margin: 2px; + background: var(--base-background); + color: var(--base-text); + padding: 0.125rem 0.5rem; + display: flex; + flex-direction: row; + gap: 0.5rem; + --slash-color: var(--accent-1); + } + } + .craft-list { display: grid; grid-template-rows: subgrid; diff --git a/templates/css/elements/span.css b/templates/css/elements/span.css index b4a2cef..85a099b 100644 --- a/templates/css/elements/span.css +++ b/templates/css/elements/span.css @@ -10,6 +10,13 @@ overflow: hidden; } + &.slash { + width: 2px; + background: var(--slash-color, currentColor); + border-radius: 999px; + transform: rotate(var(--slash-rotation, 15deg)); + } + /* Makes it so that spans are never less than the font size */ &:empty::before { content: "\200b"; From 4f35db01b6e7cd5a88cc5a45be93c9be20bba33c Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 9 Mar 2025 00:16:21 -0700 Subject: [PATCH 049/164] Add the derived data for the aura ranges --- module/Apps/ActorSheets/HeroCraftCardV1.mjs | 35 +++++++++++++++++++++ module/api.mjs | 2 ++ module/data/Actor/Hero.mjs | 8 +++++ module/utils/rank.mjs | 6 ++++ templates/Apps/HeroCraftCardV1/content.hbs | 6 ++-- 5 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 module/utils/rank.mjs diff --git a/module/Apps/ActorSheets/HeroCraftCardV1.mjs b/module/Apps/ActorSheets/HeroCraftCardV1.mjs index 7d78029..e931f17 100644 --- a/module/Apps/ActorSheets/HeroCraftCardV1.mjs +++ b/module/Apps/ActorSheets/HeroCraftCardV1.mjs @@ -8,6 +8,7 @@ import { Logger } from "../../utils/Logger.mjs"; const { HandlebarsApplicationMixin } = foundry.applications.api; const { ActorSheetV2 } = foundry.applications.sheets; const { ContextMenu } = foundry.applications.ui; +const { deepClone } = foundry.utils; export class HeroCraftCardV1 extends GenericAppMixin(HandlebarsApplicationMixin(ActorSheetV2)) { @@ -79,12 +80,46 @@ export class HeroCraftCardV1 extends GenericAppMixin(HandlebarsApplicationMixin( ctx = await super._preparePartContext(partId, ctx, opts); ctx.actor = this.document; + ctx = await HeroCraftCardV1.prepareAura(ctx); ctx = await HeroCraftCardV1.prepareCraft(ctx); Logger.debug(`Context:`, ctx); return ctx; }; + static async prepareAura(ctx) { + const { normal, heavy } = ctx.aura = deepClone(ctx.actor.system.aura); + + ctx.auraClasses = {}; + if (heavy >= 4) { + ctx.auraClasses.four = `heavy`; + } + if (heavy >= 6) { + ctx.auraClasses.six = `heavy`; + } + if (heavy >= 8) { + ctx.auraClasses.eight = `heavy`; + } + if (heavy >= 10) { + ctx.auraClasses.ten = `heavy`; + } + + if (normal >= 4) { + ctx.auraClasses.four = `normal`; + } + if (normal >= 6) { + ctx.auraClasses.six = `normal`; + } + if (normal >= 8) { + ctx.auraClasses.eight = `normal`; + } + if (normal >= 10) { + ctx.auraClasses.ten = `normal`; + } + + return ctx; + }; + static async prepareCraft(ctx) { ctx.craft = {}; const aspects = Object.values(gameTerms.Aspects); diff --git a/module/api.mjs b/module/api.mjs index d2e50ac..a56f7b1 100644 --- a/module/api.mjs +++ b/module/api.mjs @@ -8,6 +8,7 @@ import { RichEditor } from "./Apps/RichEditor.mjs"; // Util imports import { distanceBetweenFates, nextFate, previousFate } from "./utils/fates.mjs"; import { documentSorter } from "./consts.mjs"; +import { rankToInteger } from "./utils/rank.mjs"; const { deepFreeze } = foundry.utils; @@ -28,6 +29,7 @@ Object.defineProperty( distanceBetweenFates, nextFate, previousFate, + rankToInteger, }, }), writable: false, diff --git a/module/data/Actor/Hero.mjs b/module/data/Actor/Hero.mjs index 2f0dc15..6f589d6 100644 --- a/module/data/Actor/Hero.mjs +++ b/module/data/Actor/Hero.mjs @@ -1,5 +1,6 @@ import { derivedMaximumBar } from "../helpers.mjs"; import { gameTerms } from "../../gameTerms.mjs"; +import { rankToInteger } from "../../utils/rank.mjs"; import { sumReduce } from "../../utils/sumReduce.mjs"; const { fields } = foundry.data; @@ -122,6 +123,13 @@ export class HeroData extends foundry.abstract.TypeDataModel { prepareBaseData() { super.prepareBaseData(); + // Calculate the person's base Crafting aura + const rank = rankToInteger(this.level.rank); + this.aura = { + normal: ( rank + 1 ) * 2, + heavy: ( rank + 2 ) * 2, + }; + this.guts.max = 0; // The limitations imposed on things like inventory spaces and equipped diff --git a/module/utils/rank.mjs b/module/utils/rank.mjs new file mode 100644 index 0000000..806703a --- /dev/null +++ b/module/utils/rank.mjs @@ -0,0 +1,6 @@ +import { gameTerms } from "../gameTerms.mjs"; + +export function rankToInteger(rankName) { + return Object.values(gameTerms.Rank) + .findIndex(r => r === rankName) + 1; +}; diff --git a/templates/Apps/HeroCraftCardV1/content.hbs b/templates/Apps/HeroCraftCardV1/content.hbs index a9348d6..29bba66 100644 --- a/templates/Apps/HeroCraftCardV1/content.hbs +++ b/templates/Apps/HeroCraftCardV1/content.hbs @@ -14,11 +14,11 @@ >
- Aura +
- + {{aura.normal}} - + {{aura.heavy}}
From f46bd6b5d3da875c2111ee8f545a90b91a66b546 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Mon, 10 Mar 2025 21:52:49 -0600 Subject: [PATCH 050/164] Localize the craft card (closes #35) --- langs/en-ca.json | 9 ++++++++- templates/Apps/HeroCraftCardV1/content.hbs | 20 ++++++++++---------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/langs/en-ca.json b/langs/en-ca.json index 8199113..c40e853 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -52,6 +52,7 @@ "fract": "Fract", "focus": "Focus" }, + "aura": "Aura", "cost": "Cost", "currency": { "gold": "Gold", @@ -61,6 +62,7 @@ "damage": "Damage", "delete": "Delete", "description": "Description", + "details": "Details", "difficulties": { "easy": "Easy", "normal": "Normal", @@ -76,6 +78,7 @@ "equipped": "Equipped", "fate": "Fate", "gear": "Gear", + "glimcraft": "Glimcraft", "glory": "Glory", "guts": "Guts", "location": "Location", @@ -188,7 +191,11 @@ "set-fate-to": "Set Fate to {ordinal}", "current-tour": "Current Delve Tour", "next-tour": "Next Delve Tour", - "prev-tour": "Previous Delve Tour" + "prev-tour": "Previous Delve Tour", + "auras": { + "normal": "The distance of your aura normally", + "heavy": "The distance of your aura when using Heavycraft" + } } } } diff --git a/templates/Apps/HeroCraftCardV1/content.hbs b/templates/Apps/HeroCraftCardV1/content.hbs index 29bba66..e30ee69 100644 --- a/templates/Apps/HeroCraftCardV1/content.hbs +++ b/templates/Apps/HeroCraftCardV1/content.hbs @@ -1,6 +1,6 @@
- Glimcraft + {{rc-i18n "RipCrypt.common.glimcraft"}}
10
@@ -14,19 +14,19 @@ >
- +
- {{aura.normal}} + {{aura.normal}} - {{aura.heavy}} + {{aura.heavy}}
- Focus - Details + {{rc-i18n "RipCrypt.common.aspectNames.focus"}} + {{rc-i18n "RipCrypt.common.details"}}
    {{#each craft.focus as | craft |}} @@ -50,8 +50,8 @@
- Flect - Details + {{rc-i18n "RipCrypt.common.aspectNames.flect"}} + {{rc-i18n "RipCrypt.common.details"}}
    {{#each craft.flect as | craft |}} @@ -75,8 +75,8 @@
- Fract - Details + {{rc-i18n "RipCrypt.common.aspectNames.fract"}} + {{rc-i18n "RipCrypt.common.details"}}
    {{#each craft.fract as | craft |}} From fd2899395286e8b20a1ffc4e68e5e4902774fec2 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Mon, 10 Mar 2025 22:04:36 -0600 Subject: [PATCH 051/164] Clean-up data preparation --- module/Apps/ActorSheets/HeroCraftCardV1.mjs | 30 +-------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/module/Apps/ActorSheets/HeroCraftCardV1.mjs b/module/Apps/ActorSheets/HeroCraftCardV1.mjs index e931f17..66d78d4 100644 --- a/module/Apps/ActorSheets/HeroCraftCardV1.mjs +++ b/module/Apps/ActorSheets/HeroCraftCardV1.mjs @@ -88,35 +88,7 @@ export class HeroCraftCardV1 extends GenericAppMixin(HandlebarsApplicationMixin( }; static async prepareAura(ctx) { - const { normal, heavy } = ctx.aura = deepClone(ctx.actor.system.aura); - - ctx.auraClasses = {}; - if (heavy >= 4) { - ctx.auraClasses.four = `heavy`; - } - if (heavy >= 6) { - ctx.auraClasses.six = `heavy`; - } - if (heavy >= 8) { - ctx.auraClasses.eight = `heavy`; - } - if (heavy >= 10) { - ctx.auraClasses.ten = `heavy`; - } - - if (normal >= 4) { - ctx.auraClasses.four = `normal`; - } - if (normal >= 6) { - ctx.auraClasses.six = `normal`; - } - if (normal >= 8) { - ctx.auraClasses.eight = `normal`; - } - if (normal >= 10) { - ctx.auraClasses.ten = `normal`; - } - + ctx.aura = deepClone(ctx.actor.system.aura); return ctx; }; From 96ef2ba56f8eed9c45dd8ed4a4bc37c888710f23 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Wed, 12 Mar 2025 22:40:19 -0600 Subject: [PATCH 052/164] Add JSdoc for the API --- module/utils/rank.mjs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/module/utils/rank.mjs b/module/utils/rank.mjs index 806703a..81f6b59 100644 --- a/module/utils/rank.mjs +++ b/module/utils/rank.mjs @@ -1,5 +1,12 @@ import { gameTerms } from "../gameTerms.mjs"; +/** + * Converts a rank's name into an integer form for use in mathematical calculations + * that rely on rank. + * + * @param {Novice|Adept|Expert|Master} rankName The rank to convert into an integer + * @returns An integer between 1 and 4 + */ export function rankToInteger(rankName) { return Object.values(gameTerms.Rank) .findIndex(r => r === rankName) + 1; From af5cf4acd5b61eee53ead4060e5b4a2480bc1e3e Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 13 Mar 2025 00:19:03 -0600 Subject: [PATCH 053/164] Begin working on laying the groundwork for the Ammo Tracker / popover --- module/Apps/ActorSheets/HeroSkillsCardV1.mjs | 6 +++ module/Apps/popovers/AmmoTracker.mjs | 45 +++++++++++++++++++ .../Apps/popovers/AmmoTracker/content.hbs | 3 ++ 3 files changed, 54 insertions(+) create mode 100644 module/Apps/popovers/AmmoTracker.mjs create mode 100644 templates/Apps/popovers/AmmoTracker/content.hbs diff --git a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs index b0e83fb..b9992b5 100644 --- a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs @@ -43,6 +43,12 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin async _onRender(context, options) { await super._onRender(context, options); HeroSkillsCardV1._onRender.bind(this)(context, options); + + const ammo = this.element.querySelector(`.ammo`); + + ammo.addEventListener(`mouseenter`, () => {console.log(`mouseenter-ing`)}); + + ammo.addEventListener(`contextmenu`, () => {console.log(`right-clicking`)}); }; static async _onRender(_context, options) { diff --git a/module/Apps/popovers/AmmoTracker.mjs b/module/Apps/popovers/AmmoTracker.mjs new file mode 100644 index 0000000..b216185 --- /dev/null +++ b/module/Apps/popovers/AmmoTracker.mjs @@ -0,0 +1,45 @@ +import { filePath } from "../../consts.mjs"; +import { GenericAppMixin } from "../GenericApp.mjs"; + +const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; + +export class AmmoTracker extends GenericAppMixin(HandlebarsApplicationMixin(ApplicationV2)) { + // #region Options + static DEFAULT_OPTIONS = { + classes: [ + `ripcrypt--AmmoTracker`, + ], + window: { + frame: false, + positioned: true, + resizable: false, + minimizable: false, + }, + position: { + width: 100, + height: 30, + }, + actions: {}, + }; + + static PARTS = { + main: { + template: filePath(`templates/Apps/popovers/AmmoTracker/content.hbs`), + }, + }; + // #endregion + + // #region Instance Data + // #endregion + + // #region Lifecycle + async _onFirstRender(context, options) { + await super._onFirstRender(context, options); + const ammoContainer = this.element.querySelector(`.ammo`); + console.dir(ammoContainer); + }; + // #endregion + + // #region Actions + // #endregion +}; diff --git a/templates/Apps/popovers/AmmoTracker/content.hbs b/templates/Apps/popovers/AmmoTracker/content.hbs new file mode 100644 index 0000000..80f0024 --- /dev/null +++ b/templates/Apps/popovers/AmmoTracker/content.hbs @@ -0,0 +1,3 @@ +
    + Hello +
    \ No newline at end of file From 8e8202f8a6508404b71ad0bf2cc344a981481ef2 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 13 Mar 2025 23:36:25 -0600 Subject: [PATCH 054/164] Getting the popover Application working on the most superficial level, and creating a generic popover mixin --- module/Apps/ActorSheets/HeroSkillsCardV1.mjs | 63 +++++++- module/Apps/popovers/AmmoTracker.mjs | 158 ++++++++++++++++++- module/Apps/popovers/GenericPopoverMixin.mjs | 4 + module/api.mjs | 2 + module/consts.mjs | 11 ++ templates/Apps/HeroSkillsCardV1/content.hbs | 8 +- templates/Apps/HeroSkillsCardV1/style.css | 10 ++ 7 files changed, 244 insertions(+), 12 deletions(-) create mode 100644 module/Apps/popovers/GenericPopoverMixin.mjs diff --git a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs index b9992b5..78448a1 100644 --- a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs @@ -1,9 +1,10 @@ import { deleteItemFromElement, editItemFromElement } from "../utils.mjs"; -import { documentSorter, filePath } from "../../consts.mjs"; +import { documentSorter, filePath, getTooltipDelay } from "../../consts.mjs"; import { gameTerms } from "../../gameTerms.mjs"; import { GenericAppMixin } from "../GenericApp.mjs"; import { localizer } from "../../utils/Localizer.mjs"; import { Logger } from "../../utils/Logger.mjs"; +import { AmmoTracker } from "../popovers/AmmoTracker.mjs"; const { HandlebarsApplicationMixin } = foundry.applications.api; const { ActorSheetV2 } = foundry.applications.sheets; @@ -39,16 +40,19 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin }; // #endregion + // #region Instance Data + /** @type {number | undefined} */ + #ammoTrackerHoverTimeout = null; + + /** @type {AmmoTracker | null} */ + #ammoTracker = null; + // #endregion + // #region Lifecycle async _onRender(context, options) { await super._onRender(context, options); HeroSkillsCardV1._onRender.bind(this)(context, options); - - const ammo = this.element.querySelector(`.ammo`); - - ammo.addEventListener(`mouseenter`, () => {console.log(`mouseenter-ing`)}); - - ammo.addEventListener(`contextmenu`, () => {console.log(`right-clicking`)}); + await this.#createAmmoTrackerEvents(); }; static async _onRender(_context, options) { @@ -81,6 +85,13 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin ); }; + async #createAmmoTrackerEvents() { + const ammoInfoIcon = this.element.querySelector(`.ammo-info-icon`); + ammoInfoIcon.addEventListener(`pointerenter`, this.#ammoInfoPointerEnter.bind(this)); + ammoInfoIcon.addEventListener(`pointerout`, this.#ammoInfoPointerOut.bind(this)); + ammoInfoIcon.addEventListener(`click`, this.#ammoInfoClick.bind(this)); + }; + async _preparePartContext(partId, ctx, opts) { ctx = await super._preparePartContext(partId, ctx, opts); ctx.actor = this.document; @@ -177,6 +188,44 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin }; // #endregion + // #region Event Listeners + /** + * @param {PointerEvent} event + */ + async #ammoInfoPointerEnter(event) { + console.log(event.x, event.y); + const { x, y } = event; + + this.#ammoTrackerHoverTimeout = setTimeout( + () => { + this.#ammoTrackerHoverTimeout = null; + const tracker = new AmmoTracker({ + popover: { + framed: false, + x, y, + }, + }); + tracker.render({ force: true }); + this.#ammoTracker = tracker; + }, + getTooltipDelay(), + ); + }; + + async #ammoInfoPointerOut() { + if (this.#ammoTracker) { + // this.#ammoTracker.close(); + }; + + if (this.#ammoTrackerHoverTimeout !== null) { + clearTimeout(this.#ammoTrackerHoverTimeout); + this.#ammoTrackerHoverTimeout = null; + }; + }; + + async #ammoInfoClick() {}; + // #endregion + // #region Actions // #endregion }; diff --git a/module/Apps/popovers/AmmoTracker.mjs b/module/Apps/popovers/AmmoTracker.mjs index b216185..b6d59db 100644 --- a/module/Apps/popovers/AmmoTracker.mjs +++ b/module/Apps/popovers/AmmoTracker.mjs @@ -1,9 +1,9 @@ import { filePath } from "../../consts.mjs"; -import { GenericAppMixin } from "../GenericApp.mjs"; +import { GenericPopoverMixin } from "./GenericPopoverMixin.mjs"; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; -export class AmmoTracker extends GenericAppMixin(HandlebarsApplicationMixin(ApplicationV2)) { +export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin(ApplicationV2)) { // #region Options static DEFAULT_OPTIONS = { classes: [ @@ -30,13 +30,163 @@ export class AmmoTracker extends GenericAppMixin(HandlebarsApplicationMixin(Appl // #endregion // #region Instance Data + popover = {}; + constructor({ popover, ...options}) { + + // For when the caller doesn't provide anything, we want this to behave + // like a normal Application. + popover.framed ??= true; + popover.locked ??= true; + + if (popover.framed) { + options.window.frame = true; + options.window.minimizable = true; + } + + super(options); + + this.popover = popover; + }; // #endregion // #region Lifecycle + _insertElement(element) { + // console.log(this.popover); + const existing = document.getElementById(element.id); + if (existing) { + existing.replaceWith(element); + return; + }; + // const pos = element.getBoundingClientRect(); + + // const horizontalOffset = Math.floor(pos.width / 2); + // console.log({ x: this.popover.x, y: this.popover.y, height: pos.height, xOffset: horizontalOffset }); + + element.style.position = `absolute`; + element.style.color = `black`; + element.style.background = `greenyellow`; + element.style[`z-index`] = 10000; + // element.style.left = `${this.popover.x - horizontalOffset}px`; + // element.style.top = `${this.popover.y - pos.height}px`; + // this.position = { + // left: this.popover.x - horizontalOffset, + // top: this.popover.y - pos.height, + // }; + + // standard addition + document.body.append(element); + }; + + // _updatePosition(position) { + // const pos = super._updatePosition(position); + // if (this.popover.framed) { return pos }; + + // delete pos.left; + // delete pos.top; + + // const el = this.element; + // let bounds; + // let width, height; + + // // Implicit height + // if ( true ) { + // Object.assign(el.style, {width: `${width}px`, height: ""}); + // bounds = el.getBoundingClientRect(); + // height = bounds.height; + // } + + // // Implicit width + // if ( true ) { + // Object.assign(el.style, {height: `${height}px`, width: ""}); + // bounds = el.getBoundingClientRect(); + // width = bounds.width; + // } + + // // const { width, height } = this.element.getBoundingClientRect(); + // const horizontalOffset = Math.floor(width / 2); + // pos.left = this.popover.x - horizontalOffset; + // pos.top = this.popover.y - height; + + // console.log({ x: this.popover.x, y: this.popover.y, height, xOffset: horizontalOffset, width }); + + // return pos; + // } + + /** + * @override + * Custom implementation in order to make it show up approximately where I + * want it to when being created. + */ + _updatePosition(position) { + if ( !this.element ) { return position }; + const el = this.element; + let {width, height, left, top, scale} = position; + scale ??= 1.0; + const computedStyle = getComputedStyle(el); + let minWidth = ApplicationV2.parseCSSDimension(computedStyle.minWidth, el.parentElement.offsetWidth) || 0; + let maxWidth = ApplicationV2.parseCSSDimension(computedStyle.maxWidth, el.parentElement.offsetWidth) || Infinity; + let minHeight = ApplicationV2.parseCSSDimension(computedStyle.minHeight, el.parentElement.offsetHeight) || 0; + let maxHeight = ApplicationV2.parseCSSDimension(computedStyle.maxHeight, el.parentElement.offsetHeight) || Infinity; + let bounds = el.getBoundingClientRect(); + const {clientWidth, clientHeight} = document.documentElement; + + // Explicit width + const autoWidth = width === `auto`; + if ( !autoWidth ) { + const targetWidth = Number(width || bounds.width); + minWidth = parseInt(minWidth) || 0; + maxWidth = parseInt(maxWidth) || (clientWidth / scale); + width = Math.clamp(targetWidth, minWidth, maxWidth); + } + + // Explicit height + const autoHeight = height === `auto`; + if ( !autoHeight ) { + const targetHeight = Number(height || bounds.height); + minHeight = parseInt(minHeight) || 0; + maxHeight = parseInt(maxHeight) || (clientHeight / scale); + height = Math.clamp(targetHeight, minHeight, maxHeight); + } + + // Implicit height + if ( autoHeight ) { + Object.assign(el.style, {width: `${width}px`, height: ``}); + bounds = el.getBoundingClientRect(); + height = bounds.height; + } + + // Implicit width + if ( autoWidth ) { + Object.assign(el.style, {height: `${height}px`, width: ``}); + bounds = el.getBoundingClientRect(); + width = bounds.width; + } + + // Left Offset + const scaledWidth = width * scale; + const targetLeft = left ?? (this.popover.x - Math.floor( scaledWidth / 2 )); + const maxLeft = Math.max(clientWidth - scaledWidth, 0); + left = Math.clamp(targetLeft, 0, maxLeft); + + // Top Offset + const scaledHeight = height * scale; + const targetTop = top ?? (this.popover.y - scaledHeight); + const maxTop = Math.max(clientHeight - scaledHeight, 0); + top = Math.clamp(targetTop, 0, maxTop); + + // Scale + scale ??= 1.0; + return { + width: autoWidth ? `auto` : width, + height: autoHeight ? `auto` : height, + left, + top, + scale, + }; + } + async _onFirstRender(context, options) { await super._onFirstRender(context, options); - const ammoContainer = this.element.querySelector(`.ammo`); - console.dir(ammoContainer); }; // #endregion diff --git a/module/Apps/popovers/GenericPopoverMixin.mjs b/module/Apps/popovers/GenericPopoverMixin.mjs new file mode 100644 index 0000000..89b7bfd --- /dev/null +++ b/module/Apps/popovers/GenericPopoverMixin.mjs @@ -0,0 +1,4 @@ +export function GenericPopoverMixin(HandlebarsApp) { + class GenericRipCryptPopover extends HandlebarsApp {}; + return GenericRipCryptPopover; +}; diff --git a/module/api.mjs b/module/api.mjs index a56f7b1..ee40143 100644 --- a/module/api.mjs +++ b/module/api.mjs @@ -9,6 +9,7 @@ import { RichEditor } from "./Apps/RichEditor.mjs"; import { distanceBetweenFates, nextFate, previousFate } from "./utils/fates.mjs"; import { documentSorter } from "./consts.mjs"; import { rankToInteger } from "./utils/rank.mjs"; +import { AmmoTracker } from "./Apps/popovers/AmmoTracker.mjs"; const { deepFreeze } = foundry.utils; @@ -18,6 +19,7 @@ Object.defineProperty( { value: deepFreeze({ Apps: { + AmmoTracker, DicePool, CombinedHeroSheet, HeroSummaryCardV1, diff --git a/module/consts.mjs b/module/consts.mjs index 84d8740..242d332 100644 --- a/module/consts.mjs +++ b/module/consts.mjs @@ -54,3 +54,14 @@ export function documentSorter(a, b) { }; return Math.sign(a.name.localeCompare(b.name)); }; + +// MARK: getTooltipDelay +/** + * Retrieves the configured minimum delay between the user hovering an element + * and a tooltip showing up. Used for the pseudo-tooltip Applications that I use. + * + * @returns The number of milliseconds for the timeout + */ +export function getTooltipDelay() { + return 1000; // game.tooltip.constructor.TOOLTIP_ACTIVATION_MS; +}; diff --git a/templates/Apps/HeroSkillsCardV1/content.hbs b/templates/Apps/HeroSkillsCardV1/content.hbs index eee8d4a..776f673 100644 --- a/templates/Apps/HeroSkillsCardV1/content.hbs +++ b/templates/Apps/HeroSkillsCardV1/content.hbs @@ -104,7 +104,13 @@ {{/each}}
-
+
+
{{ rc-i18n "RipCrypt.common.ammo"}}
diff --git a/templates/Apps/HeroSkillsCardV1/style.css b/templates/Apps/HeroSkillsCardV1/style.css index 9c092ca..c19aaff 100644 --- a/templates/Apps/HeroSkillsCardV1/style.css +++ b/templates/Apps/HeroSkillsCardV1/style.css @@ -116,6 +116,16 @@ --input-background: var(--base-background); --input-text: var(--base-text); + &.with-icon { + grid-template-columns: min-content minmax(0, 1.5fr) minmax(0, 1fr); + gap: 4px; + padding: 2px 0 2px 4px; + .label { + padding: 0; + } + } + + .input { margin: 2px; border-radius: 999px; From 77abcb11a98e545f3cf08f0626f1854f627746a8 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 14 Mar 2025 16:33:58 -0600 Subject: [PATCH 055/164] Get the non-framed popover working initially --- module/Apps/ActorSheets/HeroSkillsCardV1.mjs | 7 +- module/Apps/popovers/AmmoTracker.mjs | 162 +------------------ module/Apps/popovers/GenericPopoverMixin.mjs | 121 +++++++++++++- templates/css/common.css | 2 + templates/css/popover.css | 5 + 5 files changed, 132 insertions(+), 165 deletions(-) create mode 100644 templates/css/popover.css diff --git a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs index 78448a1..6921b54 100644 --- a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs @@ -193,8 +193,9 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin * @param {PointerEvent} event */ async #ammoInfoPointerEnter(event) { - console.log(event.x, event.y); - const { x, y } = event; + const pos = event.target.getBoundingClientRect(); + const x = pos.x + Math.floor(pos.width / 2); + const y = pos.y; this.#ammoTrackerHoverTimeout = setTimeout( () => { @@ -214,7 +215,7 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin async #ammoInfoPointerOut() { if (this.#ammoTracker) { - // this.#ammoTracker.close(); + this.#ammoTracker.close(); }; if (this.#ammoTrackerHoverTimeout !== null) { diff --git a/module/Apps/popovers/AmmoTracker.mjs b/module/Apps/popovers/AmmoTracker.mjs index b6d59db..aeb96a9 100644 --- a/module/Apps/popovers/AmmoTracker.mjs +++ b/module/Apps/popovers/AmmoTracker.mjs @@ -7,14 +7,9 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin( // #region Options static DEFAULT_OPTIONS = { classes: [ + `ripcrypt`, `ripcrypt--AmmoTracker`, ], - window: { - frame: false, - positioned: true, - resizable: false, - minimizable: false, - }, position: { width: 100, height: 30, @@ -30,164 +25,9 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin( // #endregion // #region Instance Data - popover = {}; - constructor({ popover, ...options}) { - - // For when the caller doesn't provide anything, we want this to behave - // like a normal Application. - popover.framed ??= true; - popover.locked ??= true; - - if (popover.framed) { - options.window.frame = true; - options.window.minimizable = true; - } - - super(options); - - this.popover = popover; - }; // #endregion // #region Lifecycle - _insertElement(element) { - // console.log(this.popover); - const existing = document.getElementById(element.id); - if (existing) { - existing.replaceWith(element); - return; - }; - // const pos = element.getBoundingClientRect(); - - // const horizontalOffset = Math.floor(pos.width / 2); - // console.log({ x: this.popover.x, y: this.popover.y, height: pos.height, xOffset: horizontalOffset }); - - element.style.position = `absolute`; - element.style.color = `black`; - element.style.background = `greenyellow`; - element.style[`z-index`] = 10000; - // element.style.left = `${this.popover.x - horizontalOffset}px`; - // element.style.top = `${this.popover.y - pos.height}px`; - // this.position = { - // left: this.popover.x - horizontalOffset, - // top: this.popover.y - pos.height, - // }; - - // standard addition - document.body.append(element); - }; - - // _updatePosition(position) { - // const pos = super._updatePosition(position); - // if (this.popover.framed) { return pos }; - - // delete pos.left; - // delete pos.top; - - // const el = this.element; - // let bounds; - // let width, height; - - // // Implicit height - // if ( true ) { - // Object.assign(el.style, {width: `${width}px`, height: ""}); - // bounds = el.getBoundingClientRect(); - // height = bounds.height; - // } - - // // Implicit width - // if ( true ) { - // Object.assign(el.style, {height: `${height}px`, width: ""}); - // bounds = el.getBoundingClientRect(); - // width = bounds.width; - // } - - // // const { width, height } = this.element.getBoundingClientRect(); - // const horizontalOffset = Math.floor(width / 2); - // pos.left = this.popover.x - horizontalOffset; - // pos.top = this.popover.y - height; - - // console.log({ x: this.popover.x, y: this.popover.y, height, xOffset: horizontalOffset, width }); - - // return pos; - // } - - /** - * @override - * Custom implementation in order to make it show up approximately where I - * want it to when being created. - */ - _updatePosition(position) { - if ( !this.element ) { return position }; - const el = this.element; - let {width, height, left, top, scale} = position; - scale ??= 1.0; - const computedStyle = getComputedStyle(el); - let minWidth = ApplicationV2.parseCSSDimension(computedStyle.minWidth, el.parentElement.offsetWidth) || 0; - let maxWidth = ApplicationV2.parseCSSDimension(computedStyle.maxWidth, el.parentElement.offsetWidth) || Infinity; - let minHeight = ApplicationV2.parseCSSDimension(computedStyle.minHeight, el.parentElement.offsetHeight) || 0; - let maxHeight = ApplicationV2.parseCSSDimension(computedStyle.maxHeight, el.parentElement.offsetHeight) || Infinity; - let bounds = el.getBoundingClientRect(); - const {clientWidth, clientHeight} = document.documentElement; - - // Explicit width - const autoWidth = width === `auto`; - if ( !autoWidth ) { - const targetWidth = Number(width || bounds.width); - minWidth = parseInt(minWidth) || 0; - maxWidth = parseInt(maxWidth) || (clientWidth / scale); - width = Math.clamp(targetWidth, minWidth, maxWidth); - } - - // Explicit height - const autoHeight = height === `auto`; - if ( !autoHeight ) { - const targetHeight = Number(height || bounds.height); - minHeight = parseInt(minHeight) || 0; - maxHeight = parseInt(maxHeight) || (clientHeight / scale); - height = Math.clamp(targetHeight, minHeight, maxHeight); - } - - // Implicit height - if ( autoHeight ) { - Object.assign(el.style, {width: `${width}px`, height: ``}); - bounds = el.getBoundingClientRect(); - height = bounds.height; - } - - // Implicit width - if ( autoWidth ) { - Object.assign(el.style, {height: `${height}px`, width: ``}); - bounds = el.getBoundingClientRect(); - width = bounds.width; - } - - // Left Offset - const scaledWidth = width * scale; - const targetLeft = left ?? (this.popover.x - Math.floor( scaledWidth / 2 )); - const maxLeft = Math.max(clientWidth - scaledWidth, 0); - left = Math.clamp(targetLeft, 0, maxLeft); - - // Top Offset - const scaledHeight = height * scale; - const targetTop = top ?? (this.popover.y - scaledHeight); - const maxTop = Math.max(clientHeight - scaledHeight, 0); - top = Math.clamp(targetTop, 0, maxTop); - - // Scale - scale ??= 1.0; - return { - width: autoWidth ? `auto` : width, - height: autoHeight ? `auto` : height, - left, - top, - scale, - }; - } - - async _onFirstRender(context, options) { - await super._onFirstRender(context, options); - }; // #endregion // #region Actions diff --git a/module/Apps/popovers/GenericPopoverMixin.mjs b/module/Apps/popovers/GenericPopoverMixin.mjs index 89b7bfd..dfe2518 100644 --- a/module/Apps/popovers/GenericPopoverMixin.mjs +++ b/module/Apps/popovers/GenericPopoverMixin.mjs @@ -1,4 +1,123 @@ +const { ApplicationV2 } = foundry.applications.api; + export function GenericPopoverMixin(HandlebarsApp) { - class GenericRipCryptPopover extends HandlebarsApp {}; + class GenericRipCryptPopover extends HandlebarsApp { + static DEFAULT_OPTIONS = { + classes: [ + `popover`, + ], + window: { + frame: false, + positioned: true, + resizable: false, + minimizable: false, + }, + actions: {}, + }; + + popover = {}; + constructor({ popover, ...options}) { + + // For when the caller doesn't provide anything, we want this to behave + // like a normal Application instance. + popover.framed ??= true; + popover.locked ??= true; + + if (popover.framed) { + options.window.frame = true; + options.window.minimizable = true; + }; + + super(options); + + this.popover = popover; + }; + + async close(options = {}) { + if (!this.popover.framed) { + options.animate ??= false; + }; + return super.close(options); + }; + + /** + * @override + * Custom implementation in order to make it show up approximately where I + * want it to when being created. + * + * Most of this implementation is identical to the ApplicationV2 + * implementation, the biggest difference is how targetLeft and targetRight + * are calculated. + */ + _updatePosition(position) { + if (!this.element) { return position }; + if (this.popover.framed) { return position } + + const el = this.element; + let {width, height, left, top, scale} = position; + scale ??= 1.0; + const computedStyle = getComputedStyle(el); + let minWidth = ApplicationV2.parseCSSDimension(computedStyle.minWidth, el.parentElement.offsetWidth) || 0; + let maxWidth = ApplicationV2.parseCSSDimension(computedStyle.maxWidth, el.parentElement.offsetWidth) || Infinity; + let minHeight = ApplicationV2.parseCSSDimension(computedStyle.minHeight, el.parentElement.offsetHeight) || 0; + let maxHeight = ApplicationV2.parseCSSDimension(computedStyle.maxHeight, el.parentElement.offsetHeight) || Infinity; + let bounds = el.getBoundingClientRect(); + const {clientWidth, clientHeight} = document.documentElement; + + // Explicit width + const autoWidth = width === `auto`; + if ( !autoWidth ) { + const targetWidth = Number(width || bounds.width); + minWidth = parseInt(minWidth) || 0; + maxWidth = parseInt(maxWidth) || (clientWidth / scale); + width = Math.clamp(targetWidth, minWidth, maxWidth); + } + + // Explicit height + const autoHeight = height === `auto`; + if ( !autoHeight ) { + const targetHeight = Number(height || bounds.height); + minHeight = parseInt(minHeight) || 0; + maxHeight = parseInt(maxHeight) || (clientHeight / scale); + height = Math.clamp(targetHeight, minHeight, maxHeight); + } + + // Implicit height + if ( autoHeight ) { + Object.assign(el.style, {width: `${width}px`, height: ``}); + bounds = el.getBoundingClientRect(); + height = bounds.height; + } + + // Implicit width + if ( autoWidth ) { + Object.assign(el.style, {height: `${height}px`, width: ``}); + bounds = el.getBoundingClientRect(); + width = bounds.width; + } + + // Left Offset + const scaledWidth = width * scale; + const targetLeft = left ?? (this.popover.x - Math.floor( scaledWidth / 2 )); + const maxLeft = Math.max(clientWidth - scaledWidth, 0); + left = Math.clamp(targetLeft, 0, maxLeft); + + // Top Offset + const scaledHeight = height * scale; + const targetTop = top ?? (this.popover.y - scaledHeight); + const maxTop = Math.max(clientHeight - scaledHeight, 0); + top = Math.clamp(targetTop, 0, maxTop); + + // Scale + scale ??= 1.0; + return { + width: autoWidth ? `auto` : width, + height: autoHeight ? `auto` : height, + left, + top, + scale, + }; + }; + }; return GenericRipCryptPopover; }; diff --git a/templates/css/common.css b/templates/css/common.css index cb8c9f0..8d025bb 100644 --- a/templates/css/common.css +++ b/templates/css/common.css @@ -2,6 +2,8 @@ @import url("./vars.css"); +@import url("./popover.css"); + @import url("./elements/button.css"); @import url("./elements/input.css"); @import url("./elements/lists.css"); diff --git a/templates/css/popover.css b/templates/css/popover.css new file mode 100644 index 0000000..1fbb7c9 --- /dev/null +++ b/templates/css/popover.css @@ -0,0 +1,5 @@ +.ripcrypt.popover { + position: absolute; + z-index: var(--z-index-tooltip); + transform-origin: top left; +} From 4b75526708fd10081c12c4d5572c2df3b3199a44 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 14 Mar 2025 16:34:13 -0600 Subject: [PATCH 056/164] Await render call --- module/Apps/GenericApp.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/Apps/GenericApp.mjs b/module/Apps/GenericApp.mjs index 5f3a75b..17cfc0c 100644 --- a/module/Apps/GenericApp.mjs +++ b/module/Apps/GenericApp.mjs @@ -38,7 +38,7 @@ export function GenericAppMixin(HandlebarsApp) { * top after being re-rendered as normal */ async render(options = {}, _options = {}) { - super.render(options, _options); + await super.render(options, _options); const instance = foundry.applications.instances.get(this.id); if (instance !== undefined && options.orBringToFront) { instance.bringToFront(); From e594b6beb033017bcd030d6c08e73b598e0a8086 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 14 Mar 2025 22:52:40 -0600 Subject: [PATCH 057/164] Get the reusable foundations of custom popovers finished. --- eslint.config.mjs | 1 + module/Apps/ActorSheets/HeroSkillsCardV1.mjs | 72 +++------- module/Apps/popovers/AmmoTracker.mjs | 9 +- module/Apps/popovers/GenericPopoverMixin.mjs | 43 +++++- module/utils/PopoverEventManager.mjs | 134 ++++++++++++++++++ templates/Apps/apps.css | 2 + templates/Apps/popovers/AmmoTracker/style.css | 4 + templates/css/popover.css | 17 ++- 8 files changed, 219 insertions(+), 63 deletions(-) create mode 100644 module/utils/PopoverEventManager.mjs create mode 100644 templates/Apps/popovers/AmmoTracker/style.css diff --git a/eslint.config.mjs b/eslint.config.mjs index aabd56c..70ae128 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -36,6 +36,7 @@ export default [ Combatant: `readonly`, canvas: `readonly`, Token: `readonly`, + Tour: `readonly`, }, }, }, diff --git a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs index 6921b54..9cd0016 100644 --- a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs @@ -1,10 +1,11 @@ import { deleteItemFromElement, editItemFromElement } from "../utils.mjs"; -import { documentSorter, filePath, getTooltipDelay } from "../../consts.mjs"; +import { documentSorter, filePath } from "../../consts.mjs"; +import { AmmoTracker } from "../popovers/AmmoTracker.mjs"; import { gameTerms } from "../../gameTerms.mjs"; import { GenericAppMixin } from "../GenericApp.mjs"; import { localizer } from "../../utils/Localizer.mjs"; import { Logger } from "../../utils/Logger.mjs"; -import { AmmoTracker } from "../popovers/AmmoTracker.mjs"; +import { PopoverEventManager } from "../../utils/PopoverEventManager.mjs"; const { HandlebarsApplicationMixin } = foundry.applications.api; const { ActorSheetV2 } = foundry.applications.sheets; @@ -40,19 +41,15 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin }; // #endregion - // #region Instance Data - /** @type {number | undefined} */ - #ammoTrackerHoverTimeout = null; - - /** @type {AmmoTracker | null} */ - #ammoTracker = null; - // #endregion - // #region Lifecycle + async _onFirstRender(context, options) { + await super._onFirstRender(context, options); + HeroSkillsCardV1._createPopoverListeners.bind(this)(); + }; + async _onRender(context, options) { await super._onRender(context, options); HeroSkillsCardV1._onRender.bind(this)(context, options); - await this.#createAmmoTrackerEvents(); }; static async _onRender(_context, options) { @@ -85,11 +82,15 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin ); }; - async #createAmmoTrackerEvents() { + /** @type {Map} */ + #popoverManagers = new Map(); + /** @this {HeroSkillsCardV1} */ + static async _createPopoverListeners() { const ammoInfoIcon = this.element.querySelector(`.ammo-info-icon`); - ammoInfoIcon.addEventListener(`pointerenter`, this.#ammoInfoPointerEnter.bind(this)); - ammoInfoIcon.addEventListener(`pointerout`, this.#ammoInfoPointerOut.bind(this)); - ammoInfoIcon.addEventListener(`click`, this.#ammoInfoClick.bind(this)); + this.#popoverManagers.set( + `.ammo-info-icon`, + new PopoverEventManager(ammoInfoIcon, AmmoTracker, { lockable: true }), + ); }; async _preparePartContext(partId, ctx, opts) { @@ -186,45 +187,14 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin } return ctx; }; - // #endregion - // #region Event Listeners - /** - * @param {PointerEvent} event - */ - async #ammoInfoPointerEnter(event) { - const pos = event.target.getBoundingClientRect(); - const x = pos.x + Math.floor(pos.width / 2); - const y = pos.y; - - this.#ammoTrackerHoverTimeout = setTimeout( - () => { - this.#ammoTrackerHoverTimeout = null; - const tracker = new AmmoTracker({ - popover: { - framed: false, - x, y, - }, - }); - tracker.render({ force: true }); - this.#ammoTracker = tracker; - }, - getTooltipDelay(), - ); - }; - - async #ammoInfoPointerOut() { - if (this.#ammoTracker) { - this.#ammoTracker.close(); - }; - - if (this.#ammoTrackerHoverTimeout !== null) { - clearTimeout(this.#ammoTrackerHoverTimeout); - this.#ammoTrackerHoverTimeout = null; + _tearDown(options) { + for (const manager of this.#popoverManagers.values()) { + manager.destroy(); }; + this.#popoverManagers.clear(); + super._tearDown(options); }; - - async #ammoInfoClick() {}; // #endregion // #region Actions diff --git a/module/Apps/popovers/AmmoTracker.mjs b/module/Apps/popovers/AmmoTracker.mjs index aeb96a9..a86e569 100644 --- a/module/Apps/popovers/AmmoTracker.mjs +++ b/module/Apps/popovers/AmmoTracker.mjs @@ -8,11 +8,12 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin( static DEFAULT_OPTIONS = { classes: [ `ripcrypt`, - `ripcrypt--AmmoTracker`, ], - position: { - width: 100, - height: 30, + window: { + title: `Ammo Tracker`, + contentClasses: [ + `ripcrypt--AmmoTracker`, + ], }, actions: {}, }; diff --git a/module/Apps/popovers/GenericPopoverMixin.mjs b/module/Apps/popovers/GenericPopoverMixin.mjs index dfe2518..81e5721 100644 --- a/module/Apps/popovers/GenericPopoverMixin.mjs +++ b/module/Apps/popovers/GenericPopoverMixin.mjs @@ -1,8 +1,14 @@ const { ApplicationV2 } = foundry.applications.api; +/** + * This mixin provides the ability to designate an Application as a "popover", + * which means that it will spawn near the x/y coordinates provided it won't + * overflow the bounds of the screen. + */ export function GenericPopoverMixin(HandlebarsApp) { class GenericRipCryptPopover extends HandlebarsApp { static DEFAULT_OPTIONS = { + id: `popover-{id}`, classes: [ `popover`, ], @@ -21,21 +27,48 @@ export function GenericPopoverMixin(HandlebarsApp) { // For when the caller doesn't provide anything, we want this to behave // like a normal Application instance. popover.framed ??= true; - popover.locked ??= true; + popover.locked ??= false; + if (popover.framed) { + options.window ??= {}; options.window.frame = true; options.window.minimizable = true; - }; + } + + options.classes ??= []; + options.classes.push(popover.framed ? `framed` : `frameless`); super(options); - this.popover = popover; }; + toggleLock() { + this.popover.locked = !this.popover.locked; + this.classList.toggle(`locked`, this.popover.locked); + }; + + /** + * This render utility is intended in order to make the popovers able to be + * used in both framed and frameless mode, making sure that the content classes + * from the framed mode get shunted onto the frameless Application's root + * element. + */ + async _onFirstRender(...args) { + await super._onFirstRender(...args); + + const hasContentClasses = this.options?.window?.contentClasses?.length > 0; + if (!this.popover.framed && hasContentClasses) { + this.classList.add(...this.options.window.contentClasses); + }; + }; + async close(options = {}) { + // prevent locked popovers from being closed + if (this.popover.locked && !options.force) { return }; + if (!this.popover.framed) { - options.animate ??= false; + options.animate = false; }; return super.close(options); }; @@ -51,7 +84,7 @@ export function GenericPopoverMixin(HandlebarsApp) { */ _updatePosition(position) { if (!this.element) { return position }; - if (this.popover.framed) { return position } + if (this.popover.framed) { return super._updatePosition(position) }; const el = this.element; let {width, height, left, top, scale} = position; diff --git a/module/utils/PopoverEventManager.mjs b/module/utils/PopoverEventManager.mjs new file mode 100644 index 0000000..777fbbd --- /dev/null +++ b/module/utils/PopoverEventManager.mjs @@ -0,0 +1,134 @@ +import { getTooltipDelay } from "../consts.mjs"; +import { Logger } from "./Logger.mjs"; + +export class PopoverEventManager { + #options; + + /** + * @param {HTMLElement} element The element to attach the listeners to. + * @param {GenericPopoverMixin} popoverClass The class reference that represents the popover app + */ + constructor(element, popoverClass, options = {}) { + options.locked ??= false; + options.lockable ??= true; + + this.#options = options; + this.#element = element; + this.#class = popoverClass; + + element.addEventListener(`pointerenter`, this.#pointerEnterHandler.bind(this)); + element.addEventListener(`pointerout`, this.#pointerOutHandler.bind(this)); + element.addEventListener(`click`, this.#clickHandler.bind(this)); + + if (options.lockable) { + element.addEventListener(`pointerup`, this.#pointerUpHandler.bind(this)); + }; + }; + + destroy() { + this.close(); + this.#element.removeEventListener(`pointerenter`, this.#pointerEnterHandler); + this.#element.removeEventListener(`pointerout`, this.#pointerOutHandler); + this.#element.removeEventListener(`click`, this.#clickHandler); + if (this.#options.lockable) { + this.#element.removeEventListener(`pointerup`, this.#pointerUpHandler); + }; + this.stopOpen(); + this.stopClose(); + }; + + close() { + this.#frameless?.close({ force: true }); + this.#framed?.close({ force: true }); + }; + + stopOpen() { + if (this.#openTimeout != null) { + clearTimeout(this.#openTimeout); + this.#openTimeout = null; + }; + }; + + stopClose() { + if (this.#closeTimeout != null) { + clearTimeout(this.#closeTimeout); + this.#closeTimeout = null; + } + }; + + #element; + #class; + #openTimeout = null; + #closeTimeout = null; + + #frameless; + #framed; + + #clickHandler() { + // Cleanup for the frameless lifecycle + this.stopOpen(); + this.#frameless?.close({ force: true }); + + if (!this.#framed) { + const app = new this.#class({ popover: { ...this.#options, framed: true } }); + this.#framed = app; + } + this.#framed.render({ force: true }); + }; + + #pointerEnterHandler(event) { + this.stopClose(); + + const pos = event.target.getBoundingClientRect(); + const x = pos.x + Math.floor(pos.width / 2); + const y = pos.y; + + this.#openTimeout = setTimeout( + () => { + this.#openTimeout = null; + + // When we have the framed version rendered, we might as well just focus + // it instead of rendering a new application + if (this.#framed?.rendered) { + this.#framed.bringToFront(); + return; + }; + + if (this.#frameless?.rendered) { + const { width, height } = this.#frameless.position; + this.#frameless.render({ position: { left: x - Math.floor(width / 2), top: y - height }}); + return; + } + + this.#frameless = new this.#class({ + popover: { + ...this.#options, + framed: false, + x, y, + }, + }); + this.#frameless.render({ force: true }); + }, + getTooltipDelay(), + ); + }; + + #pointerOutHandler() { + this.stopOpen(); + + this.#closeTimeout = setTimeout( + () => { + this.#closeTimeout = null; + this.#frameless?.close(); + }, + getTooltipDelay(), + ); + }; + + #pointerUpHandler(event) { + Logger.debug(event); + if (event.button !== 1 || !this.#frameless?.rendered || Tour.tourInProgress) { return }; + event.preventDefault(); + this.#frameless.toggleLock(); + }; +}; diff --git a/templates/Apps/apps.css b/templates/Apps/apps.css index 2ec33cc..728dfb9 100644 --- a/templates/Apps/apps.css +++ b/templates/Apps/apps.css @@ -6,3 +6,5 @@ @import url("./HeroSummaryCardV1/style.css"); @import url("./HeroSkillsCardV1/style.css"); @import url("./RichEditor/style.css"); + +@import url("./popovers/AmmoTracker/style.css"); diff --git a/templates/Apps/popovers/AmmoTracker/style.css b/templates/Apps/popovers/AmmoTracker/style.css new file mode 100644 index 0000000..63e4d92 --- /dev/null +++ b/templates/Apps/popovers/AmmoTracker/style.css @@ -0,0 +1,4 @@ +.ripcrypt--AmmoTracker { + color: var(--base-text); + background: var(--base-background); +} diff --git a/templates/css/popover.css b/templates/css/popover.css index 1fbb7c9..05ab2dd 100644 --- a/templates/css/popover.css +++ b/templates/css/popover.css @@ -1,5 +1,16 @@ .ripcrypt.popover { - position: absolute; - z-index: var(--z-index-tooltip); - transform-origin: top left; + border-width: 2px; + border-style: solid; + border-color: transparent; + border-radius: 4px; + + &.frameless { + position: absolute; + z-index: var(--z-index-tooltip); + transform-origin: top left; + } + + &.locked { + border-color: var(--accent-3); + } } From 9ea2eebdd9a80ebcb21a30d19487f9d97d8c4960 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 14 Mar 2025 23:55:12 -0600 Subject: [PATCH 058/164] Make sure the moving works when the width/height are auto --- module/utils/PopoverEventManager.mjs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/module/utils/PopoverEventManager.mjs b/module/utils/PopoverEventManager.mjs index 777fbbd..b4b9751 100644 --- a/module/utils/PopoverEventManager.mjs +++ b/module/utils/PopoverEventManager.mjs @@ -94,9 +94,13 @@ export class PopoverEventManager { return; }; + // When the frameless is already rendered, we should just move it to the + // new location instead of spawning a new one if (this.#frameless?.rendered) { - const { width, height } = this.#frameless.position; - this.#frameless.render({ position: { left: x - Math.floor(width / 2), top: y - height }}); + const { width, height } = this.#frameless.element.getBoundingClientRect(); + const top = y - height; + const left = x - Math.floor(width / 2); + this.#frameless.setPosition({ left, top }); return; } @@ -126,7 +130,6 @@ export class PopoverEventManager { }; #pointerUpHandler(event) { - Logger.debug(event); if (event.button !== 1 || !this.#frameless?.rendered || Tour.tourInProgress) { return }; event.preventDefault(); this.#frameless.toggleLock(); From 88a47ba02bbd3f6b9c2a9ff5d27ec4de78e7c869 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 14 Mar 2025 23:55:48 -0600 Subject: [PATCH 059/164] Tweak default popover styles --- templates/css/popover.css | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/templates/css/popover.css b/templates/css/popover.css index 05ab2dd..0d898b2 100644 --- a/templates/css/popover.css +++ b/templates/css/popover.css @@ -1,16 +1,17 @@ .ripcrypt.popover { - border-width: 2px; - border-style: solid; - border-color: transparent; - border-radius: 4px; + box-sizing: border-box; &.frameless { + border-width: 2px; + border-style: solid; + border-color: transparent; + border-radius: 4px; position: absolute; z-index: var(--z-index-tooltip); transform-origin: top left; - } - &.locked { - border-color: var(--accent-3); + &.locked { + border-color: var(--accent-3); + } } } From e8fdf6e9522d315f335eddd7c9298da21c7c44f0 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 15 Mar 2025 14:43:08 -0600 Subject: [PATCH 060/164] Enable generic styling in frameless popovers --- templates/css/elements/button.css | 1 + templates/css/elements/input.css | 1 + templates/css/elements/lists.css | 1 + templates/css/elements/p.css | 1 + templates/css/elements/pill-bar.css | 1 + templates/css/elements/select.css | 1 + templates/css/elements/span.css | 1 + templates/css/elements/table.css | 1 + 8 files changed, 8 insertions(+) diff --git a/templates/css/elements/button.css b/templates/css/elements/button.css index 6eee9d8..14fd464 100644 --- a/templates/css/elements/button.css +++ b/templates/css/elements/button.css @@ -1,3 +1,4 @@ +.ripcrypt.popover.frameless button, .ripcrypt.hud button, .ripcrypt > .window-content button { all: revert; diff --git a/templates/css/elements/input.css b/templates/css/elements/input.css index 3121505..bdf6c7f 100644 --- a/templates/css/elements/input.css +++ b/templates/css/elements/input.css @@ -1,3 +1,4 @@ +.ripcrypt.popover.frameless, .ripcrypt.hud, .ripcrypt > .window-content { input, .input { diff --git a/templates/css/elements/lists.css b/templates/css/elements/lists.css index b55f34f..74ac6f3 100644 --- a/templates/css/elements/lists.css +++ b/templates/css/elements/lists.css @@ -1,3 +1,4 @@ +.ripcrypt.popover.frameless, .ripcrypt.hud, .ripcrypt > .window-content { ol { diff --git a/templates/css/elements/p.css b/templates/css/elements/p.css index fcf243a..91c29a1 100644 --- a/templates/css/elements/p.css +++ b/templates/css/elements/p.css @@ -1,3 +1,4 @@ +.ripcrypt.popover.frameless p, .ripcrypt.hud p, .ripcrypt > .window-content p { &.warning { diff --git a/templates/css/elements/pill-bar.css b/templates/css/elements/pill-bar.css index 425499c..ec85807 100644 --- a/templates/css/elements/pill-bar.css +++ b/templates/css/elements/pill-bar.css @@ -1,3 +1,4 @@ +.ripcrypt.popover.frameless .pill-bar, .ripcrypt.hud .pill-bar, .ripcrypt > .window-content .pill-bar { display: flex; diff --git a/templates/css/elements/select.css b/templates/css/elements/select.css index 0940786..be51dbb 100644 --- a/templates/css/elements/select.css +++ b/templates/css/elements/select.css @@ -1,3 +1,4 @@ +.ripcrypt.popover.frameless select, .ripcrypt.hud select, .ripcrypt > .window-content select { all: revert; diff --git a/templates/css/elements/span.css b/templates/css/elements/span.css index 85a099b..ef8bfa4 100644 --- a/templates/css/elements/span.css +++ b/templates/css/elements/span.css @@ -1,3 +1,4 @@ +.ripcrypt.popover.frameless span, .ripcrypt.hud span, .ripcrypt > .window-content span { &.small { diff --git a/templates/css/elements/table.css b/templates/css/elements/table.css index 44abd0b..e06668c 100644 --- a/templates/css/elements/table.css +++ b/templates/css/elements/table.css @@ -1,3 +1,4 @@ +.ripcrypt.popover.frameless table, .ripcrypt.hud table, .ripcrypt > .window-content table { all: revert; From 69bf712eca8a221488615ffae353faa9b566a87f Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 15 Mar 2025 17:05:14 -0600 Subject: [PATCH 061/164] Have the PopoverManager call a hook to get additional options for the popover Application --- module/Apps/ActorSheets/HeroSkillsCardV1.mjs | 14 ++++++++++++-- module/utils/PopoverEventManager.mjs | 17 +++++++++++------ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs index 9cd0016..36e8ed3 100644 --- a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs @@ -44,12 +44,12 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin // #region Lifecycle async _onFirstRender(context, options) { await super._onFirstRender(context, options); - HeroSkillsCardV1._createPopoverListeners.bind(this)(); }; async _onRender(context, options) { await super._onRender(context, options); HeroSkillsCardV1._onRender.bind(this)(context, options); + HeroSkillsCardV1._createPopoverListeners.bind(this)(); }; static async _onRender(_context, options) { @@ -84,13 +84,20 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin /** @type {Map} */ #popoverManagers = new Map(); + /** @type {Map} */ + #hookIDs = new Map(); /** @this {HeroSkillsCardV1} */ static async _createPopoverListeners() { const ammoInfoIcon = this.element.querySelector(`.ammo-info-icon`); + this.#popoverManagers.set( `.ammo-info-icon`, - new PopoverEventManager(ammoInfoIcon, AmmoTracker, { lockable: true }), + new PopoverEventManager(ammoInfoIcon, AmmoTracker), ); + + this.#hookIDs.set(Hooks.on(`get${AmmoTracker.name}Options`, (opts) => { + opts.ammo = this.actor.itemTypes.ammo; + }), `get${AmmoTracker.name}Options`); }; async _preparePartContext(partId, ctx, opts) { @@ -193,6 +200,9 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin manager.destroy(); }; this.#popoverManagers.clear(); + for (const [id, hook] of this.#hookIDs.entries()) { + Hooks.off(hook, id); + } super._tearDown(options); }; // #endregion diff --git a/module/utils/PopoverEventManager.mjs b/module/utils/PopoverEventManager.mjs index b4b9751..4bb8cf1 100644 --- a/module/utils/PopoverEventManager.mjs +++ b/module/utils/PopoverEventManager.mjs @@ -1,5 +1,4 @@ import { getTooltipDelay } from "../consts.mjs"; -import { Logger } from "./Logger.mjs"; export class PopoverEventManager { #options; @@ -64,16 +63,22 @@ export class PopoverEventManager { #frameless; #framed; + #construct(options) { + const valid = Hooks.call(`get${this.#class.name}Options`, options); + if (!valid) { return }; + return new this.#class(options); + }; + #clickHandler() { // Cleanup for the frameless lifecycle this.stopOpen(); + this.stopClose(); this.#frameless?.close({ force: true }); if (!this.#framed) { - const app = new this.#class({ popover: { ...this.#options, framed: true } }); - this.#framed = app; + this.#framed = this.#construct({ popover: { ...this.#options, framed: true } }); } - this.#framed.render({ force: true }); + this.#framed?.render({ force: true }); }; #pointerEnterHandler(event) { @@ -104,14 +109,14 @@ export class PopoverEventManager { return; } - this.#frameless = new this.#class({ + this.#frameless = this.#construct({ popover: { ...this.#options, framed: false, x, y, }, }); - this.#frameless.render({ force: true }); + this.#frameless?.render({ force: true }); }, getTooltipDelay(), ); From 96e4d09e7bda2a6f82e56e46950288599cddbfc1 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 15 Mar 2025 18:35:24 -0600 Subject: [PATCH 062/164] Update the popover management to work with origin rerenders, and rerendering the popovers directly. --- module/Apps/ActorSheets/HeroSkillsCardV1.mjs | 29 +++--------- module/Apps/GenericApp.mjs | 30 +++++++++++++ module/Apps/popovers/GenericPopoverMixin.mjs | 17 +++++++- module/utils/PopoverEventManager.mjs | 46 ++++++++++++++++++-- 4 files changed, 93 insertions(+), 29 deletions(-) diff --git a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs index 36e8ed3..ccffbef 100644 --- a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs @@ -82,22 +82,16 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin ); }; - /** @type {Map} */ - #popoverManagers = new Map(); - /** @type {Map} */ - #hookIDs = new Map(); /** @this {HeroSkillsCardV1} */ static async _createPopoverListeners() { const ammoInfoIcon = this.element.querySelector(`.ammo-info-icon`); + const idPrefix = this.actor.uuid; - this.#popoverManagers.set( - `.ammo-info-icon`, - new PopoverEventManager(ammoInfoIcon, AmmoTracker), - ); - - this.#hookIDs.set(Hooks.on(`get${AmmoTracker.name}Options`, (opts) => { - opts.ammo = this.actor.itemTypes.ammo; - }), `get${AmmoTracker.name}Options`); + const manager = new PopoverEventManager(`${idPrefix}.ammo-info-icon`, ammoInfoIcon, AmmoTracker); + this._popoverManagers.set(`.ammo-info-icon`, manager); + this._hookIDs.set(Hooks.on(`prepare${manager.id}Context`, (ctx) => { + ctx.ammos = this.actor.itemTypes.ammo; + }), `prepare${manager.id}Context`); }; async _preparePartContext(partId, ctx, opts) { @@ -194,17 +188,6 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin } return ctx; }; - - _tearDown(options) { - for (const manager of this.#popoverManagers.values()) { - manager.destroy(); - }; - this.#popoverManagers.clear(); - for (const [id, hook] of this.#hookIDs.entries()) { - Hooks.off(hook, id); - } - super._tearDown(options); - }; // #endregion // #region Actions diff --git a/module/Apps/GenericApp.mjs b/module/Apps/GenericApp.mjs index 17cfc0c..7c1077e 100644 --- a/module/Apps/GenericApp.mjs +++ b/module/Apps/GenericApp.mjs @@ -31,6 +31,13 @@ export function GenericAppMixin(HandlebarsApp) { }; // #endregion + // #region Instance Data + /** @type {Map} */ + _popoverManagers = new Map(); + /** @type {Map} */ + _hookIDs = new Map(); + // #endregion + // #region Lifecycle /** * @override @@ -45,6 +52,13 @@ export function GenericAppMixin(HandlebarsApp) { }; }; + async _onRender() { + await super._onRender(); + for (const manager of this._popoverManagers.values()) { + manager.render(); + }; + }; + async _preparePartContext(partId, ctx, opts) { ctx = await super._preparePartContext(partId, ctx, opts); delete ctx.document; @@ -60,6 +74,22 @@ export function GenericAppMixin(HandlebarsApp) { return ctx; }; + + _tearDown(options) { + // Clear all popovers associated with the app + for (const manager of this._popoverManagers.values()) { + manager.destroy(); + }; + this._popoverManagers.clear(); + + // Remove any hooks added for this app + for (const [id, hook] of this._hookIDs.entries()) { + Hooks.off(hook, id); + }; + this._hookIDs.clear(); + + super._tearDown(options); + }; // #endregion // #region Actions diff --git a/module/Apps/popovers/GenericPopoverMixin.mjs b/module/Apps/popovers/GenericPopoverMixin.mjs index 81e5721..017c2b0 100644 --- a/module/Apps/popovers/GenericPopoverMixin.mjs +++ b/module/Apps/popovers/GenericPopoverMixin.mjs @@ -3,7 +3,10 @@ const { ApplicationV2 } = foundry.applications.api; /** * This mixin provides the ability to designate an Application as a "popover", * which means that it will spawn near the x/y coordinates provided it won't - * overflow the bounds of the screen. + * overflow the bounds of the screen. This also implements a _preparePartContext + * in order to allow the parent application passing new data into the popover + * whenever it rerenders; how the popover handles this data is up to the + * specific implementation. */ export function GenericPopoverMixin(HandlebarsApp) { class GenericRipCryptPopover extends HandlebarsApp { @@ -29,7 +32,6 @@ export function GenericPopoverMixin(HandlebarsApp) { popover.framed ??= true; popover.locked ??= false; - if (popover.framed) { options.window ??= {}; options.window.frame = true; @@ -151,6 +153,17 @@ export function GenericPopoverMixin(HandlebarsApp) { scale, }; }; + + /** + * This is here in order allow things that are not this Application + * to provide / augment the context data for the lifecycle of the app. + */ + async _prepareContext(_partId, _context, options) { + const context = {}; + Hooks.callAll(`prepare${this.constructor.name}Context`, context, options); + Hooks.callAll(`prepare${this.popover.managerId}Context`, context, options); + return context; + }; }; return GenericRipCryptPopover; }; diff --git a/module/utils/PopoverEventManager.mjs b/module/utils/PopoverEventManager.mjs index 4bb8cf1..6c0b0c8 100644 --- a/module/utils/PopoverEventManager.mjs +++ b/module/utils/PopoverEventManager.mjs @@ -1,13 +1,28 @@ import { getTooltipDelay } from "../consts.mjs"; +import { Logger } from "./Logger.mjs"; export class PopoverEventManager { #options; + id; + + /** @type {Map} */ + static #existing = new Map(); /** * @param {HTMLElement} element The element to attach the listeners to. * @param {GenericPopoverMixin} popoverClass The class reference that represents the popover app */ - constructor(element, popoverClass, options = {}) { + constructor(id, element, popoverClass, options = {}) { + id = `${id}-${popoverClass.name}`; + this.id = id; + + if (PopoverEventManager.#existing.has(id)) { + const manager = PopoverEventManager.#existing.get(id); + manager.#addListeners(element); + return manager; + }; + + options.managerId = id; options.locked ??= false; options.lockable ??= true; @@ -15,11 +30,19 @@ export class PopoverEventManager { this.#element = element; this.#class = popoverClass; + this.#addListeners(element); + PopoverEventManager.#existing.set(id, this); + }; + + /** + * @param {HTMLElement} element + */ + #addListeners(element) { element.addEventListener(`pointerenter`, this.#pointerEnterHandler.bind(this)); element.addEventListener(`pointerout`, this.#pointerOutHandler.bind(this)); element.addEventListener(`click`, this.#clickHandler.bind(this)); - if (options.lockable) { + if (this.#options.lockable) { element.addEventListener(`pointerup`, this.#pointerUpHandler.bind(this)); }; }; @@ -55,6 +78,19 @@ export class PopoverEventManager { } }; + get rendered() { + return Boolean(this.#frameless?.rendered || this.#framed?.rendered); + }; + + render(options) { + if (this.#framed?.rendered) { + this.#framed.render(options); + }; + if (this.#frameless?.rendered) { + this.#frameless.render(options); + }; + }; + #element; #class; #openTimeout = null; @@ -64,12 +100,14 @@ export class PopoverEventManager { #framed; #construct(options) { - const valid = Hooks.call(`get${this.#class.name}Options`, options); - if (!valid) { return }; + options.popover ??= {}; + options.popover.managerId = this.id; + return new this.#class(options); }; #clickHandler() { + Logger.debug(`click event handler`); // Cleanup for the frameless lifecycle this.stopOpen(); this.stopClose(); From 3ae7e9489a248f8440c5e027dc0f6b222a5841cd Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 15 Mar 2025 18:36:04 -0600 Subject: [PATCH 063/164] Add ammo to the gear types, and get more design stuff for the AmmoTracker --- langs/en-ca.json | 3 +- module/Apps/popovers/AmmoTracker.mjs | 10 ++++- module/gameTerms.mjs | 1 + .../Apps/popovers/AmmoTracker/ammoList.hbs | 23 +++++++++++ .../Apps/popovers/AmmoTracker/content.hbs | 3 -- templates/Apps/popovers/AmmoTracker/style.css | 40 +++++++++++++++++-- templates/css/elements/button.css | 3 +- templates/css/elements/lists.css | 3 ++ templates/css/themes/dark.css | 10 +++++ 9 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 templates/Apps/popovers/AmmoTracker/ammoList.hbs delete mode 100644 templates/Apps/popovers/AmmoTracker/content.hbs diff --git a/langs/en-ca.json b/langs/en-ca.json index c40e853..badf3f2 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -172,7 +172,8 @@ "numberOfDice": "# of Dice", "rollTarget": "Target", "difficulty": "(DC: {dc})", - "RichEditor-no-collaborative": "Warning: This editor is not collaborative, that means that if you and someone else are editing it at the same time, you won't see that someone else is making changes until they save, and then your changes will be lost." + "RichEditor-no-collaborative": "Warning: This editor is not collaborative, that means that if you and someone else are editing it at the same time, you won't see that someone else is making changes until they save, and then your changes will be lost.", + "no-ammo": "You don't have any ammo!" }, "notifs": { "error": { diff --git a/module/Apps/popovers/AmmoTracker.mjs b/module/Apps/popovers/AmmoTracker.mjs index a86e569..e1203c0 100644 --- a/module/Apps/popovers/AmmoTracker.mjs +++ b/module/Apps/popovers/AmmoTracker.mjs @@ -19,8 +19,8 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin( }; static PARTS = { - main: { - template: filePath(`templates/Apps/popovers/AmmoTracker/content.hbs`), + ammoList: { + template: filePath(`templates/Apps/popovers/AmmoTracker/ammoList.hbs`), }, }; // #endregion @@ -29,6 +29,12 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin( // #endregion // #region Lifecycle + async _preparePartContext(partId, data) { + const ctx = { partId }; + ctx.canPin = false; + ctx.ammos = data.ammos; + return ctx; + }; // #endregion // #region Actions diff --git a/module/gameTerms.mjs b/module/gameTerms.mjs index 5bcbe71..86507ad 100644 --- a/module/gameTerms.mjs +++ b/module/gameTerms.mjs @@ -37,6 +37,7 @@ export const gameTerms = Object.preventExtensions({ }), /** The types of items that contribute to the gear limit */ gearItemTypes: new Set([ + `ammo`, `armour`, `weapon`, `shield`, diff --git a/templates/Apps/popovers/AmmoTracker/ammoList.hbs b/templates/Apps/popovers/AmmoTracker/ammoList.hbs new file mode 100644 index 0000000..87118d7 --- /dev/null +++ b/templates/Apps/popovers/AmmoTracker/ammoList.hbs @@ -0,0 +1,23 @@ +
+ {{#if ammos}} +
    + {{#each ammos as | ammo |}} +
    + {{ ammo.name }} + {{ ammo.system.quantity }} + +
    + {{/each}} +
+ {{else}} + + {{ rc-i18n "RipCrypt.Apps.no-ammo" }} + + {{/if}} +
\ No newline at end of file diff --git a/templates/Apps/popovers/AmmoTracker/content.hbs b/templates/Apps/popovers/AmmoTracker/content.hbs deleted file mode 100644 index 80f0024..0000000 --- a/templates/Apps/popovers/AmmoTracker/content.hbs +++ /dev/null @@ -1,3 +0,0 @@ -
- Hello -
\ No newline at end of file diff --git a/templates/Apps/popovers/AmmoTracker/style.css b/templates/Apps/popovers/AmmoTracker/style.css index 63e4d92..1f1cf1d 100644 --- a/templates/Apps/popovers/AmmoTracker/style.css +++ b/templates/Apps/popovers/AmmoTracker/style.css @@ -1,4 +1,38 @@ -.ripcrypt--AmmoTracker { - color: var(--base-text); - background: var(--base-background); +.ripcrypt--AmmoTracker.ripcrypt--AmmoTracker { + color: var(--popover-text); + background: var(--popover-background); + padding: 4px 8px; + + --button-text: var(--header-text); + --button-background: var(--header-background); + + button { + border-radius: 4px; + } + + ul { + &:nth-child(even) { + color: var(--popover-alt-row-text); + background: var(--popover-alt-row-background); + --button-text: unset; + --button-background: unset; + } + } + + .ammo { + display: grid; + grid-template-columns: 150px 30px min-content; + grid-template-rows: min-content; + align-items: center; + justify-items: center; + /* display: flex; + flex-direction: row; + align-items: center; */ + gap: 8px; + + .name { + flex-grow: 1; + justify-self: left; + } + } } diff --git a/templates/css/elements/button.css b/templates/css/elements/button.css index 14fd464..e912a42 100644 --- a/templates/css/elements/button.css +++ b/templates/css/elements/button.css @@ -1,5 +1,4 @@ -.ripcrypt.popover.frameless button, -.ripcrypt.hud button, +.ripcrypt:where(.popover.frameless, .hud) button, .ripcrypt > .window-content button { all: revert; outline: none; diff --git a/templates/css/elements/lists.css b/templates/css/elements/lists.css index 74ac6f3..24aa561 100644 --- a/templates/css/elements/lists.css +++ b/templates/css/elements/lists.css @@ -36,6 +36,9 @@ } ul { + margin: 0; + padding: 0; + > li { margin: 0; } diff --git a/templates/css/themes/dark.css b/templates/css/themes/dark.css index aa4a8a4..b454025 100644 --- a/templates/css/themes/dark.css +++ b/templates/css/themes/dark.css @@ -27,6 +27,16 @@ --col-gap: 2px; --row-gap: 0px; + /* Popover Variables */ + --popover-text: var(--base-text); + --popover-background: var(--base-background); + + --popover-alt-row-text: var(--alt-row-text); + --popover-alt-row-background: var(--alt-row-background); + + --popover-header-text: var(--header-text); + --popover-header-background: var(--header-background); + /* Additional Variables */ --string-tags-border: inherit; --string-tags-tag-background: inherit; From 032f2564c140f7368c691459fc02a23c5a8ae3e0 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 16 Mar 2025 10:53:57 -0600 Subject: [PATCH 064/164] Make the stopEvent methods private --- module/utils/PopoverEventManager.mjs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/module/utils/PopoverEventManager.mjs b/module/utils/PopoverEventManager.mjs index 6c0b0c8..ff1fd74 100644 --- a/module/utils/PopoverEventManager.mjs +++ b/module/utils/PopoverEventManager.mjs @@ -55,8 +55,8 @@ export class PopoverEventManager { if (this.#options.lockable) { this.#element.removeEventListener(`pointerup`, this.#pointerUpHandler); }; - this.stopOpen(); - this.stopClose(); + this.#stopOpen(); + this.#stopClose(); }; close() { @@ -64,14 +64,14 @@ export class PopoverEventManager { this.#framed?.close({ force: true }); }; - stopOpen() { + #stopOpen() { if (this.#openTimeout != null) { clearTimeout(this.#openTimeout); this.#openTimeout = null; }; }; - stopClose() { + #stopClose() { if (this.#closeTimeout != null) { clearTimeout(this.#closeTimeout); this.#closeTimeout = null; @@ -109,8 +109,8 @@ export class PopoverEventManager { #clickHandler() { Logger.debug(`click event handler`); // Cleanup for the frameless lifecycle - this.stopOpen(); - this.stopClose(); + this.#stopOpen(); + this.#stopClose(); this.#frameless?.close({ force: true }); if (!this.#framed) { @@ -120,7 +120,7 @@ export class PopoverEventManager { }; #pointerEnterHandler(event) { - this.stopClose(); + this.#stopClose(); const pos = event.target.getBoundingClientRect(); const x = pos.x + Math.floor(pos.width / 2); @@ -161,7 +161,7 @@ export class PopoverEventManager { }; #pointerOutHandler() { - this.stopOpen(); + this.#stopOpen(); this.#closeTimeout = setTimeout( () => { From 3437dadb9b5c099d9bc5f625741f206392573fd1 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 16 Mar 2025 10:57:53 -0600 Subject: [PATCH 065/164] Reduce the z-index of the popovers a little bit --- templates/css/popover.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/css/popover.css b/templates/css/popover.css index 0d898b2..73cfe4a 100644 --- a/templates/css/popover.css +++ b/templates/css/popover.css @@ -7,7 +7,7 @@ border-color: transparent; border-radius: 4px; position: absolute; - z-index: var(--z-index-tooltip); + z-index: calc(var(--z-index-tooltip) - 5); transform-origin: top left; &.locked { From 7d39c487dc21a2cc514aed5ca9053ee4d6e90d5f Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 22 Mar 2025 14:34:20 -0600 Subject: [PATCH 066/164] Prevent deprecation warnings as of v13.338 --- eslint.config.mjs | 2 -- module/hooks/init.mjs | 5 +++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 70ae128..a0d500d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -22,9 +22,7 @@ export default [ Hooks: `readonly`, ui: `readonly`, Actor: `readonly`, - Actors: `readonly`, Item: `readonly`, - Items: `readonly`, foundry: `readonly`, ChatMessage: `readonly`, ActiveEffect: `readonly`, diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 48c3171..d829eb4 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -35,6 +35,9 @@ import { registerMetaSettings } from "../settings/metaSettings.mjs"; import { registerUserSettings } from "../settings/userSettings.mjs"; import { registerWorldSettings } from "../settings/worldSettings.mjs"; +const { Items, Actors } = foundry.documents.collections; +const { ItemSheet, ActorSheet } = foundry.appv1.sheets; + Hooks.once(`init`, () => { Logger.log(`Initializing`); @@ -70,10 +73,8 @@ Hooks.once(`init`, () => { // #region Sheets // Unregister core sheets - /* eslint-disable no-undef */ Items.unregisterSheet(`core`, ItemSheet); Actors.unregisterSheet(`core`, ActorSheet); - /* eslint-enabled no-undef */ // #region Actors Actors.registerSheet(game.system.id, CombinedHeroSheet, { From c495f45015250549551c8c6dea9a4f4bf6d96b10 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 22 Mar 2025 21:20:02 -0600 Subject: [PATCH 067/164] Get the base favourite mechanism working so the items are visible on the skills card --- assets/_credit.txt | 2 + assets/icons/star-empty.svg | 1 + assets/icons/star.svg | 1 + langs/en-ca.json | 9 ++- module/Apps/ActorSheets/HeroSkillsCardV1.mjs | 18 +++++- module/Apps/GenericApp.mjs | 5 ++ module/Apps/popovers/AmmoTracker.mjs | 57 ++++++++++++++++++- module/Apps/popovers/GenericPopoverMixin.mjs | 2 +- templates/Apps/HeroSkillsCardV1/content.hbs | 18 ++++++ templates/Apps/HeroSkillsCardV1/style.css | 8 ++- .../Apps/popovers/AmmoTracker/ammoList.hbs | 45 ++++++++++----- templates/Apps/popovers/AmmoTracker/style.css | 22 +++++-- templates/css/common.css | 1 + templates/css/elements/button.css | 3 + 14 files changed, 165 insertions(+), 27 deletions(-) create mode 100644 assets/icons/star-empty.svg create mode 100644 assets/icons/star.svg diff --git a/assets/_credit.txt b/assets/_credit.txt index 43cf3fb..bac6dc2 100644 --- a/assets/_credit.txt +++ b/assets/_credit.txt @@ -1,6 +1,8 @@ Oliver Akins: - geist-silhouette.v2.svg : All rights reserved. - caster-silhouette.v1.svg : All rights reserved. + - icons/star-empty.svg : Modified from https://thenounproject.com/icon/star-7711815/ by Llisole + - icons/star.svg : Modified from https://thenounproject.com/icon/star-7711815/ by Llisole Kýnan Antos (Gritsilk Games): - hero-silhouette.svg : Licensed to Distribute and Modify within the bounds of the "Foundry-RipCrypt" system. diff --git a/assets/icons/star-empty.svg b/assets/icons/star-empty.svg new file mode 100644 index 0000000..8760cc9 --- /dev/null +++ b/assets/icons/star-empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/star.svg b/assets/icons/star.svg new file mode 100644 index 0000000..829431b --- /dev/null +++ b/assets/icons/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/langs/en-ca.json b/langs/en-ca.json index badf3f2..de9e051 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -173,12 +173,17 @@ "rollTarget": "Target", "difficulty": "(DC: {dc})", "RichEditor-no-collaborative": "Warning: This editor is not collaborative, that means that if you and someone else are editing it at the same time, you won't see that someone else is making changes until they save, and then your changes will be lost.", - "no-ammo": "You don't have any ammo!" + "AmmoTracker": { + "no-ammo": "You don't have any ammo!", + "pin-button": "Pin {name} to your character sheet", + "pin-button-tooltip": "Pin {name}" + } }, "notifs": { "error": { "cannot-equip": "Cannot equip the {itemType}, see console for more details.", - "invalid-delta": "The delta for \"{name}\" is not a number, cannot finish processing the action." + "invalid-delta": "The delta for \"{name}\" is not a number, cannot finish processing the action.", + "at-favourite-limit": "Cannot favourite more than three items, unfavourite one to make space." }, "warn": { "cannot-go-negative": "\"{name}\" is unable to be a negative number." diff --git a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs index ccffbef..be8fb26 100644 --- a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs @@ -139,7 +139,23 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin }; static async prepareAmmo(ctx) { - ctx.ammo = 0; + let total = 0; + ctx.favouriteAmmo = []; + + for (const ammo of ctx.actor.itemTypes.ammo) { + total += ammo.system.quantity; + + if (ctx.favouriteAmmo.length < 3 && ammo.getFlag(game.system.id, `favourited`)) { + ctx.favouriteAmmo.push({ + uuid: ammo.uuid, + name: ammo.name, + quantity: ammo.system.quantity, + }); + }; + }; + ctx.favouriteAmmo.length = 3; // assert array length + + ctx.ammo = total; return ctx; }; diff --git a/module/Apps/GenericApp.mjs b/module/Apps/GenericApp.mjs index 7c1077e..2e9c1c2 100644 --- a/module/Apps/GenericApp.mjs +++ b/module/Apps/GenericApp.mjs @@ -52,6 +52,11 @@ export function GenericAppMixin(HandlebarsApp) { }; }; + /** + * @override + * This override makes it so that if the application has any framable popovers + * within it that are currently open, they will rerender as well. + */ async _onRender() { await super._onRender(); for (const manager of this._popoverManagers.values()) { diff --git a/module/Apps/popovers/AmmoTracker.mjs b/module/Apps/popovers/AmmoTracker.mjs index e1203c0..8c4c798 100644 --- a/module/Apps/popovers/AmmoTracker.mjs +++ b/module/Apps/popovers/AmmoTracker.mjs @@ -1,5 +1,7 @@ import { filePath } from "../../consts.mjs"; import { GenericPopoverMixin } from "./GenericPopoverMixin.mjs"; +import { localizer } from "../../utils/Localizer.mjs"; +import { Logger } from "../../utils/Logger.mjs"; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -15,7 +17,10 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin( `ripcrypt--AmmoTracker`, ], }, - actions: {}, + actions: { + favourite: this.#favourite, + unfavourite: this.#unfavourite, + }, }; static PARTS = { @@ -26,17 +31,63 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin( // #endregion // #region Instance Data + _favouriteCount = 0; // #endregion // #region Lifecycle async _preparePartContext(partId, data) { const ctx = { partId }; - ctx.canPin = false; - ctx.ammos = data.ammos; + + let favouriteCount = 0; + ctx.ammos = data.ammos.map(ammo => { + const favourite = ammo.getFlag(game.system.id, `favourited`) ?? false; + if (favourite) { favouriteCount++ }; + + return { + ammo, + favourite, + }; + }); + + this._favouriteCount = favouriteCount; + ctx.atFavouriteLimit = favouriteCount >= 3; return ctx; }; // #endregion // #region Actions + static async #favourite(_, el) { + const targetEl = el.closest(`[data-item-id]`); + if (!targetEl) { + Logger.warn(`Cannot find a parent element with data-item-id`); + return; + }; + + // get count of favourites + if (this._favouriteCount > 3) { + ui.notifications.error(localizer(`RipCrypt.notifs.error.at-favourite-limit`)); + return; + }; + + const data = targetEl.dataset; + const item = await fromUuid(data.itemId); + if (!item) { return }; + + item.setFlag(game.system.id, `favourited`, true); + }; + + static async #unfavourite(_, el) { + const targetEl = el.closest(`[data-item-id]`); + if (!targetEl) { + Logger.warn(`Cannot find a parent element with data-item-id`); + return; + }; + + const data = targetEl.dataset; + const item = await fromUuid(data.itemId); + if (!item) { return }; + + item.unsetFlag(game.system.id, `favourited`); + }; // #endregion }; diff --git a/module/Apps/popovers/GenericPopoverMixin.mjs b/module/Apps/popovers/GenericPopoverMixin.mjs index 017c2b0..a64b067 100644 --- a/module/Apps/popovers/GenericPopoverMixin.mjs +++ b/module/Apps/popovers/GenericPopoverMixin.mjs @@ -81,7 +81,7 @@ export function GenericPopoverMixin(HandlebarsApp) { * want it to when being created. * * Most of this implementation is identical to the ApplicationV2 - * implementation, the biggest difference is how targetLeft and targetRight + * implementation, the biggest difference is how targetLeft and targetTop * are calculated. */ _updatePosition(position) { diff --git a/templates/Apps/HeroSkillsCardV1/content.hbs b/templates/Apps/HeroSkillsCardV1/content.hbs index 776f673..5259737 100644 --- a/templates/Apps/HeroSkillsCardV1/content.hbs +++ b/templates/Apps/HeroSkillsCardV1/content.hbs @@ -118,6 +118,24 @@ {{ ammo }}
+ {{#each favouriteAmmo as | data |}} + {{#if data}} +
+ + +
+ {{else}} + {{/if}} + {{/each}} {{!-- * Currencies --}}
diff --git a/templates/Apps/HeroSkillsCardV1/style.css b/templates/Apps/HeroSkillsCardV1/style.css index c19aaff..bf35321 100644 --- a/templates/Apps/HeroSkillsCardV1/style.css +++ b/templates/Apps/HeroSkillsCardV1/style.css @@ -8,6 +8,7 @@ grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: repeat(13, minmax(0, 1fr)); column-gap: var(--col-gap); + row-gap: var(--row-gap); background: var(--base-background); color: var(--base-text); @@ -125,8 +126,13 @@ } } + label, .label { + white-space: nowrap; + text-overflow: ellipsis; + } - .input { + + input, .input { margin: 2px; border-radius: 999px; text-align: center; diff --git a/templates/Apps/popovers/AmmoTracker/ammoList.hbs b/templates/Apps/popovers/AmmoTracker/ammoList.hbs index 87118d7..a7dc8b5 100644 --- a/templates/Apps/popovers/AmmoTracker/ammoList.hbs +++ b/templates/Apps/popovers/AmmoTracker/ammoList.hbs @@ -1,18 +1,37 @@
{{#if ammos}}
    - {{#each ammos as | ammo |}} -
    - {{ ammo.name }} - {{ ammo.system.quantity }} - -
    + {{#each ammos as | data |}} +
  • + {{ data.ammo.name }} + {{ data.ammo.system.quantity }} + {{#if data.favourite}} + + {{else}} + + {{/if}} +
  • {{/each}}
{{else}} @@ -20,4 +39,4 @@ {{ rc-i18n "RipCrypt.Apps.no-ammo" }} {{/if}} -
\ No newline at end of file +
diff --git a/templates/Apps/popovers/AmmoTracker/style.css b/templates/Apps/popovers/AmmoTracker/style.css index 1f1cf1d..d81ef2d 100644 --- a/templates/Apps/popovers/AmmoTracker/style.css +++ b/templates/Apps/popovers/AmmoTracker/style.css @@ -1,8 +1,9 @@ .ripcrypt--AmmoTracker.ripcrypt--AmmoTracker { color: var(--popover-text); background: var(--popover-background); - padding: 4px 8px; + padding: 4px; + --row-gap: 4px; --button-text: var(--header-text); --button-background: var(--header-background); @@ -11,11 +12,20 @@ } ul { - &:nth-child(even) { - color: var(--popover-alt-row-text); - background: var(--popover-alt-row-background); - --button-text: unset; - --button-background: unset; + display: flex; + flex-direction: column; + row-gap: var(--row-gap); + + > li { + padding: 4px 8px; + border-radius: 999px; + + &:nth-child(even) { + color: var(--popover-alt-row-text); + background: var(--popover-alt-row-background); + --button-text: unset; + --button-background: unset; + } } } diff --git a/templates/css/common.css b/templates/css/common.css index 8d025bb..1b2758e 100644 --- a/templates/css/common.css +++ b/templates/css/common.css @@ -29,6 +29,7 @@ /* height: 270px; */ width: 680px; --col-gap: 2px; + --row-gap: 4px; } label, input, select { diff --git a/templates/css/elements/button.css b/templates/css/elements/button.css index e912a42..62431e7 100644 --- a/templates/css/elements/button.css +++ b/templates/css/elements/button.css @@ -1,6 +1,9 @@ .ripcrypt:where(.popover.frameless, .hud) button, .ripcrypt > .window-content button { all: revert; + display: flex; + justify-content: center; + align-items: center; outline: none; border: none; padding: 2px 4px; From a7e0fe899a8683a6218a148e3b9c1d41e3065973 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 5 Apr 2025 00:43:44 -0600 Subject: [PATCH 068/164] Display all of the pinned ammo on the sheet and tweak the list header style. --- langs/en-ca.json | 7 +++++-- module/Apps/ActorSheets/HeroSkillsCardV1.mjs | 11 ++++++----- templates/Apps/HeroSkillsCardV1/content.hbs | 17 ++++++++++------- templates/Apps/HeroSkillsCardV1/style.css | 14 ++++++-------- .../Apps/popovers/AmmoTracker/ammoList.hbs | 8 +++++--- 5 files changed, 32 insertions(+), 25 deletions(-) diff --git a/langs/en-ca.json b/langs/en-ca.json index de9e051..1d2ad3e 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -173,10 +173,13 @@ "rollTarget": "Target", "difficulty": "(DC: {dc})", "RichEditor-no-collaborative": "Warning: This editor is not collaborative, that means that if you and someone else are editing it at the same time, you won't see that someone else is making changes until they save, and then your changes will be lost.", + "starred-ammo-placeholder": "Starred Ammo Slot", "AmmoTracker": { "no-ammo": "You don't have any ammo!", - "pin-button": "Pin {name} to your character sheet", - "pin-button-tooltip": "Pin {name}" + "star-button": "Add {name} as a starred ammo", + "star-button-tooltip": "Add Star", + "unstar-button": "Remove {name} as a starred ammo", + "unstar-button-tooltip": "Remove Star" } }, "notifs": { diff --git a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs index be8fb26..7b1301a 100644 --- a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs @@ -140,20 +140,21 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin static async prepareAmmo(ctx) { let total = 0; - ctx.favouriteAmmo = []; + let favouriteCount = 0; + ctx.favouriteAmmo = new Array(3).fill(null); for (const ammo of ctx.actor.itemTypes.ammo) { total += ammo.system.quantity; - if (ctx.favouriteAmmo.length < 3 && ammo.getFlag(game.system.id, `favourited`)) { - ctx.favouriteAmmo.push({ + if (favouriteCount < 3 && ammo.getFlag(game.system.id, `favourited`)) { + ctx.favouriteAmmo[favouriteCount] = { uuid: ammo.uuid, name: ammo.name, quantity: ammo.system.quantity, - }); + }; + favouriteCount++; }; }; - ctx.favouriteAmmo.length = 3; // assert array length ctx.ammo = total; return ctx; diff --git a/templates/Apps/HeroSkillsCardV1/content.hbs b/templates/Apps/HeroSkillsCardV1/content.hbs index 5259737..6f8c11b 100644 --- a/templates/Apps/HeroSkillsCardV1/content.hbs +++ b/templates/Apps/HeroSkillsCardV1/content.hbs @@ -104,7 +104,7 @@ {{/each}} -
+
-
{{else}} +
+ {{ rc-i18n "RipCrypt.Apps.starred-ammo-placeholder" }} +
{{/if}} {{/each}} {{!-- * Currencies --}}
-
+
@@ -150,7 +153,7 @@ value="0" >
-
+
@@ -161,7 +164,7 @@ value="0" >
-
+
diff --git a/templates/Apps/HeroSkillsCardV1/style.css b/templates/Apps/HeroSkillsCardV1/style.css index bf35321..8c654b0 100644 --- a/templates/Apps/HeroSkillsCardV1/style.css +++ b/templates/Apps/HeroSkillsCardV1/style.css @@ -36,6 +36,7 @@ display: flex; justify-content: space-between; align-items: center; + border-radius: 999px; } .skill-list { display: grid; @@ -107,33 +108,30 @@ grid-template-columns: repeat(3, minmax(0, 1fr)); } - .half-pill { + .pill { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); align-items: center; background: var(--section-header-background); - border-radius: 0 999px 999px 0; + border-radius: 999px; color: var(--section-header-text); + padding: 2px 0 2px 4px; --input-background: var(--base-background); --input-text: var(--base-text); &.with-icon { grid-template-columns: min-content minmax(0, 1.5fr) minmax(0, 1fr); gap: 4px; - padding: 2px 0 2px 4px; - .label { - padding: 0; - } } label, .label { + padding: 0; white-space: nowrap; text-overflow: ellipsis; } - input, .input { - margin: 2px; + margin: 0 2px 0 0; border-radius: 999px; text-align: center; } diff --git a/templates/Apps/popovers/AmmoTracker/ammoList.hbs b/templates/Apps/popovers/AmmoTracker/ammoList.hbs index a7dc8b5..67d43b2 100644 --- a/templates/Apps/popovers/AmmoTracker/ammoList.hbs +++ b/templates/Apps/popovers/AmmoTracker/ammoList.hbs @@ -1,4 +1,4 @@ -
+
{{#if ammos}}
    {{#each ammos as | data |}} @@ -10,7 +10,8 @@ type="button" class="icon" data-action="unfavourite" - aria-label="Unpin ammo" + aria-label="{{ rc-i18n "RipCrypt.Apps.AmmoTracker.unstar-button" name=data.ammo.name }}" + data-tooltip="{{ rc-i18n "RipCrypt.Apps.AmmoTracker.unstar-button-tooltip" name=data.ammo.name }}" > Date: Sat, 5 Apr 2025 15:29:49 -0600 Subject: [PATCH 069/164] Localize the app title --- langs/en-ca.json | 3 +++ module/Apps/popovers/AmmoTracker.mjs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/langs/en-ca.json b/langs/en-ca.json index 1d2ad3e..1fef505 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -21,6 +21,9 @@ "HeroCraftCardV1": "Hero Craft Card", "HeroSkillsCardV1": "Hero Skill Card" }, + "app-titles": { + "AmmoTracker": "Ammo Tracker" + }, "common": { "abilities": { "grit": "Grit", diff --git a/module/Apps/popovers/AmmoTracker.mjs b/module/Apps/popovers/AmmoTracker.mjs index 8c4c798..6e0e399 100644 --- a/module/Apps/popovers/AmmoTracker.mjs +++ b/module/Apps/popovers/AmmoTracker.mjs @@ -12,7 +12,7 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin( `ripcrypt`, ], window: { - title: `Ammo Tracker`, + title: `RipCrypt.app-titles.AmmoTracker`, contentClasses: [ `ripcrypt--AmmoTracker`, ], From 7ae5d1b814359bb42b7a996a3ddd81c6d2c0d2de Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 5 Apr 2025 15:30:00 -0600 Subject: [PATCH 070/164] Get rid of extraneous function override --- module/Apps/ActorSheets/HeroSkillsCardV1.mjs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs index 7b1301a..cccc359 100644 --- a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs @@ -42,10 +42,6 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin // #endregion // #region Lifecycle - async _onFirstRender(context, options) { - await super._onFirstRender(context, options); - }; - async _onRender(context, options) { await super._onRender(context, options); HeroSkillsCardV1._onRender.bind(this)(context, options); From 95443d3709b961c74ccfbfb0bf4c91a96383b929 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 5 Apr 2025 15:30:26 -0600 Subject: [PATCH 071/164] Pull the tooltip delay from the Foundry tooltip class --- module/consts.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/consts.mjs b/module/consts.mjs index 242d332..405707e 100644 --- a/module/consts.mjs +++ b/module/consts.mjs @@ -63,5 +63,5 @@ export function documentSorter(a, b) { * @returns The number of milliseconds for the timeout */ export function getTooltipDelay() { - return 1000; // game.tooltip.constructor.TOOLTIP_ACTIVATION_MS; + return game.tooltip.constructor.TOOLTIP_ACTIVATION_MS; }; From bfddf855a48b25424c22537da424abe95338132c Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 5 Apr 2025 15:31:28 -0600 Subject: [PATCH 072/164] Move the magic string into an enum --- module/Apps/ActorSheets/HeroSkillsCardV1.mjs | 3 ++- module/Apps/popovers/AmmoTracker.mjs | 7 ++++--- module/api.mjs | 6 +++++- module/flags/item.mjs | 4 ++++ 4 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 module/flags/item.mjs diff --git a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs index cccc359..76f0e9d 100644 --- a/module/Apps/ActorSheets/HeroSkillsCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSkillsCardV1.mjs @@ -3,6 +3,7 @@ import { documentSorter, filePath } from "../../consts.mjs"; import { AmmoTracker } from "../popovers/AmmoTracker.mjs"; import { gameTerms } from "../../gameTerms.mjs"; import { GenericAppMixin } from "../GenericApp.mjs"; +import { ItemFlags } from "../../flags/item.mjs"; import { localizer } from "../../utils/Localizer.mjs"; import { Logger } from "../../utils/Logger.mjs"; import { PopoverEventManager } from "../../utils/PopoverEventManager.mjs"; @@ -142,7 +143,7 @@ export class HeroSkillsCardV1 extends GenericAppMixin(HandlebarsApplicationMixin for (const ammo of ctx.actor.itemTypes.ammo) { total += ammo.system.quantity; - if (favouriteCount < 3 && ammo.getFlag(game.system.id, `favourited`)) { + if (favouriteCount < 3 && ammo.getFlag(game.system.id, ItemFlags.FAVOURITE)) { ctx.favouriteAmmo[favouriteCount] = { uuid: ammo.uuid, name: ammo.name, diff --git a/module/Apps/popovers/AmmoTracker.mjs b/module/Apps/popovers/AmmoTracker.mjs index 6e0e399..c91e3a4 100644 --- a/module/Apps/popovers/AmmoTracker.mjs +++ b/module/Apps/popovers/AmmoTracker.mjs @@ -1,5 +1,6 @@ import { filePath } from "../../consts.mjs"; import { GenericPopoverMixin } from "./GenericPopoverMixin.mjs"; +import { ItemFlags } from "../../flags/item.mjs"; import { localizer } from "../../utils/Localizer.mjs"; import { Logger } from "../../utils/Logger.mjs"; @@ -40,7 +41,7 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin( let favouriteCount = 0; ctx.ammos = data.ammos.map(ammo => { - const favourite = ammo.getFlag(game.system.id, `favourited`) ?? false; + const favourite = ammo.getFlag(game.system.id, ItemFlags.FAVOURITE) ?? false; if (favourite) { favouriteCount++ }; return { @@ -73,7 +74,7 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin( const item = await fromUuid(data.itemId); if (!item) { return }; - item.setFlag(game.system.id, `favourited`, true); + item.setFlag(game.system.id, ItemFlags.FAVOURITE, true); }; static async #unfavourite(_, el) { @@ -87,7 +88,7 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin( const item = await fromUuid(data.itemId); if (!item) { return }; - item.unsetFlag(game.system.id, `favourited`); + item.unsetFlag(game.system.id, ItemFlags.FAVOURITE); }; // #endregion }; diff --git a/module/api.mjs b/module/api.mjs index ee40143..389ca3e 100644 --- a/module/api.mjs +++ b/module/api.mjs @@ -1,4 +1,5 @@ // App imports +import { AmmoTracker } from "./Apps/popovers/AmmoTracker.mjs"; import { CombinedHeroSheet } from "./Apps/ActorSheets/CombinedHeroSheet.mjs"; import { DicePool } from "./Apps/DicePool.mjs"; import { HeroSkillsCardV1 } from "./Apps/ActorSheets/HeroSkillsCardV1.mjs"; @@ -9,7 +10,9 @@ import { RichEditor } from "./Apps/RichEditor.mjs"; import { distanceBetweenFates, nextFate, previousFate } from "./utils/fates.mjs"; import { documentSorter } from "./consts.mjs"; import { rankToInteger } from "./utils/rank.mjs"; -import { AmmoTracker } from "./Apps/popovers/AmmoTracker.mjs"; + +// Misc Imports +import { ItemFlags } from "./flags/item.mjs"; const { deepFreeze } = foundry.utils; @@ -33,6 +36,7 @@ Object.defineProperty( previousFate, rankToInteger, }, + ItemFlags, }), writable: false, }, diff --git a/module/flags/item.mjs b/module/flags/item.mjs new file mode 100644 index 0000000..de53d3b --- /dev/null +++ b/module/flags/item.mjs @@ -0,0 +1,4 @@ +export const ItemFlags = Object.freeze({ + /** The boolean value to indicate if an item is considered favourited/starred or not */ + FAVOURITE: `favourited`, +}); From 228cc21de7216b97394c97bb238691c0f5d09041 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 5 Apr 2025 15:32:31 -0600 Subject: [PATCH 073/164] Make the ID publicly readonly, privately writable --- module/utils/PopoverEventManager.mjs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/module/utils/PopoverEventManager.mjs b/module/utils/PopoverEventManager.mjs index ff1fd74..07e4793 100644 --- a/module/utils/PopoverEventManager.mjs +++ b/module/utils/PopoverEventManager.mjs @@ -3,7 +3,11 @@ import { Logger } from "./Logger.mjs"; export class PopoverEventManager { #options; - id; + #id; + + get id() { + return this.#id; + }; /** @type {Map} */ static #existing = new Map(); @@ -14,7 +18,7 @@ export class PopoverEventManager { */ constructor(id, element, popoverClass, options = {}) { id = `${id}-${popoverClass.name}`; - this.id = id; + this.#id = id; if (PopoverEventManager.#existing.has(id)) { const manager = PopoverEventManager.#existing.get(id); @@ -101,7 +105,7 @@ export class PopoverEventManager { #construct(options) { options.popover ??= {}; - options.popover.managerId = this.id; + options.popover.managerId = this.#id; return new this.#class(options); }; From 8de50185c13a632d8425b75ef95e2c6dd940deee Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 5 Apr 2025 15:32:55 -0600 Subject: [PATCH 074/164] Remove unused CSS --- templates/Apps/popovers/AmmoTracker/style.css | 3 --- 1 file changed, 3 deletions(-) diff --git a/templates/Apps/popovers/AmmoTracker/style.css b/templates/Apps/popovers/AmmoTracker/style.css index d81ef2d..d902772 100644 --- a/templates/Apps/popovers/AmmoTracker/style.css +++ b/templates/Apps/popovers/AmmoTracker/style.css @@ -35,9 +35,6 @@ grid-template-rows: min-content; align-items: center; justify-items: center; - /* display: flex; - flex-direction: row; - align-items: center; */ gap: 8px; .name { From 26134b03908606dab15a1cc0112911cdb245352a Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 5 Apr 2025 23:33:20 -0600 Subject: [PATCH 075/164] Correct the height of the HUD element to prevent collapse only when not a GM --- templates/Apps/DelveDiceHUD/tour/next.hbs | 6 +++--- templates/Apps/DelveDiceHUD/tour/previous.hbs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/templates/Apps/DelveDiceHUD/tour/next.hbs b/templates/Apps/DelveDiceHUD/tour/next.hbs index d81c879..5e102f0 100644 --- a/templates/Apps/DelveDiceHUD/tour/next.hbs +++ b/templates/Apps/DelveDiceHUD/tour/next.hbs @@ -1,7 +1,4 @@
    - {{!-- This is here to prevent height collapsing --}} - ​ - {{#if meta.editable}} + {{else}} + {{!-- This is here to prevent height collapsing --}} + ​ {{/if}}
    diff --git a/templates/Apps/DelveDiceHUD/tour/previous.hbs b/templates/Apps/DelveDiceHUD/tour/previous.hbs index 1120e8f..b8c59b7 100644 --- a/templates/Apps/DelveDiceHUD/tour/previous.hbs +++ b/templates/Apps/DelveDiceHUD/tour/previous.hbs @@ -1,7 +1,4 @@
    - {{!-- This is here to prevent height collapsing --}} - ​ - {{#if meta.editable}} + {{else}} + {{!-- This is here to prevent height collapsing --}} + ​ {{/if}}
    From f1487bd9b8a9d06e5f8f54b2049d3ab0c4ae570e Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Wed, 9 Apr 2025 21:08:35 -0600 Subject: [PATCH 076/164] Correctly forward the parameters to the super method --- module/Apps/GenericApp.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/Apps/GenericApp.mjs b/module/Apps/GenericApp.mjs index 2e9c1c2..413569d 100644 --- a/module/Apps/GenericApp.mjs +++ b/module/Apps/GenericApp.mjs @@ -57,8 +57,8 @@ export function GenericAppMixin(HandlebarsApp) { * This override makes it so that if the application has any framable popovers * within it that are currently open, they will rerender as well. */ - async _onRender() { - await super._onRender(); + async _onRender(...args) { + await super._onRender(...args); for (const manager of this._popoverManagers.values()) { manager.render(); }; From 4e89763901e8c1cd9757ba044ec0d57cae851c82 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Wed, 9 Apr 2025 21:08:52 -0600 Subject: [PATCH 077/164] Add localization string override for GM -> Keeper --- langs/en-ca.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/langs/en-ca.json b/langs/en-ca.json index 1fef505..8eab18e 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -209,5 +209,8 @@ "heavy": "The distance of your aura when using Heavycraft" } } + }, + "USER": { + "GM": "Keeper" } } From 01f9fba5934688761c64f83ae0f269a6aeb1578c Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Wed, 9 Apr 2025 21:42:32 -0600 Subject: [PATCH 078/164] Add the ability to update the ammo quantity from the starred shortcuts --- module/Apps/GenericApp.mjs | 44 ++++++++++++++++++--- templates/Apps/HeroSkillsCardV1/content.hbs | 6 ++- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/module/Apps/GenericApp.mjs b/module/Apps/GenericApp.mjs index 413569d..c131baa 100644 --- a/module/Apps/GenericApp.mjs +++ b/module/Apps/GenericApp.mjs @@ -52,16 +52,34 @@ export function GenericAppMixin(HandlebarsApp) { }; }; - /** - * @override - * This override makes it so that if the application has any framable popovers - * within it that are currently open, they will rerender as well. - */ + /** @override */ async _onRender(...args) { await super._onRender(...args); + + /* + Rendering each of the popover managers associated with this app allows us + to have them be dynamic and update when their parent application is rerendered, + this could eventually be something we can move into the Document's apps + collection so Foundry auto-rerenders it, but because it isn't actually + associated with the Document (as it's dependendant on the Application), I + decided that it would be best to do my own handling for it. + */ for (const manager of this._popoverManagers.values()) { manager.render(); }; + + /* + Foreign update listeners so that we can easily update items that may not + be this document itself, but are useful to be able to be edited from this + sheet. Primarily useful for editing the Actors' Item collection, or an Items' + ActiveEffect collection. + */ + this.element.querySelectorAll(`input[data-foreign-update-on]`).forEach(el => { + const events = el.dataset.foreignUpdateOn.split(`,`); + for (const event of events) { + el.addEventListener(event, this.updateEmbedded); + }; + }); }; async _preparePartContext(partId, ctx, opts) { @@ -132,6 +150,22 @@ export function GenericAppMixin(HandlebarsApp) { }); app.render({ force: true }); }; + + /** + * @param {Event} event + */ + async updateForeign(event) { + const target = event.currentTarget; + const data = target.dataset; + const document = await fromUuid(data.foreignUuid); + + let value = target.value; + switch (target.type) { + case `checkbox`: value = target.checked; break; + }; + + await document?.update({ [data.foreignName]: value }); + }; // #endregion }; return GenericRipCryptApp; diff --git a/templates/Apps/HeroSkillsCardV1/content.hbs b/templates/Apps/HeroSkillsCardV1/content.hbs index 6f8c11b..a4a7a56 100644 --- a/templates/Apps/HeroSkillsCardV1/content.hbs +++ b/templates/Apps/HeroSkillsCardV1/content.hbs @@ -129,8 +129,12 @@
{{else}} From 55cff3c048c1b606df1c2b502dffa9531c491207 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Wed, 9 Apr 2025 21:43:30 -0600 Subject: [PATCH 079/164] Set verified version Foundry v13.339 --- system.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system.json b/system.json index 68d0f25..06aecb9 100644 --- a/system.json +++ b/system.json @@ -5,7 +5,7 @@ "version": "0.0.1", "compatibility": { "minimum": 13, - "verified": 13, + "verified": 13.339, "maximum": 13 }, "authors": [ From 053ab05dcba53ff83f00cf0fbd3c2a094dcfc5a4 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Wed, 9 Apr 2025 21:51:49 -0600 Subject: [PATCH 080/164] Pull the foreign document updating into a utility method and add it to the GenericPopoverMixin --- module/Apps/GenericApp.mjs | 20 ++----------------- module/Apps/popovers/GenericPopoverMixin.mjs | 19 ++++++++++++++++++ module/Apps/utils.mjs | 21 ++++++++++++++++++++ 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/module/Apps/GenericApp.mjs b/module/Apps/GenericApp.mjs index c131baa..1fe5edf 100644 --- a/module/Apps/GenericApp.mjs +++ b/module/Apps/GenericApp.mjs @@ -1,4 +1,4 @@ -import { createItemFromElement, deleteItemFromElement, editItemFromElement } from "./utils.mjs"; +import { createItemFromElement, deleteItemFromElement, editItemFromElement, updateForeignDocumentFromEvent } from "./utils.mjs"; import { DicePool } from "./DicePool.mjs"; import { RichEditor } from "./RichEditor.mjs"; import { toBoolean } from "../consts.mjs"; @@ -77,7 +77,7 @@ export function GenericAppMixin(HandlebarsApp) { this.element.querySelectorAll(`input[data-foreign-update-on]`).forEach(el => { const events = el.dataset.foreignUpdateOn.split(`,`); for (const event of events) { - el.addEventListener(event, this.updateEmbedded); + el.addEventListener(event, updateForeignDocumentFromEvent); }; }); }; @@ -150,22 +150,6 @@ export function GenericAppMixin(HandlebarsApp) { }); app.render({ force: true }); }; - - /** - * @param {Event} event - */ - async updateForeign(event) { - const target = event.currentTarget; - const data = target.dataset; - const document = await fromUuid(data.foreignUuid); - - let value = target.value; - switch (target.type) { - case `checkbox`: value = target.checked; break; - }; - - await document?.update({ [data.foreignName]: value }); - }; // #endregion }; return GenericRipCryptApp; diff --git a/module/Apps/popovers/GenericPopoverMixin.mjs b/module/Apps/popovers/GenericPopoverMixin.mjs index a64b067..271bb9a 100644 --- a/module/Apps/popovers/GenericPopoverMixin.mjs +++ b/module/Apps/popovers/GenericPopoverMixin.mjs @@ -1,3 +1,5 @@ +import { updateForeignDocumentFromEvent } from "../utils.mjs"; + const { ApplicationV2 } = foundry.applications.api; /** @@ -65,6 +67,23 @@ export function GenericPopoverMixin(HandlebarsApp) { }; }; + async _onRender(...args) { + await super._onRender(...args); + + /* + Foreign update listeners so that we can easily update items that may not + be this document itself, but are useful to be able to be edited from this + sheet. Primarily useful for editing the Actors' Item collection, or an Items' + ActiveEffect collection. + */ + this.element.querySelectorAll(`input[data-foreign-update-on]`).forEach(el => { + const events = el.dataset.foreignUpdateOn.split(`,`); + for (const event of events) { + el.addEventListener(event, updateForeignDocumentFromEvent); + }; + }); + }; + async close(options = {}) { // prevent locked popovers from being closed if (this.popover.locked && !options.force) { return }; diff --git a/module/Apps/utils.mjs b/module/Apps/utils.mjs index 1baee18..7c55ec4 100644 --- a/module/Apps/utils.mjs +++ b/module/Apps/utils.mjs @@ -42,3 +42,24 @@ export async function deleteItemFromElement(target) { const item = await fromUuid(itemId); item.delete(); }; + +/** + * Updates a document using the UUID, expects there to be the following + * dataset attributes: + * - "data-foreign-uuid" : The UUID of the document to update + * - "data-foreign-name" : The dot-separated path of the value to update + * + * @param {Event} event + */ +export async function updateForeignDocumentFromEvent(event) { + const target = event.currentTarget; + const data = target.dataset; + const document = await fromUuid(data.foreignUuid); + + let value = target.value; + switch (target.type) { + case `checkbox`: value = target.checked; break; + }; + + await document?.update({ [data.foreignName]: value }); +}; From 05a3db98c8816e392cc771e8d629beff7e5b9978 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Wed, 9 Apr 2025 22:13:52 -0600 Subject: [PATCH 081/164] Get the AmmoTracker's in-popover editing working using the foreign document updating --- module/Apps/popovers/AmmoTracker.mjs | 5 ++++- templates/Apps/popovers/AmmoTracker/ammoList.hbs | 11 ++++++++++- templates/Apps/popovers/AmmoTracker/style.css | 16 +++++++++------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/module/Apps/popovers/AmmoTracker.mjs b/module/Apps/popovers/AmmoTracker.mjs index c91e3a4..6171c75 100644 --- a/module/Apps/popovers/AmmoTracker.mjs +++ b/module/Apps/popovers/AmmoTracker.mjs @@ -37,7 +37,10 @@ export class AmmoTracker extends GenericPopoverMixin(HandlebarsApplicationMixin( // #region Lifecycle async _preparePartContext(partId, data) { - const ctx = { partId }; + const ctx = { + meta: { idp: this.id }, + partId, + }; let favouriteCount = 0; ctx.ammos = data.ammos.map(ammo => { diff --git a/templates/Apps/popovers/AmmoTracker/ammoList.hbs b/templates/Apps/popovers/AmmoTracker/ammoList.hbs index 67d43b2..a9ed916 100644 --- a/templates/Apps/popovers/AmmoTracker/ammoList.hbs +++ b/templates/Apps/popovers/AmmoTracker/ammoList.hbs @@ -1,10 +1,19 @@
+ {{log @root}} {{#if ammos}}
    {{#each ammos as | data |}}
  • {{ data.ammo.name }} - {{ data.ammo.system.quantity }} + {{#if data.favourite}}