From a956fd2d8e5b9fa2eb31805828b38a0ce5578aec Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 10 Dec 2024 21:18:50 -0700 Subject: [PATCH 001/252] RC-15 | Add currency data to the hero --- module/data/Actor/Hero.mjs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/module/data/Actor/Hero.mjs b/module/data/Actor/Hero.mjs index 48dd4f5..a09a59b 100644 --- a/module/data/Actor/Hero.mjs +++ b/module/data/Actor/Hero.mjs @@ -33,6 +33,26 @@ export class HeroData extends foundry.abstract.TypeDataModel { nullable: false, }), }), + coin: new fields.SchemaField({ + gold: new fields.NumberField({ + initial: 5, + integer: true, + required: true, + nullable: false, + }), + silver: new fields.NumberField({ + initial: 0, + integer: true, + required: true, + nullable: false, + }), + copper: new fields.NumberField({ + initial: 0, + integer: true, + required: true, + nullable: false, + }), + }), }); return schema; }; From 43658301c9a3c58078175173ec54f3d0ecce3915 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 10 Dec 2024 21:37:11 -0700 Subject: [PATCH 002/252] RC-16 | Item limits base data --- module/data/Actor/Hero.mjs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/module/data/Actor/Hero.mjs b/module/data/Actor/Hero.mjs index a09a59b..18a3468 100644 --- a/module/data/Actor/Hero.mjs +++ b/module/data/Actor/Hero.mjs @@ -57,7 +57,16 @@ export class HeroData extends foundry.abstract.TypeDataModel { return schema; }; - prepareBaseData() {}; + prepareBaseData() { + super.prepareBaseData(); + + // The limitations imposed on things like inventory spaces and equipped + // weapon count + this.limit = { + weapons: 4, + equipment: 12, + }; + }; prepareDerivedData() {}; }; From 7af02261d6910b5630a82548949993a18dd1902a Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 10 Dec 2024 22:39:32 -0700 Subject: [PATCH 003/252] Fix data model so it doesn't crash Foundry --- module/data/Actor/Hero.mjs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/module/data/Actor/Hero.mjs b/module/data/Actor/Hero.mjs index 18a3468..0bb6c17 100644 --- a/module/data/Actor/Hero.mjs +++ b/module/data/Actor/Hero.mjs @@ -2,7 +2,7 @@ const { fields } = foundry.data; export class HeroData extends foundry.abstract.TypeDataModel { static defineSchema() { - const schema = new fields.SchemaField({ + return { ability: new fields.SchemaField({ grit: new fields.NumberField({ min: 0, @@ -53,8 +53,7 @@ export class HeroData extends foundry.abstract.TypeDataModel { nullable: false, }), }), - }); - return schema; + }; }; prepareBaseData() { From 49c37b66f8e615926a45c798eba1bd9abf7b3955 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 10 Dec 2024 22:39:47 -0700 Subject: [PATCH 004/252] Actually instantiate the system in Foundry correctly --- module/hooks/hotReload.mjs | 4 +++- module/main.mjs | 4 ++++ module/utils/Logger.mjs | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/module/hooks/hotReload.mjs b/module/hooks/hotReload.mjs index 424fbee..67a44aa 100644 --- a/module/hooks/hotReload.mjs +++ b/module/hooks/hotReload.mjs @@ -1,3 +1,5 @@ +import { Logger } from "../utils/Logger.mjs"; + const loaders = { svg(data) { const iconName = data.path.split(`/`).slice(-1)[0].slice(0, -4); @@ -14,4 +16,4 @@ const loaders = { Hooks.on(`hotReload`, async (data) => { if (!loaders[data.extension]) {return} return loaders[data.extension](data); -}); \ No newline at end of file +}); diff --git a/module/main.mjs b/module/main.mjs index e69de29..58ec33d 100644 --- a/module/main.mjs +++ b/module/main.mjs @@ -0,0 +1,4 @@ +// Hooks +import "./hooks/init.mjs"; +import "./hooks/ready.mjs"; +import "./hooks/hotReload.mjs"; diff --git a/module/utils/Logger.mjs b/module/utils/Logger.mjs index 66730bc..fc1b51c 100644 --- a/module/utils/Logger.mjs +++ b/module/utils/Logger.mjs @@ -19,4 +19,4 @@ export const Logger = new Proxy(console, { }; return target[prop]; }, -}); \ No newline at end of file +}); From 0f9afca2746458a8c2a2bf0af365acdd8c621d5f Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 10 Dec 2024 22:40:11 -0700 Subject: [PATCH 005/252] RC-14 | Add the speed derived data --- module/data/Actor/Hero.mjs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/module/data/Actor/Hero.mjs b/module/data/Actor/Hero.mjs index 0bb6c17..8bf0756 100644 --- a/module/data/Actor/Hero.mjs +++ b/module/data/Actor/Hero.mjs @@ -67,5 +67,13 @@ export class HeroData extends foundry.abstract.TypeDataModel { }; }; - prepareDerivedData() {}; + prepareDerivedData() { + super.prepareDerivedData(); + + // Movement speeds + this.speed = { + move: this.ability.gait + 3, + run: (this.ability.gait + 3) * 2, + }; + }; }; From f9ef7c2c1884aa0c89f5961a1dbcdf2114129258 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 10 Dec 2024 22:50:58 -0700 Subject: [PATCH 006/252] RC-19 | Add Glory to the datamodel --- module/data/Actor/Hero.mjs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/module/data/Actor/Hero.mjs b/module/data/Actor/Hero.mjs index 8bf0756..c84ec64 100644 --- a/module/data/Actor/Hero.mjs +++ b/module/data/Actor/Hero.mjs @@ -53,6 +53,15 @@ export class HeroData extends foundry.abstract.TypeDataModel { nullable: false, }), }), + level: new fields.SchemaField({ + glory: new fields.NumberField({ + min: 0, + initial: 0, + integer: true, + required: true, + nullable: false, + }), + }), }; }; From 27f668b92faef0b669a8d12c37c95f5d0820a2aa Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 10 Dec 2024 22:53:26 -0700 Subject: [PATCH 007/252] RC-17 | Add rank to the datamodel --- module/data/Actor/Hero.mjs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/module/data/Actor/Hero.mjs b/module/data/Actor/Hero.mjs index c84ec64..07369c8 100644 --- a/module/data/Actor/Hero.mjs +++ b/module/data/Actor/Hero.mjs @@ -61,6 +61,14 @@ export class HeroData extends foundry.abstract.TypeDataModel { required: true, nullable: false, }), + rank: new fields.NumberField({ + min: 0, + initial: 0, + max: 3, + integer: true, + required: true, + nullable: false, + }), }), }; }; From 550aeb084fe4dd8ddebf7eac85ab060a362b09c6 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 10 Dec 2024 23:33:09 -0700 Subject: [PATCH 008/252] RC-20 | Add Fate path to datamodel --- module/data/Actor/Hero.mjs | 11 +++++++++++ module/gameTerms.mjs | 6 ++++++ 2 files changed, 17 insertions(+) create mode 100644 module/gameTerms.mjs diff --git a/module/data/Actor/Hero.mjs b/module/data/Actor/Hero.mjs index 07369c8..9b0f065 100644 --- a/module/data/Actor/Hero.mjs +++ b/module/data/Actor/Hero.mjs @@ -1,3 +1,5 @@ +import { FatePath } from "../../gameTerms.mjs"; + const { fields } = foundry.data; export class HeroData extends foundry.abstract.TypeDataModel { @@ -53,6 +55,15 @@ export class HeroData extends foundry.abstract.TypeDataModel { nullable: false, }), }), + fate: new fields.StringField({ + initial: ``, + blank: true, + trim: true, + nullable: false, + choices: () => { + return Object.values(FatePath).concat(``); + }, + }), level: new fields.SchemaField({ glory: new fields.NumberField({ min: 0, diff --git a/module/gameTerms.mjs b/module/gameTerms.mjs new file mode 100644 index 0000000..36a0200 --- /dev/null +++ b/module/gameTerms.mjs @@ -0,0 +1,6 @@ +export const FatePath = { + NORTH: `North`, + EAST: `East`, + SOUTH: `South`, + WEST: `West`, +}; From fbc574d1a5c94ed17adf7f1b89ff36fdd26b1dc7 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 10 Dec 2024 23:34:47 -0700 Subject: [PATCH 009/252] RC-18 | Add step to the level data --- module/data/Actor/Hero.mjs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/module/data/Actor/Hero.mjs b/module/data/Actor/Hero.mjs index 9b0f065..12f8c64 100644 --- a/module/data/Actor/Hero.mjs +++ b/module/data/Actor/Hero.mjs @@ -72,6 +72,14 @@ export class HeroData extends foundry.abstract.TypeDataModel { required: true, nullable: false, }), + step: new fields.NumberField({ + min: 1, + initial: 1, + max: 3, + integer: true, + required: true, + nullable: false, + }), rank: new fields.NumberField({ min: 0, initial: 0, From 930b24a2a9f6e0d100d92277674def776ddef810 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 12 Dec 2024 00:24:59 -0700 Subject: [PATCH 010/252] Ignore symlinks --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e07cc8e..23d5f03 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ dist/ +*.link # Dependency directories node_modules/ From 2fc3b867921baddb224423c9f4f333d8af651db9 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 12 Dec 2024 00:39:09 -0700 Subject: [PATCH 011/252] Add dev settings for convenience --- module/hooks/init.mjs | 24 +++++++++++++++++++++++- module/hooks/ready.mjs | 14 ++++++++++++++ module/settings/devSettings.mjs | 17 +++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 module/settings/devSettings.mjs diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 4d9e1cf..03f4c17 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -1,9 +1,31 @@ +// Applications +import { HeroSummaryCardV1 } from "../Apps/ActorSheets/HeroSummaryCardV1.mjs"; + +// Data Models import { HeroData } from "../data/Actor/Hero.mjs"; +import { registerDevSettings } from "../settings/devSettings.mjs"; + +// Misc import { Logger } from "../utils/Logger.mjs"; Hooks.once(`init`, () => { Logger.log(`Initializing`); - // Datamodel registrations + // #region Settings + registerDevSettings(); + // #endregion + + // #region Datamodels CONFIG.Actor.dataModels.hero = HeroData; + // #endregion + + // #region Sheets + // #region Actors + Actors.registerSheet(game.system.id, HeroSummaryCardV1, { + makeDefault: true, + types: [`hero`], + label: `RipCrypt.sheet-names.HeroSummaryCardV1`, + }); + // #endregion + // #endregion }); diff --git a/module/hooks/ready.mjs b/module/hooks/ready.mjs index dbea1e1..e367c81 100644 --- a/module/hooks/ready.mjs +++ b/module/hooks/ready.mjs @@ -2,4 +2,18 @@ import { Logger } from "../utils/Logger.mjs"; Hooks.once(`ready`, () => { Logger.log(`Ready`); + + let defaultTab = game.settings.get(`ripcrypt`, `defaultTab`); + if (defaultTab) { + if (!ui.sidebar?.tabs?.[defaultTab]) { + Logger.error(`Couldn't find a sidebar tab with ID:`, defaultTab); + } else { + Logger.debug(`Switching sidebar tab to:`, defaultTab); + ui.sidebar.tabs[defaultTab].activate(); + }; + }; + + if (game.settings.get(`ripcrypt`, `devMode`)) { + if (game.paused) { game.togglePause() }; + }; }); diff --git a/module/settings/devSettings.mjs b/module/settings/devSettings.mjs new file mode 100644 index 0000000..69767d9 --- /dev/null +++ b/module/settings/devSettings.mjs @@ -0,0 +1,17 @@ +export function registerDevSettings() { + game.settings.register(`ripcrypt`, `devMode`, { + scope: `client`, + type: Boolean, + config: false, + default: false, + requiresReload: false, + }); + + game.settings.register(`ripcrypt`, `defaultTab`, { + name: `Default Tab`, + scope: `client`, + type: String, + config: game.settings.get(`ripcrypt`, `devMode`), + requiresReload: false, + }); +}; From b10535ecebd992d9a51bc3d6fa843f5cd49f0180 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 20 Dec 2024 00:19:58 -0700 Subject: [PATCH 012/252] RC-25 | Initialize the Hero Summary Sheet application --- Apps/HeroSummaryCardV1/content.hbs | 24 +++++++++ Apps/HeroSummaryCardV1/style.css | 50 +++++++++++++++++ Apps/common.css | 12 +++++ langs/en-ca.json | 5 ++ module/Apps/ActorSheets/HeroSummaryCardV1.mjs | 53 +++++++++++++++++++ module/consts.mjs | 7 +++ system.json | 5 +- 7 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 Apps/HeroSummaryCardV1/content.hbs create mode 100644 Apps/HeroSummaryCardV1/style.css create mode 100644 Apps/common.css create mode 100644 module/Apps/ActorSheets/HeroSummaryCardV1.mjs create mode 100644 module/consts.mjs diff --git a/Apps/HeroSummaryCardV1/content.hbs b/Apps/HeroSummaryCardV1/content.hbs new file mode 100644 index 0000000..7c63780 --- /dev/null +++ b/Apps/HeroSummaryCardV1/content.hbs @@ -0,0 +1,24 @@ +
+ {{!-- * Header --}} +
+
Logo Image
+ +
+ Player +
+
+ + + {{!-- * Armour --}} +
+ + {{!-- * Fate & Advancement --}} + + {{!-- * Weapons --}} + + {{!-- * Skills --}} + + {{!-- * Equipment --}} +
diff --git a/Apps/HeroSummaryCardV1/style.css b/Apps/HeroSummaryCardV1/style.css new file mode 100644 index 0000000..163f195 --- /dev/null +++ b/Apps/HeroSummaryCardV1/style.css @@ -0,0 +1,50 @@ +.ripcrypt .HeroSummaryCardV1 { + display: grid; + grid-template-columns: minmax(0, 3fr) minmax(0, 2fr) minmax(0, 2fr) minmax(0, 1fr) minmax(0, 2.5fr); + grid-template-rows: repeat(15, minmax(0, 1fr)); + column-gap: var(--col-gap); + + background: white; + color: black; + + .row-alt { + background: rgba(0,0,0, 0.3); + } + + label { + box-sizing: border-box; + padding: 2px 4px; + } + + input { + box-sizing: border-box; + border: none; + + &[type="text"], + &[type="number"] { + padding: 2px 4px; + border-bottom: 2px dashed blueviolet; + } + } + + .hero_name { + grid-column: span 3; + margin-left: calc(var(--col-gap) * -1); + padding-left: var(--col-gap); + } + + .header { + grid-row: span 2; + grid-column: span 1; + display: grid; + grid-template-rows: subgrid; + grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); + + .image { + grid-row: span 2; + display: flex; + justify-content: center; + align-items: center; + } + } +} diff --git a/Apps/common.css b/Apps/common.css new file mode 100644 index 0000000..bfd8397 --- /dev/null +++ b/Apps/common.css @@ -0,0 +1,12 @@ +.ripcrypt { + .window-content { + padding: 0; + margin: 0; + } + + .HeroSummaryCardV1 { + height: 270px; + width: 680px; + --col-gap: 2px; + } +} diff --git a/langs/en-ca.json b/langs/en-ca.json index 6297e90..af3b8f9 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -3,5 +3,10 @@ "Actor": { "hero": "Hero" } + }, + "RipCrypt": { + "sheet-names": { + "HeroSummaryCardV1": "Hero Stat Card" + } } } diff --git a/module/Apps/ActorSheets/HeroSummaryCardV1.mjs b/module/Apps/ActorSheets/HeroSummaryCardV1.mjs new file mode 100644 index 0000000..8bf9cfc --- /dev/null +++ b/module/Apps/ActorSheets/HeroSummaryCardV1.mjs @@ -0,0 +1,53 @@ +import { filePath } from "../../consts.mjs"; + +const { DocumentSheetV2, HandlebarsApplicationMixin } = foundry.applications.api; + +export class HeroSummaryCardV1 extends HandlebarsApplicationMixin(DocumentSheetV2) { + + // #region Options + static DEFAULT_OPTIONS = { + classes: [ + `ripcrypt`, + `ripcrypt--actor`, + `ripcrypt--HeroSummaryCardV1`, + `ripcrypt-theme--dark`, + ], + position: { + width: `auto`, + height: `auto`, + }, + window: { + resizable: false, + }, + actions: {}, + form: { + submitOnChange: true, + closeOnSubmit: false, + }, + }; + + static PARTS = { + content: { + template: filePath(`Apps/HeroSummaryCardV1/content.hbs`), + }, + }; + // #endregion + + // #region Lifecycle + async _preparePartContext(partId, ctx, opts) { + ctx = await super._preparePartContext(partId, ctx, opts); + + ctx.meta ??= {}; + ctx.meta.idp = this.document.uuid; + + partId = partId.slice(0,1).toUpperCase() + partId.slice(1); + if (this[`_prepare${partId}Context`] != null) { + ctx = await this[`_prepare${partId}Context`](ctx, opts); + }; + return ctx; + }; + // #endregion + + // #region Actions + // #endregion +}; diff --git a/module/consts.mjs b/module/consts.mjs new file mode 100644 index 0000000..dc5d000 --- /dev/null +++ b/module/consts.mjs @@ -0,0 +1,7 @@ +// MARK: filePath +export function filePath(path) { + if (path.startsWith(`/`)) { + path = path.slice(1); + }; + return `systems/ripcrypt/${path}`; +}; diff --git a/system.json b/system.json index ae53692..b5d96f2 100644 --- a/system.json +++ b/system.json @@ -17,7 +17,10 @@ "esmodules": [ "module/main.mjs" ], - "styles": [], + "styles": [ + "Apps/common.css", + "Apps/HeroSummaryCardV1/style.css" + ], "languages": [ { "lang": "en", From fe4ebcdb5ced2e5f9657669454fb82797f4a8ef6 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 21 Dec 2024 00:05:50 -0700 Subject: [PATCH 013/252] Add custom Die class for handling the ripping and crypting dice --- module/dice/CryptDie.mjs | 56 ++++++++++++++++++++++++++++++++++++++++ module/hooks/init.mjs | 7 +++++ 2 files changed, 63 insertions(+) create mode 100644 module/dice/CryptDie.mjs diff --git a/module/dice/CryptDie.mjs b/module/dice/CryptDie.mjs new file mode 100644 index 0000000..4b67ef0 --- /dev/null +++ b/module/dice/CryptDie.mjs @@ -0,0 +1,56 @@ +import { Logger } from "../utils/Logger.mjs"; + +const { Die } = foundry.dice.terms; + +export class CryptDie extends Die { + static get MODIFIERS() { + return { + ...super.MODIFIERS, + "rc": `ripOrCrypt`, + }; + }; + + ripCryptState = undefined; + async ripOrCrypt(modifier) { + + const rgx = /rc([0-9]+)/i; + const match = modifier.match(rgx); + if (!match) { return false }; + let [ target ] = match.slice(1); + + /* + Handle "Ripping" rolls, which is equivalent to re-rolling 8's and counting + it as a success. + */ + await this.explode(`x=8`, { recursive: true }); + if(this.results.some(result => result.exploded)) { + this.ripCryptState = `ripping`; + }; + + /* + Handles "Crypting" rolls, which is a single explosion that allows + */ + if (!this.ripCryptState) { + await this.explode(`xo=1`, { recursive: false }); + + let almostCrypted = false; + for (const result of this.results) { + if (result.result !== 1) { continue }; + if (almostCrypted) { + this.ripCryptState = `crypted`; + break; + } else { + almostCrypted = true; + } + } + }; + + // Count successes and deduct failures from total + await this.countSuccess(`cs>=${target}`); + await this.deductFailures(`df<${target}`); + }; + + get total() { + return Math.max(super.total, 0); + } +}; diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 03f4c17..0519436 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -5,6 +5,9 @@ import { HeroSummaryCardV1 } from "../Apps/ActorSheets/HeroSummaryCardV1.mjs"; import { HeroData } from "../data/Actor/Hero.mjs"; import { registerDevSettings } from "../settings/devSettings.mjs"; +// Class Overrides +import { CryptDie } from "../dice/CryptDie.mjs"; + // Misc import { Logger } from "../utils/Logger.mjs"; @@ -19,6 +22,10 @@ Hooks.once(`init`, () => { CONFIG.Actor.dataModels.hero = HeroData; // #endregion + // #region Class Changes + CONFIG.Dice.terms.d = CryptDie; + // #endregion + // #region Sheets // #region Actors Actors.registerSheet(game.system.id, HeroSummaryCardV1, { From 4773e44f4c53547417bd9a6411e8dff1f834997f Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 21 Dec 2024 00:06:07 -0700 Subject: [PATCH 014/252] Bump version maximum to support v13 --- system.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system.json b/system.json index b5d96f2..be816c1 100644 --- a/system.json +++ b/system.json @@ -6,7 +6,7 @@ "compatibility": { "minimum": 12, "verified": 12, - "maximum": 12 + "maximum": 13 }, "authors": [ { From e2d8ae292826a0e4d5ab43e9572e529c6c7b06cc Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 21 Dec 2024 00:06:59 -0700 Subject: [PATCH 015/252] Make it so that the defaultTab switching works in v13 --- module/hooks/ready.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/module/hooks/ready.mjs b/module/hooks/ready.mjs index e367c81..0f69cef 100644 --- a/module/hooks/ready.mjs +++ b/module/hooks/ready.mjs @@ -5,15 +5,16 @@ Hooks.once(`ready`, () => { let defaultTab = game.settings.get(`ripcrypt`, `defaultTab`); if (defaultTab) { - if (!ui.sidebar?.tabs?.[defaultTab]) { + if (!ui.sidebar?.TABS?.[defaultTab]) { Logger.error(`Couldn't find a sidebar tab with ID:`, defaultTab); } else { Logger.debug(`Switching sidebar tab to:`, defaultTab); - ui.sidebar.tabs[defaultTab].activate(); + ui.sidebar.activateTab(defaultTab); }; }; if (game.settings.get(`ripcrypt`, `devMode`)) { + ui.sidebar.expand(); if (game.paused) { game.togglePause() }; }; }); From b257ed218f77c91eb17732df059cf31097216fae Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 21 Dec 2024 00:08:42 -0700 Subject: [PATCH 016/252] Remove unused import --- module/dice/CryptDie.mjs | 2 -- 1 file changed, 2 deletions(-) diff --git a/module/dice/CryptDie.mjs b/module/dice/CryptDie.mjs index 4b67ef0..9722990 100644 --- a/module/dice/CryptDie.mjs +++ b/module/dice/CryptDie.mjs @@ -1,5 +1,3 @@ -import { Logger } from "../utils/Logger.mjs"; - const { Die } = foundry.dice.terms; export class CryptDie extends Die { From e435f9102b8f8352c6db4a28a29dc43041bb7c35 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 21 Dec 2024 23:24:00 -0700 Subject: [PATCH 017/252] RC-26 | Name input --- Apps/HeroSummaryCardV1/content.hbs | 8 +++++++- Apps/HeroSummaryCardV1/style.css | 11 ----------- Apps/common.css | 16 +++++++++++++++- module/Apps/ActorSheets/HeroSummaryCardV1.mjs | 7 +++++-- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/Apps/HeroSummaryCardV1/content.hbs b/Apps/HeroSummaryCardV1/content.hbs index 7c63780..7dd3f30 100644 --- a/Apps/HeroSummaryCardV1/content.hbs +++ b/Apps/HeroSummaryCardV1/content.hbs @@ -9,7 +9,13 @@ Player - + {{!-- * Armour --}}
diff --git a/Apps/HeroSummaryCardV1/style.css b/Apps/HeroSummaryCardV1/style.css index 163f195..7419db7 100644 --- a/Apps/HeroSummaryCardV1/style.css +++ b/Apps/HeroSummaryCardV1/style.css @@ -16,17 +16,6 @@ padding: 2px 4px; } - input { - box-sizing: border-box; - border: none; - - &[type="text"], - &[type="number"] { - padding: 2px 4px; - border-bottom: 2px dashed blueviolet; - } - } - .hero_name { grid-column: span 3; margin-left: calc(var(--col-gap) * -1); diff --git a/Apps/common.css b/Apps/common.css index bfd8397..21c9e42 100644 --- a/Apps/common.css +++ b/Apps/common.css @@ -5,8 +5,22 @@ } .HeroSummaryCardV1 { - height: 270px; + /* height: 270px; */ width: 680px; --col-gap: 2px; } + + input { + all: initial; + box-sizing: border-box; + border: none; + outline: none; + font-family: inherit; + font-size: inherit; + position: relative; + + &[type="text"] { + border-bottom: 2px dashed purple; + } + } } diff --git a/module/Apps/ActorSheets/HeroSummaryCardV1.mjs b/module/Apps/ActorSheets/HeroSummaryCardV1.mjs index 8bf9cfc..67e0446 100644 --- a/module/Apps/ActorSheets/HeroSummaryCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSummaryCardV1.mjs @@ -1,8 +1,9 @@ import { filePath } from "../../consts.mjs"; -const { DocumentSheetV2, HandlebarsApplicationMixin } = foundry.applications.api; +const { HandlebarsApplicationMixin } = foundry.applications.api; +const { ActorSheetV2 } = foundry.applications.sheets; -export class HeroSummaryCardV1 extends HandlebarsApplicationMixin(DocumentSheetV2) { +export class HeroSummaryCardV1 extends HandlebarsApplicationMixin(ActorSheetV2) { // #region Options static DEFAULT_OPTIONS = { @@ -40,6 +41,8 @@ export class HeroSummaryCardV1 extends HandlebarsApplicationMixin(DocumentSheetV ctx.meta ??= {}; ctx.meta.idp = this.document.uuid; + ctx.actor = this.document; + partId = partId.slice(0,1).toUpperCase() + partId.slice(1); if (this[`_prepare${partId}Context`] != null) { ctx = await this[`_prepare${partId}Context`](ctx, opts); From 06c5320786612d8b926a3fdf5a97ef837cce87a1 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 21 Dec 2024 23:24:20 -0700 Subject: [PATCH 018/252] Add hot-reloading for CSS as required --- module/hooks/hotReload.mjs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/module/hooks/hotReload.mjs b/module/hooks/hotReload.mjs index 67a44aa..7ef784f 100644 --- a/module/hooks/hotReload.mjs +++ b/module/hooks/hotReload.mjs @@ -7,10 +7,7 @@ const loaders = { Hooks.call(`${game.system.id}-hmr:svg`, iconName, data); }, mjs() {window.location.reload()}, - css(data) { - Logger.debug(`Hot-reloading CSS: ${data.path}`); - Hooks.call(`${game.system.id}-hmr:css`, data); - }, + css() {window.location.reload()}, }; Hooks.on(`hotReload`, async (data) => { From edd4e49f622907f841f5eb430c71f1b8e4d12ead Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 21 Dec 2024 23:46:26 -0700 Subject: [PATCH 019/252] Add Handlebars helpers --- langs/en-ca.json | 3 +++ module/handlebarHelpers/_index.mjs | 11 +++++++++ module/handlebarHelpers/options.mjs | 36 ++++++++++++++++++++++++++++ module/utils/Localizer.mjs | 37 +++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 module/handlebarHelpers/_index.mjs create mode 100644 module/handlebarHelpers/options.mjs create mode 100644 module/utils/Localizer.mjs diff --git a/langs/en-ca.json b/langs/en-ca.json index af3b8f9..751507c 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -7,6 +7,9 @@ "RipCrypt": { "sheet-names": { "HeroSummaryCardV1": "Hero Stat Card" + }, + "common": { + "empty": "---" } } } diff --git a/module/handlebarHelpers/_index.mjs b/module/handlebarHelpers/_index.mjs new file mode 100644 index 0000000..1603faf --- /dev/null +++ b/module/handlebarHelpers/_index.mjs @@ -0,0 +1,11 @@ +import { handlebarsLocalizer, localizer } from "../utils/Localizer.mjs"; +import { options } from "./options.mjs"; + +export default { + // #region Complex + "rc-i18n": handlebarsLocalizer, + "rc-options": options, + + // #region Simple + "rc-empty-state": (v) => v ?? localizer(`RipCrypt.common.empty`), +}; diff --git a/module/handlebarHelpers/options.mjs b/module/handlebarHelpers/options.mjs new file mode 100644 index 0000000..e88ba34 --- /dev/null +++ b/module/handlebarHelpers/options.mjs @@ -0,0 +1,36 @@ +import { localizer } from "../utils/Localizer.mjs"; + +/** + * @typedef {object} Option + * @property {string} [label] + * @property {string|number} value + * @property {boolean} [disabled] + */ + +/** + * @param {string | number} selected + * @param {Array`, + ); + }; +}; diff --git a/module/utils/Localizer.mjs b/module/utils/Localizer.mjs new file mode 100644 index 0000000..1a92058 --- /dev/null +++ b/module/utils/Localizer.mjs @@ -0,0 +1,37 @@ +import { localizerConfig } from "../config.mjs"; + +export function handlebarsLocalizer(key, ...args) { + let data = args[0]; + if (args.length === 1) { data = args[0].hash }; + if (key instanceof Handlebars.SafeString) { key = key.toString() }; + const localized = localizer(key, data); + return localized; +}; + +export function localizer(key, args = {}, depth = 0) { + /** @type {string} */ + let localized = game.i18n.format(key, args); + const subkeys = localized.matchAll(localizerConfig.subKeyPattern); + + // Short-cut to help prevent infinite recursion + if (depth > localizerConfig.maxDepth) { + return localized; + }; + + /* + Helps prevent recursion on the same key so that we aren't doing excess work. + */ + const localizedSubkeys = new Map(); + for (const match of subkeys) { + const subkey = match.groups.key; + if (localizedSubkeys.has(subkey)) { continue }; + localizedSubkeys.set(subkey, localizer(subkey, args, depth + 1)); + }; + + return localized.replace( + localizerConfig.subKeyPattern, + (_fullMatch, subkey) => { + return localizedSubkeys.get(subkey); + }, + ); +}; From f6ca694dccff9c341d5a22a67bc69779f40b4263 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 22 Dec 2024 00:23:29 -0700 Subject: [PATCH 020/252] Begin work on the fate path dropdown --- Apps/HeroSummaryCardV1/content.hbs | 6 +++++ Apps/HeroSummaryCardV1/style.css | 27 ++++++++++++++----- Apps/common.css | 9 +++++++ langs/en-ca.json | 8 +++++- module/Apps/ActorSheets/HeroSummaryCardV1.mjs | 17 ++++++++++++ module/data/Actor/Hero.mjs | 4 +-- module/gameTerms.mjs | 14 +++++----- module/handlebarHelpers/options.mjs | 4 +-- module/hooks/init.mjs | 3 +++ module/utils/Localizer.mjs | 11 +++++--- 10 files changed, 82 insertions(+), 21 deletions(-) diff --git a/Apps/HeroSummaryCardV1/content.hbs b/Apps/HeroSummaryCardV1/content.hbs index 7dd3f30..6e68d31 100644 --- a/Apps/HeroSummaryCardV1/content.hbs +++ b/Apps/HeroSummaryCardV1/content.hbs @@ -21,6 +21,12 @@
{{!-- * Fate & Advancement --}} +
+ + +
{{!-- * Weapons --}} diff --git a/Apps/HeroSummaryCardV1/style.css b/Apps/HeroSummaryCardV1/style.css index 7419db7..e15e8d2 100644 --- a/Apps/HeroSummaryCardV1/style.css +++ b/Apps/HeroSummaryCardV1/style.css @@ -1,4 +1,8 @@ .ripcrypt .HeroSummaryCardV1 { + + /* Foundry Variable Tweaks */ + --input-height: 1rem; + display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr) minmax(0, 2fr) minmax(0, 1fr) minmax(0, 2.5fr); grid-template-rows: repeat(15, minmax(0, 1fr)); @@ -7,6 +11,10 @@ background: white; color: black; + .col-header { + background: black; + color: white; + } .row-alt { background: rgba(0,0,0, 0.3); } @@ -16,12 +24,6 @@ padding: 2px 4px; } - .hero_name { - grid-column: span 3; - margin-left: calc(var(--col-gap) * -1); - padding-left: var(--col-gap); - } - .header { grid-row: span 2; grid-column: span 1; @@ -36,4 +38,17 @@ align-items: center; } } + + .hero_name { + grid-column: span 3; + margin-left: calc(var(--col-gap) * -1); + padding-left: var(--col-gap); + } + + .fate { + grid-column: 1 / span 1; + grid-row: 4 / span 2; + display: grid; + grid-template-rows: subgrid; + } } diff --git a/Apps/common.css b/Apps/common.css index 21c9e42..9605c7b 100644 --- a/Apps/common.css +++ b/Apps/common.css @@ -23,4 +23,13 @@ border-bottom: 2px dashed purple; } } + + select { + all: initial; + box-sizing: border-box; + border: none; + outline: none; + font-family: inherit; + font-size: inherit; + } } diff --git a/langs/en-ca.json b/langs/en-ca.json index 751507c..4c8c336 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -9,7 +9,13 @@ "HeroSummaryCardV1": "Hero Stat Card" }, "common": { - "empty": "---" + "empty": "---", + "fate": { + "North": "North", + "East": "East", + "South": "South", + "West": "West" + } } } } diff --git a/module/Apps/ActorSheets/HeroSummaryCardV1.mjs b/module/Apps/ActorSheets/HeroSummaryCardV1.mjs index 67e0446..0d93da1 100644 --- a/module/Apps/ActorSheets/HeroSummaryCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSummaryCardV1.mjs @@ -1,4 +1,6 @@ import { filePath } from "../../consts.mjs"; +import { gameTerms } from "../../gameTerms.mjs"; +import { Logger } from "../../utils/Logger.mjs"; const { HandlebarsApplicationMixin } = foundry.applications.api; const { ActorSheetV2 } = foundry.applications.sheets; @@ -43,10 +45,25 @@ export class HeroSummaryCardV1 extends HandlebarsApplicationMixin(ActorSheetV2) ctx.actor = this.document; + ctx = await this._prepareFatePath(ctx); + partId = partId.slice(0,1).toUpperCase() + partId.slice(1); if (this[`_prepare${partId}Context`] != null) { ctx = await this[`_prepare${partId}Context`](ctx, opts); }; + + Logger.debug(`Context:`, ctx); + return ctx; + }; + + async _prepareFatePath(ctx) { + ctx.fate = {}; + ctx.fate.selected = ctx.actor.system.fate; + ctx.fate.options = [ + { label: `RipCrypt.common.empty`, v: `` }, + ...gameTerms.FatePath + .map(v => ({ label: `RipCrypt.common.fate.${v}`, value: v })), + ]; return ctx; }; // #endregion diff --git a/module/data/Actor/Hero.mjs b/module/data/Actor/Hero.mjs index 12f8c64..44c2129 100644 --- a/module/data/Actor/Hero.mjs +++ b/module/data/Actor/Hero.mjs @@ -1,4 +1,4 @@ -import { FatePath } from "../../gameTerms.mjs"; +import { gameTerms } from "../../gameTerms.mjs"; const { fields } = foundry.data; @@ -61,7 +61,7 @@ export class HeroData extends foundry.abstract.TypeDataModel { trim: true, nullable: false, choices: () => { - return Object.values(FatePath).concat(``); + return gameTerms.FatePath.concat(``); }, }), level: new fields.SchemaField({ diff --git a/module/gameTerms.mjs b/module/gameTerms.mjs index 36a0200..239486a 100644 --- a/module/gameTerms.mjs +++ b/module/gameTerms.mjs @@ -1,6 +1,8 @@ -export const FatePath = { - NORTH: `North`, - EAST: `East`, - SOUTH: `South`, - WEST: `West`, -}; +export const gameTerms = Object.preventExtensions({ + FatePath: [ + `North`, + `East`, + `South`, + `West`, + ], +}); diff --git a/module/handlebarHelpers/options.mjs b/module/handlebarHelpers/options.mjs index e88ba34..13585f2 100644 --- a/module/handlebarHelpers/options.mjs +++ b/module/handlebarHelpers/options.mjs @@ -13,9 +13,8 @@ import { localizer } from "../utils/Localizer.mjs"; * @param {any} meta */ export function options(selected, opts, meta) { - const { localize = false } = meta; + const { localize = false } = meta.hash; selected = Handlebars.escapeExpression(selected); - const htmlOptions = []; for (let opt of opts) { @@ -33,4 +32,5 @@ export function options(selected, opts, meta) { `, ); }; + return new Handlebars.SafeString(htmlOptions.join(`\n`)); }; diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 0519436..aaa2c20 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -9,6 +9,7 @@ import { registerDevSettings } from "../settings/devSettings.mjs"; import { CryptDie } from "../dice/CryptDie.mjs"; // Misc +import helpers from "../handlebarHelpers/_index.mjs"; import { Logger } from "../utils/Logger.mjs"; Hooks.once(`init`, () => { @@ -35,4 +36,6 @@ Hooks.once(`init`, () => { }); // #endregion // #endregion + + Handlebars.registerHelper(helpers); }); diff --git a/module/utils/Localizer.mjs b/module/utils/Localizer.mjs index 1a92058..0525913 100644 --- a/module/utils/Localizer.mjs +++ b/module/utils/Localizer.mjs @@ -1,4 +1,7 @@ -import { localizerConfig } from "../config.mjs"; +const config = Object.preventExtensions({ + subKeyPattern: /@(?[a-zA-Z.]+)/gm, + maxDepth: 10, +}); export function handlebarsLocalizer(key, ...args) { let data = args[0]; @@ -11,10 +14,10 @@ export function handlebarsLocalizer(key, ...args) { export function localizer(key, args = {}, depth = 0) { /** @type {string} */ let localized = game.i18n.format(key, args); - const subkeys = localized.matchAll(localizerConfig.subKeyPattern); + const subkeys = localized.matchAll(config.subKeyPattern); // Short-cut to help prevent infinite recursion - if (depth > localizerConfig.maxDepth) { + if (depth > config.maxDepth) { return localized; }; @@ -29,7 +32,7 @@ export function localizer(key, args = {}, depth = 0) { }; return localized.replace( - localizerConfig.subKeyPattern, + config.subKeyPattern, (_fullMatch, subkey) => { return localizedSubkeys.get(subkey); }, From 92d137bd3d74d570dfbec74efb39f891761d0261 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 22 Dec 2024 15:30:54 -0700 Subject: [PATCH 021/252] RC-27 | Finish styling the select --- Apps/common.css | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Apps/common.css b/Apps/common.css index 9605c7b..e85c82b 100644 --- a/Apps/common.css +++ b/Apps/common.css @@ -11,7 +11,7 @@ } input { - all: initial; + all: revert; box-sizing: border-box; border: none; outline: none; @@ -25,11 +25,18 @@ } select { - all: initial; + all: revert; + appearance: auto; box-sizing: border-box; border: none; outline: none; font-family: inherit; font-size: inherit; + display: flex; + align-items: center; + } + + label, input, select { + cursor: pointer; } } From 3672241aeceb6956860f531181f76f6d3b821914 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 22 Dec 2024 16:44:47 -0700 Subject: [PATCH 022/252] RC-28 | Abilities | Display --- Apps/HeroSummaryCardV1/content.hbs | 36 ++++++++++++++++- Apps/HeroSummaryCardV1/style.css | 40 ++++++++++++++++++- module/Apps/ActorSheets/HeroSummaryCardV1.mjs | 22 +++++++++- 3 files changed, 94 insertions(+), 4 deletions(-) diff --git a/Apps/HeroSummaryCardV1/content.hbs b/Apps/HeroSummaryCardV1/content.hbs index 6e68d31..4103adb 100644 --- a/Apps/HeroSummaryCardV1/content.hbs +++ b/Apps/HeroSummaryCardV1/content.hbs @@ -30,7 +30,41 @@ {{!-- * Weapons --}} - {{!-- * Skills --}} + {{!-- * Abilities --}} +
+ {{!-- Actual Abilities --}} + {{#each abilities as | ability |}} +
+
+ {{#unless ability.readonly}} + + {{else}} + {{ability.value}} + {{/unless}} +
+ {{#unless ability.readonly}} + + {{else}} +
+ {{ ability.name }} +
+ {{/unless}} +
+ {{/each}} + + {{!-- Health --}} + + {{!-- Move & Run --}} +
{{!-- * Equipment --}} diff --git a/Apps/HeroSummaryCardV1/style.css b/Apps/HeroSummaryCardV1/style.css index e15e8d2..0d17cd0 100644 --- a/Apps/HeroSummaryCardV1/style.css +++ b/Apps/HeroSummaryCardV1/style.css @@ -19,9 +19,10 @@ background: rgba(0,0,0, 0.3); } - label { + label, .label { box-sizing: border-box; padding: 2px 4px; + text-transform: uppercase; } .header { @@ -51,4 +52,41 @@ display: grid; grid-template-rows: subgrid; } + + .abilities { + grid-column: 1 / span 4; + grid-row: 12 / span 4; + display: grid; + /* grid-template-rows: minmax(0, 3fr) minmax(0, 1fr); */ + grid-template-columns: repeat(6, minmax(0, 1fr)); + + .ability { + display: grid; + grid-template-rows: minmax(0, 3fr) minmax(0, 1fr); + justify-items: center; + align-items: center; + } + + label, .label { + width: 100%; + text-align: center; + } + } + + .compass { + --size: 45px; + width: var(--size); + height: var(--size); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + border: 2px solid black; + border-radius: 50%; + font-size: 1.5rem; + + > input { + width: 75%; + } + } } diff --git a/module/Apps/ActorSheets/HeroSummaryCardV1.mjs b/module/Apps/ActorSheets/HeroSummaryCardV1.mjs index 0d93da1..18049fc 100644 --- a/module/Apps/ActorSheets/HeroSummaryCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSummaryCardV1.mjs @@ -1,5 +1,6 @@ import { filePath } from "../../consts.mjs"; import { gameTerms } from "../../gameTerms.mjs"; +import { localizer } from "../../utils/Localizer.mjs"; import { Logger } from "../../utils/Logger.mjs"; const { HandlebarsApplicationMixin } = foundry.applications.api; @@ -45,7 +46,8 @@ export class HeroSummaryCardV1 extends HandlebarsApplicationMixin(ActorSheetV2) ctx.actor = this.document; - ctx = await this._prepareFatePath(ctx); + ctx = await this.prepareFatePath(ctx); + ctx = await this.prepareAbilityRow(ctx); partId = partId.slice(0,1).toUpperCase() + partId.slice(1); if (this[`_prepare${partId}Context`] != null) { @@ -56,7 +58,7 @@ export class HeroSummaryCardV1 extends HandlebarsApplicationMixin(ActorSheetV2) return ctx; }; - async _prepareFatePath(ctx) { + async prepareFatePath(ctx) { ctx.fate = {}; ctx.fate.selected = ctx.actor.system.fate; ctx.fate.options = [ @@ -66,6 +68,22 @@ export class HeroSummaryCardV1 extends HandlebarsApplicationMixin(ActorSheetV2) ]; return ctx; }; + + async prepareAbilityRow(ctx) { + ctx.abilities = []; + for (const key in ctx.actor.system.ability) { + ctx.abilities.push({ + id: key, + name: localizer( + `RipCrypt.common.ability.${key}`, + { value: ctx.actor.system.ability[key] }, + ), + value: ctx.actor.system.ability[key], + readonly: true, + }); + }; + return ctx; + } // #endregion // #region Actions From 07ad74822f71fea36bc7acecf4b1cc00246cf315 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 24 Dec 2024 20:49:14 -0700 Subject: [PATCH 023/252] RC-44 | Changing Actor field while Fate is empty causes validation error --- module/handlebarHelpers/options.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/handlebarHelpers/options.mjs b/module/handlebarHelpers/options.mjs index 13585f2..5c1410e 100644 --- a/module/handlebarHelpers/options.mjs +++ b/module/handlebarHelpers/options.mjs @@ -24,7 +24,7 @@ export function options(selected, opts, meta) { opt.value = Handlebars.escapeExpression(opt.value); htmlOptions.push( `