From 761961a7fbdaeba954684e8da29ecab20f47828b Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 18 Apr 2024 23:33:55 -0600 Subject: [PATCH 01/23] Begin playing around with a custom DataField for dice selection that will allow using the default AE modes on it --- module/models/Actor/Player.mjs | 3 +- module/models/fields/DiceField.mjs | 73 ++++++++++++++++++++++++++++++ system.json | 2 +- 3 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 module/models/fields/DiceField.mjs diff --git a/module/models/Actor/Player.mjs b/module/models/Actor/Player.mjs index 244c887..5c31503 100644 --- a/module/models/Actor/Player.mjs +++ b/module/models/Actor/Player.mjs @@ -1,4 +1,5 @@ import DOTDUNGEON from "../../config.mjs"; +import { DiceField } from "../fields/DiceField.mjs"; function diceChoiceField() { return new foundry.data.fields.StringField({ @@ -38,7 +39,7 @@ export class PlayerData extends foundry.abstract.TypeDataModel { integer: true, }), stats: new fields.SchemaField({ - build: diceChoiceField(), + build: new DiceField(), meta: diceChoiceField(), presence: diceChoiceField(), hands: diceChoiceField(), diff --git a/module/models/fields/DiceField.mjs b/module/models/fields/DiceField.mjs new file mode 100644 index 0000000..294e1a9 --- /dev/null +++ b/module/models/fields/DiceField.mjs @@ -0,0 +1,73 @@ +import { statDice } from "../../config.mjs"; + +/** + * A subclass of StringField that allows ActiveEffects to integrate with dice + * values and increase/decrease the value step-wise according to the dice ladder. + */ +export class DiceField extends foundry.data.fields.DataField { + static get _defaults() { + return foundry.utils.mergeObject(super._defaults, { + trim: true, + blank: true, + initial: ``, + choices: [``, ...statDice], + }); + }; + + constructor(options = {}, context = {}) { + super(options, context); + + // v- because for some reason Foundry doesn't respect the _defaults getter + this.blank = true; + console.log(this.choices) + }; + + applyChange(...args) { + console.log(`DiceField.applyChange`, args); + return super.applyChange(...args) + } + + _cast(value) { + return String(value); + } + + _castChangeDelta(delta) { + console.log(`DiceField._castChangeDelta(${delta})`) + return parseInt(delta) ?? 0; + }; + + /** @inheritdoc */ + _applyChangeAdd(value, delta, model, change) { + console.warn(`Cannot apply Add ActiveEffects to DiceFields. Not changing value.`); + return value; + }; + + _applyChangeMultiply(value, delta, model, change) { + console.warn(`Cannot apply Multiply ActiveEffects to DiceFields. Not changing value.`); + return value; + }; + + _applyChangeOverride(value, delta, model, change) { + return delta; + }; + + _applyChangeUpgrade(value, delta, model, change) { + console.log(`.dungeon | Pre: value=${value}; delta=${delta}`); + if (value === "") return value; + const dieIndex = statDice.findIndex(value); + const newIndex = Math.min(Math.max(0, dieIndex - delta), statDice.length - 1); + value = statDice[newIndex]; + console.log(`.dungeon | Post: value=${value}; delta=${delta}`); + return value; + }; + + _applyChangeDowngrade(value, delta, model, change) { + console.log(`.dungeon | Pre: value=${value}; delta=${delta}`); + if (value === "") return value; + const dieIndex = statDice.findIndex(value); + const newIndex = Math.min(Math.max(0, dieIndex + delta), statDice.length - 1); + value = statDice[newIndex]; + console.log(`.dungeon | Post: value=${value}; delta=${delta}`); + return value + }; +}; diff --git a/system.json b/system.json index 5d3387c..01f77c4 100644 --- a/system.json +++ b/system.json @@ -9,7 +9,7 @@ "compatibility": { "minimum": 11, "verified": 11, - "maximum": 11 + "maximum": 12 }, "authors": [ { From 5f1dc53a915d757637e1b76efb2619c47ccf23ed Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 18 Apr 2024 23:34:05 -0600 Subject: [PATCH 02/23] Disable AE legacy transferral --- module/dotdungeon.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/module/dotdungeon.mjs b/module/dotdungeon.mjs index 09f4d39..daa0530 100644 --- a/module/dotdungeon.mjs +++ b/module/dotdungeon.mjs @@ -40,6 +40,7 @@ import DOTDUNGEON from "./config.mjs"; Hooks.once(`init`, async () => { console.debug(`.dungeon | Initializing`); + CONFIG.ActiveEffect.legacyTransferral = false; loadSettings(); From ee77f109579052afc119969523993fe13a6f6dc4 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 19 Apr 2024 18:51:56 -0600 Subject: [PATCH 03/23] Remove methods that don't need to be overridden for StringField --- module/models/fields/DiceField.mjs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/module/models/fields/DiceField.mjs b/module/models/fields/DiceField.mjs index 294e1a9..7534101 100644 --- a/module/models/fields/DiceField.mjs +++ b/module/models/fields/DiceField.mjs @@ -4,7 +4,7 @@ import { statDice } from "../../config.mjs"; * A subclass of StringField that allows ActiveEffects to integrate with dice * values and increase/decrease the value step-wise according to the dice ladder. */ -export class DiceField extends foundry.data.fields.DataField { +export class DiceField extends foundry.data.fields.StringField { static get _defaults() { return foundry.utils.mergeObject(super._defaults, { trim: true, @@ -22,15 +22,6 @@ export class DiceField extends foundry.data.fields.DataField { console.log(this.choices) }; - applyChange(...args) { - console.log(`DiceField.applyChange`, args); - return super.applyChange(...args) - } - - _cast(value) { - return String(value); - } - _castChangeDelta(delta) { console.log(`DiceField._castChangeDelta(${delta})`) return parseInt(delta) ?? 0; @@ -70,4 +61,9 @@ export class DiceField extends foundry.data.fields.DataField { console.log(`.dungeon | Post: value=${value}; delta=${delta}`); return value }; + + _applyChangeCustom(...args) { + console.log(`.dungeon | Dicefield._applyChangeCustom`) + return super._applyChangeCustom(...args); + } }; From 901eea4ff326c0d18fbcafd1366ea701f90dcb25 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 19 Apr 2024 18:52:11 -0600 Subject: [PATCH 04/23] Remove reference to the DiceField data field (for now) --- module/models/Actor/Player.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/models/Actor/Player.mjs b/module/models/Actor/Player.mjs index 5c31503..84af0a6 100644 --- a/module/models/Actor/Player.mjs +++ b/module/models/Actor/Player.mjs @@ -39,7 +39,7 @@ export class PlayerData extends foundry.abstract.TypeDataModel { integer: true, }), stats: new fields.SchemaField({ - build: new DiceField(), + build: diceChoiceField(), //new DiceField(), meta: diceChoiceField(), presence: diceChoiceField(), hands: diceChoiceField(), From 0064e10635491a825feae286e4b62df92c3b8b3d Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 27 Apr 2024 02:05:59 -0600 Subject: [PATCH 05/23] Make the ActiveEffects able to pull values from properties on the target object for it's value --- .../documents/ActiveEffect/GenericActiveEffect.mjs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/module/documents/ActiveEffect/GenericActiveEffect.mjs b/module/documents/ActiveEffect/GenericActiveEffect.mjs index 8ee70f3..7b3a036 100644 --- a/module/documents/ActiveEffect/GenericActiveEffect.mjs +++ b/module/documents/ActiveEffect/GenericActiveEffect.mjs @@ -4,4 +4,18 @@ export class DotDungeonActiveEffect extends ActiveEffect { // embedded controls get enabled() { return !this.disabled }; set enabled(newValue) { this.disabled = !newValue }; + + apply(object, change) { + change.value = change.value.replace( + /@(?[\w\.]+)/gi, + (...args) => { + const key = args[1]; + if (foundry.utils.hasProperty(object, key)) { + return foundry.utils.getProperty(object, key) + }; + return args[0]; + } + ) + return super.apply(object, change); + } }; From 327f921b9cd0e8a1d4155d224eddf276e4b56a9c Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 27 Apr 2024 02:07:22 -0600 Subject: [PATCH 06/23] Implement the DiceField and required changes to get it working (closes #179) --- module/documents/Actor/GenericActor.mjs | 2 +- module/models/Actor/Player.mjs | 12 +-- module/models/fields/DiceField.mjs | 83 ++++++++++++------- module/sheets/Actors/PC/PlayerSheetV2.mjs | 2 +- .../char-sheet/v2/partials/stats.v2.pc.hbs | 3 + 5 files changed, 62 insertions(+), 40 deletions(-) diff --git a/module/documents/Actor/GenericActor.mjs b/module/documents/Actor/GenericActor.mjs index 171af63..feb3825 100644 --- a/module/documents/Actor/GenericActor.mjs +++ b/module/documents/Actor/GenericActor.mjs @@ -6,7 +6,7 @@ export class DotDungeonActor extends Actor { provide all that data to AE's without needing to disable any inputs. */ prepareEmbeddedDocuments() { - this.preAE = foundry.utils.deepClone(this.system); + this.preAE = foundry.utils.duplicate(this.system); super.prepareEmbeddedDocuments(); }; diff --git a/module/models/Actor/Player.mjs b/module/models/Actor/Player.mjs index 84af0a6..f7b5481 100644 --- a/module/models/Actor/Player.mjs +++ b/module/models/Actor/Player.mjs @@ -39,12 +39,12 @@ export class PlayerData extends foundry.abstract.TypeDataModel { integer: true, }), stats: new fields.SchemaField({ - build: diceChoiceField(), //new DiceField(), - meta: diceChoiceField(), - presence: diceChoiceField(), - hands: diceChoiceField(), - tilt: diceChoiceField(), - rng: diceChoiceField(), + build: new DiceField(), + meta: new DiceField(), + presence: new DiceField(), + hands: new DiceField(), + tilt: new DiceField(), + rng: new DiceField(), }), skills: new fields.SchemaField({ build: new fields.SchemaField({ diff --git a/module/models/fields/DiceField.mjs b/module/models/fields/DiceField.mjs index 7534101..e147fb2 100644 --- a/module/models/fields/DiceField.mjs +++ b/module/models/fields/DiceField.mjs @@ -1,10 +1,10 @@ import { statDice } from "../../config.mjs"; /** - * A subclass of StringField that allows ActiveEffects to integrate with dice + * A subclass of DataField that allows ActiveEffects to integrate with dice * values and increase/decrease the value step-wise according to the dice ladder. */ -export class DiceField extends foundry.data.fields.StringField { +export class DiceField extends foundry.data.fields.DataField { static get _defaults() { return foundry.utils.mergeObject(super._defaults, { trim: true, @@ -19,51 +19,70 @@ export class DiceField extends foundry.data.fields.StringField { // v- because for some reason Foundry doesn't respect the _defaults getter this.blank = true; - console.log(this.choices) }; - _castChangeDelta(delta) { - console.log(`DiceField._castChangeDelta(${delta})`) - return parseInt(delta) ?? 0; - }; + _cast(value) { return value }; + _castChangeDelta(delta) { return delta }; - /** @inheritdoc */ - _applyChangeAdd(value, delta, model, change) { - console.warn(`Cannot apply Add ActiveEffects to DiceFields. Not changing value.`); + /** + * @param {string} value The current value + * @param {string} delta The AE value + * @param {unknown} model + * @param {unknown} changes + */ + _applyChangeAdd(value, delta, model, changes) { + if (value === "") return value; + delta = parseInt(delta); + const dieIndex = statDice.findIndex(die => die === value); + const newIndex = Math.min(Math.max(0, dieIndex + delta), statDice.length - 1); + value = statDice[newIndex]; return value; }; - _applyChangeMultiply(value, delta, model, change) { - console.warn(`Cannot apply Multiply ActiveEffects to DiceFields. Not changing value.`); + /** + * @param {string} value The current value + * @param {string} delta The AE value + * @param {unknown} model + * @param {unknown} changes + */ + _applyChangeMultiply(value, delta, model, changes) { + console.warn(`.dungeon | Cannot apply Multiply ActiveEffects to DiceFields. Not changing value.`); return value; }; - _applyChangeOverride(value, delta, model, change) { + /** + * @param {string} value The current value + * @param {string} delta The AE value + * @param {unknown} model + * @param {unknown} changes + */ + _applyChangeOverride(value, delta, model, changes) { return delta; }; - _applyChangeUpgrade(value, delta, model, change) { - console.log(`.dungeon | Pre: value=${value}; delta=${delta}`); + /** + * @param {string} value The current value + * @param {string} delta The AE value + * @param {unknown} model + * @param {unknown} changes + */ + _applyChangeUpgrade(value, delta, model, changes) { if (value === "") return value; - const dieIndex = statDice.findIndex(value); - const newIndex = Math.min(Math.max(0, dieIndex - delta), statDice.length - 1); - value = statDice[newIndex]; - console.log(`.dungeon | Post: value=${value}; delta=${delta}`); - return value; + const currentIndex = statDice.findIndex(die => die === value); + const upgradedIndex = statDice.findIndex(die => die === delta); + return statDice[Math.max(currentIndex, upgradedIndex)]; }; - _applyChangeDowngrade(value, delta, model, change) { - console.log(`.dungeon | Pre: value=${value}; delta=${delta}`); + /** + * @param {string} value The current value + * @param {string} delta The AE value + * @param {unknown} model + * @param {unknown} changes + */ + _applyChangeDowngrade(value, delta, model, changes) { if (value === "") return value; - const dieIndex = statDice.findIndex(value); - const newIndex = Math.min(Math.max(0, dieIndex + delta), statDice.length - 1); - value = statDice[newIndex]; - console.log(`.dungeon | Post: value=${value}; delta=${delta}`); - return value + const currentIndex = statDice.findIndex(die => die === value); + const upgradedIndex = statDice.findIndex(die => die === delta); + return statDice[Math.min(currentIndex, upgradedIndex)]; }; - - _applyChangeCustom(...args) { - console.log(`.dungeon | Dicefield._applyChangeCustom`) - return super._applyChangeCustom(...args); - } }; diff --git a/module/sheets/Actors/PC/PlayerSheetV2.mjs b/module/sheets/Actors/PC/PlayerSheetV2.mjs index ac1a378..466e6c5 100644 --- a/module/sheets/Actors/PC/PlayerSheetV2.mjs +++ b/module/sheets/Actors/PC/PlayerSheetV2.mjs @@ -92,7 +92,7 @@ export class PlayerSheetv2 extends GenericActorSheet { get #statData() { const stats = []; - const usedDice = new Set(Object.values(this.actor.system.stats)); + const usedDice = new Set(Object.values(this.actor.preAE.stats)); for (const statName in this.actor.system.stats) { const stat = { key: statName, diff --git a/templates/actors/char-sheet/v2/partials/stats.v2.pc.hbs b/templates/actors/char-sheet/v2/partials/stats.v2.pc.hbs index 6353120..59e61ca 100644 --- a/templates/actors/char-sheet/v2/partials/stats.v2.pc.hbs +++ b/templates/actors/char-sheet/v2/partials/stats.v2.pc.hbs @@ -17,6 +17,9 @@ data-roll-label="Stat Roll : {{stat.name}}" > Roll + {{#if stat.value}} + 1{{stat.value}} + {{/if}} {{#if stat.skills}} From d84c5e2a20a6b37994a328bc372973f974da3287 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 28 Apr 2024 00:20:47 -0600 Subject: [PATCH 07/23] Add the setting translations to the new localization file --- langs/en-ca.2.json | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/langs/en-ca.2.json b/langs/en-ca.2.json index dce0efa..f8a1ab5 100644 --- a/langs/en-ca.2.json +++ b/langs/en-ca.2.json @@ -101,6 +101,32 @@ "title": "Delete Effect", "content": "

Are you sure you would like to delete the active effect: {name}

" } + }, + "settings": { + "showAvatarOnSheet": { + "name": "Show Avatar On Player Sheet", + "description": "Determines whether or not to show the avatar to you on the Player Character sheets, turning this off will replace the image with a file picker so that you can still change the image from the character sheet." + }, + "playersCanChangeGroup": { + "name": "Allow Players to Change Group", + "description": "Setting this to true allows non-GM players to modify the group that the Actor belongs to. While this is disabled the GM will still be able to modify each player's group by editing the character sheet." + }, + "resourcesOrSupplies": { + "name": "Use Resources or Supplies", + "description": "Determines which term to use for the objects that allow travelling into the next hex tile. This is because of the", + "option": { + "supplies": "Supplies", + "resources": "Resources" + } + }, + "openEmbeddedOnCreate": { + "name": "Edit Custom Items Immediately When Created", + "description": "Tells the character sheets that have \"Add\" buttons to open the Item's sheet when you create the new item so that you can immediately edit it without needing to click more buttons." + }, + "aspectLimit": { + "name": "Character Aspect Limit", + "description": "Limit how many Aspects a single character can have." + } } }, "TYPES": { From 14ab0aa5dc4f273281eee2173db8181eb149c849 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 28 Apr 2024 00:23:57 -0600 Subject: [PATCH 08/23] Update the verified version to v12 --- system.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system.json b/system.json index 01f77c4..2dda060 100644 --- a/system.json +++ b/system.json @@ -8,7 +8,7 @@ "url": "https://github.com/Oliver-Akins/foundry.dungeon", "compatibility": { "minimum": 11, - "verified": 11, + "verified": 12, "maximum": 12 }, "authors": [ From 11d2a2a10fdb30a415a2689a9d710de08ba8cfb9 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 28 Apr 2024 14:05:23 -0600 Subject: [PATCH 09/23] Make minimum Foundry version 12 --- system.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system.json b/system.json index 2dda060..6bbb1d8 100644 --- a/system.json +++ b/system.json @@ -7,7 +7,7 @@ "manifest": "https://github.com/Oliver-Akins/foundry.dungeon/releases/latest/download/system.json", "url": "https://github.com/Oliver-Akins/foundry.dungeon", "compatibility": { - "minimum": 11, + "minimum": 12, "verified": 12, "maximum": 12 }, From 194068c22c2841c4b53d6481e4fccfc45ca3544b Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 28 Apr 2024 17:19:31 -0600 Subject: [PATCH 10/23] Update the materialize mixins to accept a base colour (allows preventing transparency for things like nav-bars; closes #160) --- styles/v3/layouts/items/untyped/v2.scss | 2 +- styles/v3/mixins/_material.scss | 27 +++++++++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/styles/v3/layouts/items/untyped/v2.scss b/styles/v3/layouts/items/untyped/v2.scss index d94bf49..5a5a767 100644 --- a/styles/v3/layouts/items/untyped/v2.scss +++ b/styles/v3/layouts/items/untyped/v2.scss @@ -5,7 +5,7 @@ --gap: 8px; .nav-bar { - @include material.elevate(8); + @include material.elevate(8, $base: var(--surface)); position: absolute; bottom: 0; left: 0; diff --git a/styles/v3/mixins/_material.scss b/styles/v3/mixins/_material.scss index a869640..cdfd203 100644 --- a/styles/v3/mixins/_material.scss +++ b/styles/v3/mixins/_material.scss @@ -1,12 +1,31 @@ -@mixin elevate($height) { - background-color: var(--elevation-#{$height}dp-bg); +@use "sass:map"; + +$elevations: ( + 0: 0%, + 1: 5%, + 2: 7%, + 3: 8%, + 4: 9%, + 6: 11%, + 8: 12%, + 12: 14%, + 16: 15%, + 24: 16%, +); + +@mixin elevate($height, $base: transparent) { + background-color: color-mix( + in lab, + #{$base}, + white map.get($elevations, $height) + ); -webkit-box-shadow: 0px 0px #{$height * 1.75}px 0px rgba(0,0,0,0.75); -moz-box-shadow: 0px 0px #{$height * 1.75}px 0px rgba(0,0,0,0.75); box-shadow: 0px 0px #{$height * 1.75}px 0px rgba(0,0,0,0.75); } -@mixin undo { - background-color: transparent; +@mixin undo($base: transparent) { + background-color: #{$base}; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; From 90a9badddc4224678a326fabcb2bbe46fe6acf21 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 28 Apr 2024 17:39:14 -0600 Subject: [PATCH 11/23] Remove file from git --- new-pc-sheet.md | 80 ------------------------------------------------- 1 file changed, 80 deletions(-) delete mode 100644 new-pc-sheet.md diff --git a/new-pc-sheet.md b/new-pc-sheet.md deleted file mode 100644 index f43ce4c..0000000 --- a/new-pc-sheet.md +++ /dev/null @@ -1,80 +0,0 @@ -## Tabs: -- Main - - Stats - - Skills -- Inventory - - Player - - Containers - - Inventory (divided into category of items) - - This is the items that the player will have "on them" - - Storage (divided into category of items) - - This is all of the items that the players owns and has put into storage *somewhere* - - Transportation -- Combat - - Easy skill buttons: - - Melee - - Accuracy - - Weapons - - Sync / Respawns -- Info - - Account name - - PFP - - Group name - - Aspects - - Roles -- Spells - -====== - -## Requirements: - -Stats: - - Needs to list all 6 of the primary stats - - Needs to have a dropdown for to select a die - - Nice to have: disables dice that have been selected in other stats - - Needs to have a button to roll the stat (when a die is selected) - - Foundry v12: ActiveEffect - Die needs to be able to be affected by ActiveEffects - -Skills: - - Each of the 25 skills needs to be grouped under a header of what stat it's - associated with - - Each skill must have a dropdown to indicate training level (null, trained, - expert, locked) - - Every skill must have a button to roll the dice that is labelled with the - correct formula for that skill (or "Locked" if the skill is locked) - - ActiveEffect - Increase Modifier - - Foundry v12: ActiveEffect - Increase Training Level - -Combat: - - Two weapon slots for the equipped weapon(s) - - A single armor slot - - Quick-access to the Melee / Accuracy skills - -Inventory: - - Needs three sub-tabs: - - Player - - Storage - - Transportation - - Player Subtab: - - Needs to have a section for container items, and indicating how many slots - each one has. - - List all of the items that the player has with the "inventory" location - - Show the total number of items the player on their character and how many - total slots are available - - Needs some way to move items to a different storage area (embedded-only - item sheet field maybe) - - Storage Subtab: - - List all of the items that the player has marked as in-storage - - Transportation: - - This is currently just a placeholder tab, no functionality needed other - than existing - -Spells: - - Lists all spells on a page (sortable by: alphabetical, cost, etc.) - -Info: - - Needs a place to edit the actor's name - - Needs a place to edit the actor's image - - Needs a place to edit the group name (if enabled by the GM, or is the GM) - - Needs a place to see and manage all equipped aspects - - Needs a place to see and manage all equipped roles \ No newline at end of file From e4ac6f4ca1fa17054d00f349e73b3facc0e355a7 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 28 Apr 2024 19:11:55 -0600 Subject: [PATCH 12/23] Add markers for VSC minimap --- module/helpers/index.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/module/helpers/index.mjs b/module/helpers/index.mjs index b48baa3..5c24614 100644 --- a/module/helpers/index.mjs +++ b/module/helpers/index.mjs @@ -7,7 +7,7 @@ import { options } from "./options.mjs"; export default { - // Complex helpers + // MARK: Complex helpers "dd-schemaOptions": schemaOptions, "dd-array": createArray, "dd-objectValue": objectValue, @@ -15,13 +15,13 @@ export default { "dd-i18n": handlebarsLocalizer, "dd-options": options, - // Simple helpers + // MARK: Simple helpers "dd-stringify": v => JSON.stringify(v, null, ` `), "dd-empty": v => v.length == 0, "dd-set-has": (s, k) => s.has(k), "dd-empty-state": (v) => v ?? localizer(`dotdungeon.common.empty`), - // Logic helpers + // MARK: Logic helpers "eq": (a, b) => a == b, "neq": (a, b) => a != b, "not": v => !v, From 851992161fd10d3ce08597570ec42df1fb207786 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 30 Apr 2024 19:55:16 -0600 Subject: [PATCH 13/23] Make the Material datamodel be the DescribedItemData --- module/dotdungeon.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/module/dotdungeon.mjs b/module/dotdungeon.mjs index 8731314..3dd1e64 100644 --- a/module/dotdungeon.mjs +++ b/module/dotdungeon.mjs @@ -1,6 +1,5 @@ // Data Models import { DescribedItemData } from "./models/Item/DescribedItemData.mjs"; -import { CommonItemData } from "./models/Item/CommonItemData.mjs"; import { WeaponItemData } from "./models/Item/Weapon.mjs"; import { AspectItemData } from "./models/Item/Aspect.mjs"; import { SpellItemData } from "./models/Item/Spell.mjs"; @@ -39,6 +38,7 @@ import { devInit } from "./hooks/devInit.mjs"; import DOTDUNGEON from "./config.mjs"; +// MARK: init hook Hooks.once(`init`, async () => { console.debug(`.dungeon | Initializing`); CONFIG.ActiveEffect.legacyTransferral = false; @@ -49,7 +49,7 @@ Hooks.once(`init`, async () => { CONFIG.Actor.dataModels.sync = SyncData; CONFIG.Actor.dataModels.mob = MobData; CONFIG.Item.dataModels.untyped = DescribedItemData; - CONFIG.Item.dataModels.material = CommonItemData; + CONFIG.Item.dataModels.material = DescribedItemData; CONFIG.Item.dataModels.foil = DescribedItemData; CONFIG.Item.dataModels.weapon = WeaponItemData; CONFIG.Item.dataModels.aspect = AspectItemData; @@ -116,6 +116,7 @@ Hooks.once(`init`, async () => { }); +// MARK: ready hook Hooks.once(`ready`, () => { console.debug(".dungeon | Ready"); From d92d6b9d3c9f1830076d5b492d77c24ec54c6c48 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 30 Apr 2024 19:56:02 -0600 Subject: [PATCH 14/23] Remove unneeded diceChoice function now that DiceField is a class I can use --- module/models/Actor/Player.mjs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/module/models/Actor/Player.mjs b/module/models/Actor/Player.mjs index f7b5481..e7ba912 100644 --- a/module/models/Actor/Player.mjs +++ b/module/models/Actor/Player.mjs @@ -1,17 +1,6 @@ import DOTDUNGEON from "../../config.mjs"; import { DiceField } from "../fields/DiceField.mjs"; -function diceChoiceField() { - return new foundry.data.fields.StringField({ - initial: ``, - blank: true, - trim: true, - options() { - return DOTDUNGEON.statDice; - }, - }); -}; - function trainingLevelField() { return new foundry.data.fields.NumberField({ initial: 0, From d433e6a51dfb4c180018071ead6671972a516981 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 30 Apr 2024 20:00:05 -0600 Subject: [PATCH 15/23] Prevent specific data fields from being modifiable via AEs --- module/config.mjs | 6 ++++++ module/documents/ActiveEffect/GenericActiveEffect.mjs | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/module/config.mjs b/module/config.mjs index 56a0fb4..d1fa118 100644 --- a/module/config.mjs +++ b/module/config.mjs @@ -68,6 +68,11 @@ export const itemFilters = [ `service`, ]; +export const invalidActiveEffectTargets = new Set([ + `system.uses_inventory_slot`, + `system.quantity_affects_used_capacity`, +]); + export default { stats, statDice, @@ -86,4 +91,5 @@ export default { syncDice, localizerConfig, itemFilters, + invalidActiveEffectTargets, }; diff --git a/module/documents/ActiveEffect/GenericActiveEffect.mjs b/module/documents/ActiveEffect/GenericActiveEffect.mjs index 7b3a036..47f10d3 100644 --- a/module/documents/ActiveEffect/GenericActiveEffect.mjs +++ b/module/documents/ActiveEffect/GenericActiveEffect.mjs @@ -1,3 +1,5 @@ +import { invalidActiveEffectTargets } from "../../config.mjs"; + export class DotDungeonActiveEffect extends ActiveEffect { // Invert the logic of the disabled property so it's easier to modify via @@ -6,6 +8,8 @@ export class DotDungeonActiveEffect extends ActiveEffect { set enabled(newValue) { this.disabled = !newValue }; apply(object, change) { + if (invalidActiveEffectTargets.has(change.key)) return; + change.value = change.value.replace( /@(?[\w\.]+)/gi, (...args) => { From 5b5e74e08568d9b9c86f2fabf71587f6840de73a Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 30 Apr 2024 21:23:15 -0600 Subject: [PATCH 16/23] Make it so that the custom input delegates focus --- module/components/incrementer.mjs | 6 +++++- module/components/mixins/Styles.mjs | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/module/components/incrementer.mjs b/module/components/incrementer.mjs index 68e426a..09e81ab 100644 --- a/module/components/incrementer.mjs +++ b/module/components/incrementer.mjs @@ -14,7 +14,11 @@ Styling: - `--height`: Controls the height of the element + the width of the buttons (default: 1.25rem) - `--width`: Controls the width of the number input (default 50px) */ -export class DotDungeonIncrementer extends StyledShadowElement(HTMLElement) { +export class DotDungeonIncrementer +extends StyledShadowElement( + HTMLElement, + { mode: `open`, delegatesFocus: true } +) { static elementName = `dd-incrementer`; static formAssociated = true; diff --git a/module/components/mixins/Styles.mjs b/module/components/mixins/Styles.mjs index 33d5eb5..bbceaad 100644 --- a/module/components/mixins/Styles.mjs +++ b/module/components/mixins/Styles.mjs @@ -1,7 +1,7 @@ /** * @param {HTMLElement} Base */ -export function StyledShadowElement(Base) { +export function StyledShadowElement(Base, shadowOptions = { mode: `open` }) { return class extends Base { /** * The path to the CSS that is loaded @@ -33,7 +33,7 @@ export function StyledShadowElement(Base) { constructor() { super(); - this._shadow = this.attachShadow({ mode: `open` }); + this._shadow = this.attachShadow(shadowOptions); this._style = document.createElement(`style`); this._shadow.appendChild(this._style); }; From 7b3ea591850624c0127f3223f844951bac310dc2 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 30 Apr 2024 21:30:27 -0600 Subject: [PATCH 17/23] Genericise some of the item sheet styling so that I'm not duplicating styles --- styles/v3/index.scss | 1 + styles/v3/layouts/items/common.scss | 71 +++++++++++++++++++++++++ styles/v3/layouts/items/untyped/v2.scss | 69 +----------------------- templates/items/untyped/v2/index.hbs | 2 +- 4 files changed, 75 insertions(+), 68 deletions(-) create mode 100644 styles/v3/layouts/items/common.scss diff --git a/styles/v3/index.scss b/styles/v3/index.scss index 85ec4bf..4c321c5 100644 --- a/styles/v3/index.scss +++ b/styles/v3/index.scss @@ -19,6 +19,7 @@ /* Sheet Layouts */ @use "./layouts/datasheet.scss"; +@use "./layouts/items/common.scss"; @use "./layouts/items/untyped/v2.scss"; /* Sheet Options */ diff --git a/styles/v3/layouts/items/common.scss b/styles/v3/layouts/items/common.scss new file mode 100644 index 0000000..93c25dd --- /dev/null +++ b/styles/v3/layouts/items/common.scss @@ -0,0 +1,71 @@ +@use "../../mixins/material"; +@use "../../mixins/utils"; + +.dotdungeon.style-v3 .item { + --gap: 8px; + + .nav-bar { + @include material.elevate(8, $base: var(--surface)); + position: absolute; + bottom: 0; + left: 0; + right: 6px; + + nav { + display: flex; + flex-direction: row; + gap: var(--gap); + padding: var(--gap); + } + } + + .page-content { + padding: calc(var(--gap) * 1); + padding-bottom: 60px; + height: 100%; + + > .tab { + height: 100%; + gap: var(--gap); + } + } + + @include utils.tab("general") { + display: grid; + --height: 50px; + grid-template-columns: var(--height) 1fr; + grid-template-rows: var(--height) 1fr; + + .description { + grid-column: 1 / -1; + } + } + + %flex-col { + display: flex; + flex-direction: column; + gap: 8px; + } + + @include utils.tab("effects") { + @extend %flex-col; + } + + @include utils.tab("settings") { + @extend %flex-col; + + .capacity-usage, .quantity-capacity, .combat-relevant { + display: flex; + align-items: center; + } + + .capacity { + &--calculated { + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + } + } + } +} diff --git a/styles/v3/layouts/items/untyped/v2.scss b/styles/v3/layouts/items/untyped/v2.scss index 5a5a767..1b6097f 100644 --- a/styles/v3/layouts/items/untyped/v2.scss +++ b/styles/v3/layouts/items/untyped/v2.scss @@ -2,53 +2,10 @@ @use "../../../mixins/utils"; .dotdungeon.style-v3 .item--untyped { - --gap: 8px; - - .nav-bar { - @include material.elevate(8, $base: var(--surface)); - position: absolute; - bottom: 0; - left: 0; - right: 6px; - - nav { - display: flex; - flex-direction: row; - gap: var(--gap); - padding: var(--gap); - } - } - - .page-content { - padding: calc(var(--gap) * 1); - padding-bottom: 60px; - height: 100%; - - > .tab { - height: 100%; - gap: var(--gap); - } - } - - @include utils.tab("general") { - display: grid; - --height: 50px; - grid-template-columns: var(--height) 1fr; - grid-template-rows: var(--height) 1fr; - - .description { - grid-column: 1 / -1; - } - } - - %flex-col { + @include utils.tab("details") { display: flex; flex-direction: column; - gap: 8px; - } - - @include utils.tab("details") { - @extend %flex-col; + gap: var(--gap); .number { display: grid; @@ -56,26 +13,4 @@ align-items: center; } } - - @include utils.tab("effects") { - @extend %flex-col; - } - - @include utils.tab("settings") { - @extend %flex-col; - - .capacity-usage, .quantity-capacity, .combat-relevant { - display: flex; - align-items: center; - } - - .capacity { - &--calculated { - display: flex; - flex-direction: row; - align-items: center; - gap: 8px; - } - } - } } diff --git a/templates/items/untyped/v2/index.hbs b/templates/items/untyped/v2/index.hbs index c8509ac..bad820a 100644 --- a/templates/items/untyped/v2/index.hbs +++ b/templates/items/untyped/v2/index.hbs @@ -1,4 +1,4 @@ -
+
{{> dotdungeon.untyped.v2.general }} {{> dotdungeon.untyped.v2.details }} From d1cb412e5aa95d10f1c306279d68ade4e882a98b Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 30 Apr 2024 21:31:11 -0600 Subject: [PATCH 18/23] Add event handling for the custom input elements --- module/sheets/Items/GenericItemSheet.mjs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/module/sheets/Items/GenericItemSheet.mjs b/module/sheets/Items/GenericItemSheet.mjs index 936bd6c..21d47a3 100644 --- a/module/sheets/Items/GenericItemSheet.mjs +++ b/module/sheets/Items/GenericItemSheet.mjs @@ -43,6 +43,15 @@ export class GenericItemSheet extends ItemSheet { if (!this.isEditable) return; console.debug(`.dungeon | Adding event listeners for Generic Item: ${this.id}`); + + /* + Custom element event listeners because Foundry doesn't listen to them by + default. + */ + html.find( + CONFIG.CACHE.componentListeners.map(n => `${n}[name]`).join(`,`) + ).on(`change`, () => this._onChangeInput.bind(this)); + html.find(`button[data-increment]`) .on(`click`, this._incrementValue.bind(this)); html.find(`button[data-decrement]`) From 91c95da63903918af67c3aaa1914644293bafebb Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 30 Apr 2024 21:31:49 -0600 Subject: [PATCH 19/23] Begin playing around with the weapon sheet --- module/dotdungeon.mjs | 6 + module/handlebars.mjs | 6 +- module/sheets/Items/WeaponSheet.mjs | 116 ++++++++++++++++++ styles/v3/elements/panel.scss | 14 ++- styles/v3/index.scss | 1 + styles/v3/layouts/items/weapon/v1.scss | 13 ++ templates/items/weapon/v1/index.hbs | 21 ++++ .../weapon/v1/tabs/details.v1.weapon.hbs | 83 +++++++++++++ 8 files changed, 256 insertions(+), 4 deletions(-) create mode 100644 module/sheets/Items/WeaponSheet.mjs create mode 100644 styles/v3/layouts/items/weapon/v1.scss create mode 100644 templates/items/weapon/v1/index.hbs create mode 100644 templates/items/weapon/v1/tabs/details.v1.weapon.hbs diff --git a/module/dotdungeon.mjs b/module/dotdungeon.mjs index 3dd1e64..16eb6dd 100644 --- a/module/dotdungeon.mjs +++ b/module/dotdungeon.mjs @@ -15,6 +15,7 @@ import { ItemProxy } from "./documents/Item/_proxy.mjs"; // Item Sheets import { UntypedItemSheet } from "./sheets/Items/UntypedItemSheet.mjs"; +import { WeaponSheet } from "./sheets/Items/WeaponSheet.mjs"; import { AspectSheet } from "./sheets/Items/AspectSheet.mjs"; import { SpellSheet } from "./sheets/Items/SpellSheet.mjs"; import { PetSheet } from "./sheets/Items/PetSheet.mjs"; @@ -95,6 +96,11 @@ Hooks.once(`init`, async () => { types: ["aspect"], label: "dotdungeon.sheet-names.AspectSheet" }); + Items.registerSheet("dotdungeon", WeaponSheet, { + makeDefault: true, + types: ["weapon"], + label: "dotdungeon.sheet-names.WeaponSheet" + }); Items.registerSheet("dotdungeon", SpellSheet, { makeDefault: true, types: ["spell"], diff --git a/module/handlebars.mjs b/module/handlebars.mjs index cdaa1b7..76e3f44 100644 --- a/module/handlebars.mjs +++ b/module/handlebars.mjs @@ -32,11 +32,15 @@ export const partials = [ `actors/char-sheet/v2/partials/inventory/items/weapon.v2.pc.hbs`, `actors/char-sheet/v2/partials/inventory/items/pet.v2.pc.hbs`, - // The v2 Untyped sheet partials + // The partials used for Untyped v2 and other item sheets that don't have a + // unique design for the other tabs `items/untyped/v2/tabs/general.v2.untyped.hbs`, `items/untyped/v2/tabs/details.v2.untyped.hbs`, `items/untyped/v2/tabs/effects.v2.untyped.hbs`, `items/untyped/v2/tabs/settings.v2.untyped.hbs`, + + // The weapon sheet partials + `items/weapon/v1/tabs/details.v1.weapon.hbs`, ]; export const preAliasedPartials = { diff --git a/module/sheets/Items/WeaponSheet.mjs b/module/sheets/Items/WeaponSheet.mjs new file mode 100644 index 0000000..2db0f23 --- /dev/null +++ b/module/sheets/Items/WeaponSheet.mjs @@ -0,0 +1,116 @@ +import { GenericContextMenu } from "../../utils/GenericContextMenu.mjs"; +import { DialogManager } from "../../utils/DialogManager.mjs"; +import { GenericItemSheet } from "./GenericItemSheet.mjs"; +import { localizer } from "../../utils/localizer.mjs"; + +export class WeaponSheet extends GenericItemSheet { + static get defaultOptions() { + let opts = foundry.utils.mergeObject( + super.defaultOptions, + { + template: `systems/dotdungeon/templates/items/weapon/v1/index.hbs`, + width: 300, + height: 340, + tabs: [ + { + group: `page`, + navSelector: `nav.page`, + contentSelector: `.page-content`, + initial: `general`, + }, + ], + } + ); + opts.classes.push(`dotdungeon`, `style-v3`); + return opts; + }; + + activateListeners(html) { + super.activateListeners(html); + + new GenericContextMenu(html, `.photo.panel`, [ + { + name: localizer(`dotdungeon.common.view-larger`), + callback: () => { + (new ImagePopout(this.item.img)).render(true); + }, + }, + { + name: localizer(`dotdungeon.common.edit`), + condition: () => this.isEditable, + callback: () => { + const fp = new FilePicker({ + callback: (path) => { + this.item.update({"img": path}); + }, + }); + fp.render(true); + }, + }, + { + name: localizer(`dotdungeon.common.reset`), + condition: () => this.isEditable, + callback: () => { + console.log(`.dungeon | Reset Item Image`) + }, + } + ]); + + if (!this.isEditable) return; + console.debug(`.dungeon | Adding event listeners for Weapon Item: ${this.item.id}`); + + html.find(`.create-ae`).on(`click`, async () => { + await this.item.createEmbeddedDocuments( + `ActiveEffect`, + [{name: localizer(`dotdungeon.default.name`, { document: `ActiveEffect`, type: `base` })}], + { renderSheet: true } + ); + }); + + new GenericContextMenu(html, `.effect.panel`, [ + { + name: localizer(`dotdungeon.common.edit`), + callback: async (html) => { + (await fromUuid(html.closest(`.effect`)[0].dataset.embeddedId))?.sheet.render(true); + }, + }, + { + name: localizer(`dotdungeon.common.delete`), + callback: async (html) => { + const target = html.closest(`.effect`)[0]; + const data = target.dataset; + const id = data.embeddedId; + const doc = await fromUuid(id); + DialogManager.createOrFocus( + `${doc.uuid}-delete`, + { + title: localizer(`dotdungeon.delete.ActiveEffect.title`, doc), + content: localizer(`dotdungeon.delete.ActiveEffect.content`, doc), + buttons: { + yes: { + label: localizer(`Yes`), + callback() { + doc.delete(); + }, + }, + no: { + label: localizer(`No`), + } + } + } + ); + }, + } + ]); + }; + + async getData() { + const ctx = await super.getData(); + + ctx.meta.showSettingsTab = ctx.isGM || this.item.isOwned; + ctx.meta.isEmbedded = this.item.isOwned; + ctx.meta.isEditable = this.isEditable; + + return ctx; + }; +}; diff --git a/styles/v3/elements/panel.scss b/styles/v3/elements/panel.scss index de7cf9f..5780e47 100644 --- a/styles/v3/elements/panel.scss +++ b/styles/v3/elements/panel.scss @@ -6,16 +6,24 @@ border-radius: 4px; padding: var(--gap); + &.space-between { + justify-content: space-between; + } + &--row { @extend .panel; display: flex; flex-direction: row; gap: var(--gap); align-items: center; + } - &.space-between { - justify-content: space-between; - } + &--column { + @extend .panel; + display: flex; + flex-direction: column; + gap: var(--gap); + justify-content: center; } } } diff --git a/styles/v3/index.scss b/styles/v3/index.scss index 4c321c5..b71c20b 100644 --- a/styles/v3/index.scss +++ b/styles/v3/index.scss @@ -21,6 +21,7 @@ @use "./layouts/datasheet.scss"; @use "./layouts/items/common.scss"; @use "./layouts/items/untyped/v2.scss"; +@use "./layouts/items/weapon/v1.scss"; /* Sheet Options */ .dotdungeon.style-v3 { diff --git a/styles/v3/layouts/items/weapon/v1.scss b/styles/v3/layouts/items/weapon/v1.scss new file mode 100644 index 0000000..82f8aaa --- /dev/null +++ b/styles/v3/layouts/items/weapon/v1.scss @@ -0,0 +1,13 @@ +@use "../../../mixins/material"; +@use "../../../mixins/utils"; + +.dotdungeon.style-v3 .item--weapon-v1 { + @include utils.tab("details") { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + + .full-width { + grid-column: 1 / -1; + } + } +} diff --git a/templates/items/weapon/v1/index.hbs b/templates/items/weapon/v1/index.hbs new file mode 100644 index 0000000..5863564 --- /dev/null +++ b/templates/items/weapon/v1/index.hbs @@ -0,0 +1,21 @@ + +
+ {{> dotdungeon.untyped.v2.general }} + {{> dotdungeon.weapon.v1.details }} + {{#if meta.showSettingsTab}} + {{> dotdungeon.untyped.v2.settings }} + {{/if}} + {{> dotdungeon.untyped.v2.effects }} +
+ + + diff --git a/templates/items/weapon/v1/tabs/details.v1.weapon.hbs b/templates/items/weapon/v1/tabs/details.v1.weapon.hbs new file mode 100644 index 0000000..c60b42d --- /dev/null +++ b/templates/items/weapon/v1/tabs/details.v1.weapon.hbs @@ -0,0 +1,83 @@ +
+
+ {{#if meta.isEditable}} + + + {{else}} + Purchase Cost + {{dd-empty-state system.buy}} + {{/if}} +
+
+ {{#if meta.isEditable}} + + + {{else}} + Usage Cost + {{dd-empty-state system.usage_cost}} + {{/if}} +
+
+ Rarity + {{#if meta.isEditable}} + + {{else}} + {{dd-i18n (concat "dotdungeon.rarity." system.tier)}} + {{/if}} +
+ {{#if meta.isEmbedded}} +
+ Item Location + {{#if meta.isEditable}} + + {{else}} + {{dd-i18n (concat "dotdungeon.location." system.location)}} + {{/if}} +
+
+ {{#if meta.isEditable}} + + + {{else}} + Quantity + {{system.quantity}} + {{/if}} +
+ {{/if}} +
From 718bd8398da1e949d510004b7fae0c600d806f60 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 5 May 2024 16:39:25 -0600 Subject: [PATCH 20/23] Move the data that should not be stored in the DB into the Actor's prepareBaseData method --- module/documents/Actor/Player.mjs | 11 +++++++++++ module/models/Actor/Player.mjs | 14 +------------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/module/documents/Actor/Player.mjs b/module/documents/Actor/Player.mjs index 2c5840d..7430fa1 100644 --- a/module/documents/Actor/Player.mjs +++ b/module/documents/Actor/Player.mjs @@ -2,6 +2,17 @@ import { DotDungeonActor } from "./GenericActor.mjs"; export class Player extends DotDungeonActor { + /* + These are special data properties that will be used by ActiveEffects to modify + certain limits within the actor, allowing for neat hacks that change these and + possible configuration of them. + */ + prepareBaseData() { + this.system.weapon_slots = 2; + this.system.inventory_slots = 0; + this.system.respawn_limit = 3; + }; + applyActiveEffects() { super.applyActiveEffects(); diff --git a/module/models/Actor/Player.mjs b/module/models/Actor/Player.mjs index e7ba912..1b71b47 100644 --- a/module/models/Actor/Player.mjs +++ b/module/models/Actor/Player.mjs @@ -14,14 +14,6 @@ export class PlayerData extends foundry.abstract.TypeDataModel { static defineSchema() { const fields = foundry.data.fields; return { - /* - These are special data properties that will be used by ActiveEffects - to modify certain limits within the actor, allowing for neat hacks - that change these - */ - weapon_slots: new fields.NumberField({ initial: 2 }), - inventory_slots: new fields.NumberField({ initial: 0 }), - bytes: new fields.NumberField({ initial: 0, min: 0, @@ -77,11 +69,7 @@ export class PlayerData extends foundry.abstract.TypeDataModel { min: 0, integer: true }), - respawns: new fields.SchemaField({ - r1: new fields.BooleanField(), - r2: new fields.BooleanField(), - r3: new fields.BooleanField(), - }), + respawns: new fields.NumberField({ initial: 0, }), syncDelta: new fields.NumberField({ required: true, integer: true, From 0972a0491f56095e337ee7ce1b4724648ce1ae1e Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 5 May 2024 16:39:56 -0600 Subject: [PATCH 21/23] Cleanup and improve JSDoc typing --- module/components/icon.mjs | 2 ++ module/components/incrementer.mjs | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/module/components/icon.mjs b/module/components/icon.mjs index 8c70d40..82c8e99 100644 --- a/module/components/icon.mjs +++ b/module/components/icon.mjs @@ -4,6 +4,8 @@ import { StyledShadowElement } from "./mixins/Styles.mjs"; Attributes: @property {string} name - The name of the icon, takes precedence over the path @property {string} path - The path of the icon file + +@extends {HTMLElement} */ export class DotDungeonIcon extends StyledShadowElement(HTMLElement) { static elementName = `dd-icon`; diff --git a/module/components/incrementer.mjs b/module/components/incrementer.mjs index 09e81ab..0c15387 100644 --- a/module/components/incrementer.mjs +++ b/module/components/incrementer.mjs @@ -13,6 +13,8 @@ Attributes: Styling: - `--height`: Controls the height of the element + the width of the buttons (default: 1.25rem) - `--width`: Controls the width of the number input (default 50px) + +@extends {HTMLElement} */ export class DotDungeonIncrementer extends StyledShadowElement( @@ -42,14 +44,14 @@ extends StyledShadowElement( get form() { return this._internals.form; - } + }; get name() { return this.getAttribute(`name`); - } + }; set name(value) { this.setAttribute(`name`, value); - } + }; get value() { return this.getAttribute(`value`); @@ -60,7 +62,7 @@ extends StyledShadowElement( get type() { return `number`; - } + }; connectedCallback() { super.connectedCallback(); From c3e7aee501eaa833b4f9ebec2838c1a272142ccc Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 5 May 2024 16:40:59 -0600 Subject: [PATCH 22/23] Add a dd-range component for repeated checkboxes that represent something like "1/3" --- module/components/index.mjs | 2 + module/components/range.mjs | 139 ++++++++++++++++++++++++++++++++ styles/v3/components/range.scss | 68 ++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 module/components/range.mjs create mode 100644 styles/v3/components/range.scss diff --git a/module/components/index.mjs b/module/components/index.mjs index f4d39e9..7d870b0 100644 --- a/module/components/index.mjs +++ b/module/components/index.mjs @@ -1,9 +1,11 @@ import { DotDungeonIncrementer } from "./incrementer.mjs"; import { DotDungeonIcon } from "./icon.mjs"; +import { DotDungeonRange } from "./range.mjs"; const components = [ DotDungeonIcon, DotDungeonIncrementer, + DotDungeonRange, ]; export function registerCustomComponents() { diff --git a/module/components/range.mjs b/module/components/range.mjs new file mode 100644 index 0000000..db286fe --- /dev/null +++ b/module/components/range.mjs @@ -0,0 +1,139 @@ +import { DotDungeonIcon } from "./icon.mjs"; +import { StyledShadowElement } from "./mixins/Styles.mjs"; + +/** +Attributes: +@property {string} name - The path to the value to update in the datamodel +@property {number} value - The actual value of the input +@property {number} max - The maximum value that this range has + +@extends {HTMLElement} +*/ +export class DotDungeonRange +extends StyledShadowElement( + HTMLElement, + { mode: `open`, delegatesFocus: true } +) { + static elementName = `dd-range`; + static formAssociated = true; + + static observedAttributes = [`max`]; + + static _stylePath = `v3/components/range.css`; + + _internals; + #input; + + constructor() { + super(); + + // Form internals + this._internals = this.attachInternals(); + this._internals.role = `spinbutton`; + }; + + get form() { + return this._internals.form; + }; + + get name() { + return this.getAttribute(`name`); + }; + set name(value) { + this.setAttribute(`name`, value); + }; + + get value() { + try { + return parseInt(this.getAttribute(`value`)); + } catch { + throw new Error(`Failed to parse attribute: "value" - Make sure it's an integer`); + }; + }; + set value(value) { + this.setAttribute(`value`, value); + }; + + get max() { + try { + return parseInt(this.getAttribute(`max`)); + } catch { + throw new Error(`Failed to parse attribute: "max" - Make sure it's an integer`); + }; + }; + set max(value) { + this.setAttribute(`max`, value); + }; + + get type() { + return `number`; + }; + + connectedCallback() { + super.connectedCallback(); + + // Attribute validation + if (!this.hasAttribute(`max`)) { + throw new Error(`dotdungeon | Cannot have a range without a maximum value`); + }; + + // Keyboard accessible input for the thing + this.#input = document.createElement(`input`); + this.#input.type = `number`; + this.#input.min = 0; + this.#input.max = this.max; + this.#input.value = this.value; + this.#input.addEventListener(`change`, () => { + const inputValue = parseInt(this.#input.value); + if (inputValue === this.value) return; + this._updateValue.bind(this)(Math.sign(this.value - inputValue)); + this._updateValue(Math.sign(this.value - inputValue)); + }); + this._shadow.appendChild(this.#input); + + // Shadow-DOM construction + this._elements = new Array(this.max); + const container = document.createElement(`div`); + container.classList.add(`container`); + + // Creating the node for filled content + const filledContainer = document.createElement(`div`); + filledContainer.classList.add(`range-increment`, `filled`); + const filledNode = this.querySelector(`[slot="filled"]`); + if (filledNode) filledContainer.appendChild(filledNode); + + const emptyContainer = document.createElement(`div`); + emptyContainer.classList.add(`range-increment`, `empty`); + const emptyNode = this.querySelector(`[slot="empty"]`); + if (emptyNode) emptyContainer.appendChild(emptyNode); + + this._elements.fill(filledContainer, 0, this.value); + this._elements.fill(emptyContainer, this.value); + container.append(...this._elements.map((slot, i) => { + const node = slot.cloneNode(true); + node.setAttribute(`data-index`, i + 1); + node.addEventListener(`click`, () => { + const filled = node.classList.contains(`filled`); + this._updateValue(filled ? -1 : 1); + }); + return node; + })); + this._shadow.appendChild(container); + + /* + This converts all of the namespace prefixed properties on the element to + CSS variables so that they don't all need to be provided by doing style="" + */ + for (const attrVar of this.attributes) { + if (attrVar.name?.startsWith(`var:`)) { + const prop = attrVar.name.replace(`var:`, ``); + this.style.setProperty(`--` + prop, attrVar.value); + }; + }; + }; + + _updateValue(delta) { + this.value += delta; + this.dispatchEvent(new Event(`change`, { bubbles: true })); + }; +}; diff --git a/styles/v3/components/range.scss b/styles/v3/components/range.scss new file mode 100644 index 0000000..15ea6d2 --- /dev/null +++ b/styles/v3/components/range.scss @@ -0,0 +1,68 @@ +/* +Disclaimer: This CSS is used by a custom web component and is scoped to JUST +the corresponding web component. Importing this into other files is forbidden +*/ + +@use "../mixins/material"; +@use "./common.scss"; + +.container { + @include material.elevate(4); + display: grid; + flex-direction: row; + gap: 4px; + border-radius: 4px; + grid-template-columns: minmax(0, 1fr); + grid-auto-columns: minmax(0, 1fr); + grid-template-rows: 1fr; + padding: 4px; + box-sizing: border-box; +} + +input { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; + + &:focus ~ .container { + @include material.elevate(8) + } +} + +.range-increment { + grid-row: 1; + + &:empty { + @include material.elevate(4); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border-radius: 2px; + margin: 0; + cursor: pointer; + width: 1.25rem; + height: 1.25rem; + position: relative; + + &:hover { + @include material.elevate(8); + } + + &.filled::before { + content: ""; + background: var(--checkbox-checked); + border-radius: 3px; + $margin: 4px; + top: $margin; + bottom: $margin; + left: $margin; + right: $margin; + position: absolute; + } + } +} From 5d41b087b5036f4f0edda680e4381795787c7abe Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 5 May 2024 16:41:17 -0600 Subject: [PATCH 23/23] Remove the function definition where it wasn't needed --- module/sheets/GenericActorSheet.mjs | 2 +- module/sheets/Items/GenericItemSheet.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/module/sheets/GenericActorSheet.mjs b/module/sheets/GenericActorSheet.mjs index 2010304..ba668db 100644 --- a/module/sheets/GenericActorSheet.mjs +++ b/module/sheets/GenericActorSheet.mjs @@ -58,7 +58,7 @@ export class GenericActorSheet extends ActorSheet { */ html.find( CONFIG.CACHE.componentListeners.map(n => `${n}[name]`).join(`,`) - ).on(`change`, () => this._onChangeInput.bind(this)); + ).on(`change`, this._onChangeInput.bind(this)); /* Utility event listeners that apply diff --git a/module/sheets/Items/GenericItemSheet.mjs b/module/sheets/Items/GenericItemSheet.mjs index 21d47a3..ef896a7 100644 --- a/module/sheets/Items/GenericItemSheet.mjs +++ b/module/sheets/Items/GenericItemSheet.mjs @@ -50,7 +50,7 @@ export class GenericItemSheet extends ItemSheet { */ html.find( CONFIG.CACHE.componentListeners.map(n => `${n}[name]`).join(`,`) - ).on(`change`, () => this._onChangeInput.bind(this)); + ).on(`change`, this._onChangeInput.bind(this)); html.find(`button[data-increment]`) .on(`click`, this._incrementValue.bind(this));