From c2c421ef2838677da0e910844eb20f3566ab5185 Mon Sep 17 00:00:00 2001 From: Jeff Hitchcock Date: Tue, 31 Oct 2023 17:19:04 -0700 Subject: [PATCH 1/2] [#2220] Add SizeAdvancement --- dnd5e.css | 18 ++++ icons/LICENSE | 1 + icons/svg/size.svg | 1 + lang/en.json | 2 + less/advancement.less | 25 ++++++ module/applications/advancement/_module.mjs | 2 + .../applications/advancement/size-config.mjs | 39 +++++++++ module/applications/advancement/size-flow.mjs | 27 ++++++ module/config.mjs | 1 + module/data/advancement/_module.mjs | 1 + module/data/advancement/size.mjs | 26 ++++++ module/documents/advancement/_module.mjs | 1 + module/documents/advancement/size.mjs | 82 +++++++++++++++++++ templates/advancement/size-config.hbs | 10 +++ templates/advancement/size-flow.hbs | 9 ++ templates/items/race.hbs | 3 +- 16 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 icons/svg/size.svg create mode 100644 module/applications/advancement/size-config.mjs create mode 100644 module/applications/advancement/size-flow.mjs create mode 100644 module/data/advancement/size.mjs create mode 100644 module/documents/advancement/size.mjs create mode 100644 templates/advancement/size-config.hbs create mode 100644 templates/advancement/size-flow.hbs diff --git a/dnd5e.css b/dnd5e.css index b3c3c8ca63..1e35d25537 100644 --- a/dnd5e.css +++ b/dnd5e.css @@ -1434,6 +1434,9 @@ h5 { /* Scale Value */ /* ----------------------------------------- */ /* ----------------------------------------- */ + /* Size */ + /* ----------------------------------------- */ + /* ----------------------------------------- */ /* Traits */ /* ----------------------------------------- */ } @@ -1613,6 +1616,16 @@ h5 { .dnd5e.advancement.scale-value select option[value=""] { color: var(--color-text-light-6); } +.dnd5e.advancement.size .trait-list { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + padding: 0; + list-style: none; +} +.dnd5e.advancement.size .trait-list li { + flex: 1 1 40%; +} .dnd5e.advancement.traits { --grid-two-column-right-size: 0.6fr; } @@ -1737,6 +1750,11 @@ h5 { .dnd5e.advancement.flow form[data-type="ScaleValue"] span.none { font-style: italic; } +.dnd5e.advancement.flow form[data-type="Size"] select { + width: 100%; + font-size: var(--font-size-16); + height: 2em; +} .dnd5e.advancement.flow nav { display: flex; justify-content: flex-end; diff --git a/icons/LICENSE b/icons/LICENSE index b0d661cfdc..6f56023a86 100644 --- a/icons/LICENSE +++ b/icons/LICENSE @@ -9,6 +9,7 @@ The dnd5e system for Foundry Virtual Tabletop includes icon artwork licensed fro /svg/item-choice.svg - "Choice" by Delapouite under CC BY 3.0 /svg/item-grant.svg - "White book" by Willdabeast under CC BY 3.0 /svg/scale-value.svg - "Dice target" by Delapouite under CC BY 3.0 +/svg/size.svg - "Body height" by Delapouite under CC BY 3.0 /svg/trait.svg - "Scroll unfurled" by Lorc under CC BY 3.0 /svg/trait-armor-proficiencies.svg - "Leather armor" by Delapouite under CC BY 3.0 /svg/trait-damage-immunities.svg - "Aura" by Lorc under CC BY 3.0 diff --git a/icons/svg/size.svg b/icons/svg/size.svg new file mode 100644 index 0000000000..5f0594fa69 --- /dev/null +++ b/icons/svg/size.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lang/en.json b/lang/en.json index a13bee8236..3127769b01 100644 --- a/lang/en.json +++ b/lang/en.json @@ -249,6 +249,8 @@ "DND5E.AdvancementScaleValueTypeString": "Anything", "DND5E.AdvancementSelectionCreateButton": "Create Advancement", "DND5E.AdvancementSelectionTitle": "Select Advancement Type", +"DND5E.AdvancementSizeTitle": "Size", +"DND5E.AdvancementSizeHint": "Set a character's size.", "DND5E.AdvancementTitle": "Advancement", "DND5E.AdvancementTraitTitle": "Traits", "DND5E.AdvancementTraitHint": "Grant a character certain traits or give them an option to select traits (such as proficiencies, skills, languages).", diff --git a/less/advancement.less b/less/advancement.less index 7212c31e42..bcb7f0c16b 100644 --- a/less/advancement.less +++ b/less/advancement.less @@ -205,6 +205,23 @@ } } + /* ----------------------------------------- */ + /* Size */ + /* ----------------------------------------- */ + &.size { + .trait-list { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + padding: 0; + list-style: none; + + li { + flex: 1 1 40%; + } + } + } + /* ----------------------------------------- */ /* Traits */ /* ----------------------------------------- */ @@ -357,6 +374,14 @@ span.none { font-style: italic; } } + form[data-type="Size"] { + select { + width: 100%; + font-size: var(--font-size-16); + height: 2em; + } + } + nav { display: flex; justify-content: flex-end; diff --git a/module/applications/advancement/_module.mjs b/module/applications/advancement/_module.mjs index 14d2fef1b3..a214963bac 100644 --- a/module/applications/advancement/_module.mjs +++ b/module/applications/advancement/_module.mjs @@ -15,4 +15,6 @@ export {default as ItemGrantConfig} from "./item-grant-config.mjs"; export {default as ItemGrantFlow} from "./item-grant-flow.mjs"; export {default as ScaleValueConfig} from "./scale-value-config.mjs"; export {default as ScaleValueFlow} from "./scale-value-flow.mjs"; +export {default as SizeConfig} from "./size-config.mjs"; +export {default as SizeFlow} from "./size-flow.mjs"; export {default as TraitConfig} from "./trait-config.mjs"; diff --git a/module/applications/advancement/size-config.mjs b/module/applications/advancement/size-config.mjs new file mode 100644 index 0000000000..02546c123e --- /dev/null +++ b/module/applications/advancement/size-config.mjs @@ -0,0 +1,39 @@ +import { filteredKeys } from "../../utils.mjs"; +import AdvancementConfig from "./advancement-config.mjs"; + +/** + * Configuration application for size advancement. + */ +export default class SizeConfig extends AdvancementConfig { + + /** @inheritdoc */ + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["dnd5e", "advancement", "size"], + dragDrop: [{ dropSelector: ".drop-target" }], + dropKeyPath: "items", + template: "systems/dnd5e/templates/advancement/size-config.hbs" + }); + } + + /* -------------------------------------------- */ + + /** @inheritdoc */ + getData() { + return foundry.utils.mergeObject(super.getData(), { + showLevelSelector: false, + sizes: Object.entries(CONFIG.DND5E.actorSizes).reduce((obj, [key, label]) => { + obj[key] = { label, chosen: this.advancement.configuration.sizes.has(key) }; + return obj; + }, {}) + }); + } + + /* -------------------------------------------- */ + + /** @inheritdoc */ + async prepareConfigurationUpdate(configuration) { + configuration.sizes = filteredKeys(configuration.sizes ?? {}); + return configuration; + } +} diff --git a/module/applications/advancement/size-flow.mjs b/module/applications/advancement/size-flow.mjs new file mode 100644 index 0000000000..d079ee7c3f --- /dev/null +++ b/module/applications/advancement/size-flow.mjs @@ -0,0 +1,27 @@ +import AdvancementFlow from "./advancement-flow.mjs"; + +/** + * Inline application that displays size advancement. + */ +export default class SizeFlow extends AdvancementFlow { + + /** @inheritdoc */ + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + template: "systems/dnd5e/templates/advancement/size-flow.hbs" + }); + } + + /* -------------------------------------------- */ + + /** @inheritdoc */ + getData() { + return foundry.utils.mergeObject(super.getData(), { + selectedSize: this.retainedData?.size ?? this.advancement.value.size, + sizes: Array.from(this.advancement.configuration.sizes).reduce((obj, key) => { + obj[key] = CONFIG.DND5E.actorSizes[key]; + return obj; + }, {}) + }); + } +} diff --git a/module/config.mjs b/module/config.mjs index 21aab2d101..3e8dfde6dc 100644 --- a/module/config.mjs +++ b/module/config.mjs @@ -1967,6 +1967,7 @@ DND5E.advancementTypes = { ItemChoice: advancement.ItemChoiceAdvancement, ItemGrant: advancement.ItemGrantAdvancement, ScaleValue: advancement.ScaleValueAdvancement, + Size: advancement.SizeAdvancement, Trait: advancement.TraitAdvancement }; diff --git a/module/data/advancement/_module.mjs b/module/data/advancement/_module.mjs index e544ab4ea9..72b0388fd9 100644 --- a/module/data/advancement/_module.mjs +++ b/module/data/advancement/_module.mjs @@ -5,4 +5,5 @@ export * from "./ability-score-improvement.mjs"; export {default as ItemChoiceConfigurationData} from "./item-choice.mjs"; export {default as ItemGrantConfigurationData} from "./item-grant.mjs"; export * as scaleValue from "./scale-value.mjs"; +export * from "./size.mjs"; export {TraitConfigurationData, TraitValueData} from "./trait.mjs"; diff --git a/module/data/advancement/size.mjs b/module/data/advancement/size.mjs new file mode 100644 index 0000000000..3b789a6075 --- /dev/null +++ b/module/data/advancement/size.mjs @@ -0,0 +1,26 @@ +/** + * Configuration data for the size advancement type. + */ +export class SizeConfigurationData extends foundry.abstract.DataModel { + /** @inheritdoc */ + static defineSchema() { + return { + hint: new foundry.data.fields.StringField({label: "DND5E.AdvancementHint"}), + sizes: new foundry.data.fields.SetField( + new foundry.data.fields.StringField(), {required: false, initial: ["med"], label: "DND5E.Size"} + ) + }; + } +} + +/** + * Value data for the size advancement type. + */ +export class SizeValueData extends foundry.abstract.DataModel { + /** @inheritdoc */ + static defineSchema() { + return { + size: new foundry.data.fields.StringField({required: false, label: "DND5E.Size"}) + }; + } +} diff --git a/module/documents/advancement/_module.mjs b/module/documents/advancement/_module.mjs index 5708b184dc..ae7596dcad 100644 --- a/module/documents/advancement/_module.mjs +++ b/module/documents/advancement/_module.mjs @@ -5,4 +5,5 @@ export {default as HitPointsAdvancement} from "./hit-points.mjs"; export {default as ItemChoiceAdvancement} from "./item-choice.mjs"; export {default as ItemGrantAdvancement} from "./item-grant.mjs"; export {default as ScaleValueAdvancement} from "./scale-value.mjs"; +export {default as SizeAdvancement} from "./size.mjs"; export {default as TraitAdvancement} from "./trait.mjs"; diff --git a/module/documents/advancement/size.mjs b/module/documents/advancement/size.mjs new file mode 100644 index 0000000000..730498efd1 --- /dev/null +++ b/module/documents/advancement/size.mjs @@ -0,0 +1,82 @@ +import Advancement from "./advancement.mjs"; +import SizeConfig from "../../applications/advancement/size-config.mjs"; +import SizeFlow from "../../applications/advancement/size-flow.mjs"; +import { SizeConfigurationData, SizeValueData } from "../../data/advancement/size.mjs"; + +/** + * Advancement that handles player size. + */ +export default class SizeAdvancement extends Advancement { + + /** @inheritdoc */ + static get metadata() { + return foundry.utils.mergeObject(super.metadata, { + dataModels: { + configuration: SizeConfigurationData, + value: SizeValueData + }, + order: 5, + icon: "systems/dnd5e/icons/svg/size.svg", + title: game.i18n.localize("DND5E.AdvancementSizeTitle"), + hint: game.i18n.localize("DND5E.AdvancementSizeHint"), + validItemTypes: new Set(["race"]), + apps: { + config: SizeConfig, + flow: SizeFlow + } + }); + } + + /* -------------------------------------------- */ + /* Instance Properties */ + /* -------------------------------------------- */ + + /** @inheritdoc */ + get levels() { + return [0]; + } + + /* -------------------------------------------- */ + /* Display Methods */ + /* -------------------------------------------- */ + + /** @inheritdoc */ + summaryForLevel(level, { configMode=false }={}) { + const sizes = configMode ? Array.from(this.configuration.sizes) : this.value.size ? [this.value.size] : []; + return sizes.map(s => `${CONFIG.DND5E.actorSizes[s]}`).join(""); + } + + /* -------------------------------------------- */ + /* Editing Methods */ + /* -------------------------------------------- */ + + /** @inheritdoc */ + static availableForItem(item) { + return !item.advancement.byType.Race?.length; + } + + /* -------------------------------------------- */ + /* Application Methods */ + /* -------------------------------------------- */ + + /** @inheritdoc */ + async apply(level, data) { + this.actor.updateSource({"system.traits.size": data.size ?? "med"}); + this.updateSource({ value: data }); + } + + /* -------------------------------------------- */ + + /** @inheritdoc */ + async restore(level, data) { + this.apply(level, data); + } + + /* -------------------------------------------- */ + + /** @inheritdoc */ + async reverse(level) { + this.actor.updateSource({"system.traits.size": "med"}); + this.updateSource({ "value.-=size": null }); + } +} diff --git a/templates/advancement/size-config.hbs b/templates/advancement/size-config.hbs new file mode 100644 index 0000000000..23d4d5ad26 --- /dev/null +++ b/templates/advancement/size-config.hbs @@ -0,0 +1,10 @@ +
+ {{> "dnd5e.advancement-controls"}} + +
+ + +
+ + {{> "dnd5e.trait-list" choices=sizes prefix="configuration.sizes"}} +
diff --git a/templates/advancement/size-flow.hbs b/templates/advancement/size-flow.hbs new file mode 100644 index 0000000000..f11cddfc08 --- /dev/null +++ b/templates/advancement/size-flow.hbs @@ -0,0 +1,9 @@ +
+

{{{this.title}}}

+ +

{{advancement.configuration.hint}}

+ + +
diff --git a/templates/items/race.hbs b/templates/items/race.hbs index ab20fb7425..d73cf6a502 100644 --- a/templates/items/race.hbs +++ b/templates/items/race.hbs @@ -15,7 +15,8 @@ From bf3e37394e65e52256964f4b0451e5e9543a34bf Mon Sep 17 00:00:00 2001 From: Jeff Hitchcock Date: Mon, 6 Nov 2023 10:40:35 -0800 Subject: [PATCH 2/2] [#2220] Add automatic hint, hide dropdown when only one size is possible, adjust styling --- dnd5e.css | 7 +++++-- lang/en.json | 2 ++ less/advancement.less | 8 ++++++-- .../applications/advancement/size-config.mjs | 5 +++-- module/applications/advancement/size-flow.mjs | 5 ++++- module/documents/advancement/size.mjs | 20 ++++++++++++++++++- templates/advancement/size-config.hbs | 2 +- templates/advancement/size-flow.hbs | 12 +++++++---- 8 files changed, 48 insertions(+), 13 deletions(-) diff --git a/dnd5e.css b/dnd5e.css index 1e35d25537..4a22e6e2bf 100644 --- a/dnd5e.css +++ b/dnd5e.css @@ -1616,9 +1616,12 @@ h5 { .dnd5e.advancement.scale-value select option[value=""] { color: var(--color-text-light-6); } +.dnd5e.advancement.size textarea { + flex: 1 0 100%; +} .dnd5e.advancement.size .trait-list { - display: flex; - flex-wrap: wrap; + display: grid; + grid-template-columns: 1fr 1fr; gap: 0.25rem; padding: 0; list-style: none; diff --git a/lang/en.json b/lang/en.json index 3127769b01..31ea06e90f 100644 --- a/lang/en.json +++ b/lang/en.json @@ -251,6 +251,8 @@ "DND5E.AdvancementSelectionTitle": "Select Advancement Type", "DND5E.AdvancementSizeTitle": "Size", "DND5E.AdvancementSizeHint": "Set a character's size.", +"DND5E.AdvancementSizeFlowHintSingle": "Your size is {size}.", +"DND5E.AdvancementSizeflowHintMultiple": "Choose your size from either {sizes}.", "DND5E.AdvancementTitle": "Advancement", "DND5E.AdvancementTraitTitle": "Traits", "DND5E.AdvancementTraitHint": "Grant a character certain traits or give them an option to select traits (such as proficiencies, skills, languages).", diff --git a/less/advancement.less b/less/advancement.less index bcb7f0c16b..3fdc41ce5e 100644 --- a/less/advancement.less +++ b/less/advancement.less @@ -209,9 +209,13 @@ /* Size */ /* ----------------------------------------- */ &.size { + textarea { + flex: 1 0 100%; + } + .trait-list { - display: flex; - flex-wrap: wrap; + display: grid; + grid-template-columns: 1fr 1fr; gap: 0.25rem; padding: 0; list-style: none; diff --git a/module/applications/advancement/size-config.mjs b/module/applications/advancement/size-config.mjs index 02546c123e..8cf89e3ec4 100644 --- a/module/applications/advancement/size-config.mjs +++ b/module/applications/advancement/size-config.mjs @@ -10,8 +10,6 @@ export default class SizeConfig extends AdvancementConfig { static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { classes: ["dnd5e", "advancement", "size"], - dragDrop: [{ dropSelector: ".drop-target" }], - dropKeyPath: "items", template: "systems/dnd5e/templates/advancement/size-config.hbs" }); } @@ -21,6 +19,9 @@ export default class SizeConfig extends AdvancementConfig { /** @inheritdoc */ getData() { return foundry.utils.mergeObject(super.getData(), { + default: { + hint: this.advancement.automaticHint + }, showLevelSelector: false, sizes: Object.entries(CONFIG.DND5E.actorSizes).reduce((obj, [key, label]) => { obj[key] = { label, chosen: this.advancement.configuration.sizes.has(key) }; diff --git a/module/applications/advancement/size-flow.mjs b/module/applications/advancement/size-flow.mjs index d079ee7c3f..0a19d53ab0 100644 --- a/module/applications/advancement/size-flow.mjs +++ b/module/applications/advancement/size-flow.mjs @@ -16,9 +16,12 @@ export default class SizeFlow extends AdvancementFlow { /** @inheritdoc */ getData() { + const sizes = this.advancement.configuration.sizes; return foundry.utils.mergeObject(super.getData(), { + singleSize: sizes.size === 1 ? sizes.first() : null, + hint: this.advancement.configuration.hint || this.advancement.automaticHint, selectedSize: this.retainedData?.size ?? this.advancement.value.size, - sizes: Array.from(this.advancement.configuration.sizes).reduce((obj, key) => { + sizes: Array.from(sizes).reduce((obj, key) => { obj[key] = CONFIG.DND5E.actorSizes[key]; return obj; }, {}) diff --git a/module/documents/advancement/size.mjs b/module/documents/advancement/size.mjs index 730498efd1..0b18121c1d 100644 --- a/module/documents/advancement/size.mjs +++ b/module/documents/advancement/size.mjs @@ -31,6 +31,24 @@ export default class SizeAdvancement extends Advancement { /* Instance Properties */ /* -------------------------------------------- */ + /** + * Hint that will be displayed to players if none is entered. + * @type {string} + */ + get automaticHint() { + if ( !this.configuration.sizes.size ) return ""; + if ( this.configuration.sizes.size === 1 ) return game.i18n.format("DND5E.AdvancementSizeFlowHintSingle", { + size: CONFIG.DND5E.actorSizes[this.configuration.sizes.first()] + }); + + const listFormatter = new Intl.ListFormat(game.i18n.lang, { type: "disjunction" }); + return game.i18n.format("DND5E.AdvancementSizeflowHintMultiple", { + sizes: listFormatter.format(this.configuration.sizes.map(s => CONFIG.DND5E.actorSizes[s])) + }); + } + + /* -------------------------------------------- */ + /** @inheritdoc */ get levels() { return [0]; @@ -52,7 +70,7 @@ export default class SizeAdvancement extends Advancement { /** @inheritdoc */ static availableForItem(item) { - return !item.advancement.byType.Race?.length; + return !item.advancement.byType.Size?.length; } /* -------------------------------------------- */ diff --git a/templates/advancement/size-config.hbs b/templates/advancement/size-config.hbs index 23d4d5ad26..0eb29977cb 100644 --- a/templates/advancement/size-config.hbs +++ b/templates/advancement/size-config.hbs @@ -3,7 +3,7 @@
- +
{{> "dnd5e.trait-list" choices=sizes prefix="configuration.sizes"}} diff --git a/templates/advancement/size-flow.hbs b/templates/advancement/size-flow.hbs index f11cddfc08..241a9570bd 100644 --- a/templates/advancement/size-flow.hbs +++ b/templates/advancement/size-flow.hbs @@ -1,9 +1,13 @@

{{{this.title}}}

-

{{advancement.configuration.hint}}

+

{{hint}}

- + {{#if singleSize}} + + {{else}} + + {{/if}}