From e6e31c3bbf7dd2d3f5a9cfec5c0459c582415132 Mon Sep 17 00:00:00 2001 From: Jeff Hitchcock Date: Tue, 31 Oct 2023 17:19:04 -0700 Subject: [PATCH] [#2220] Add SizeAdvancement --- dnd5e.css | 18 ++++ icons/LICENSE | 1 + icons/svg/size.svg | 1 + lang/en.json | 2 + less/advancement.less | 26 ++++++ module/applications/advancement/_module.mjs | 2 + .../applications/advancement/size-config.mjs | 39 +++++++++ module/applications/advancement/size-flow.mjs | 27 ++++++ module/config.mjs | 3 +- 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, 249 insertions(+), 2 deletions(-) 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 930dde8fb4..03e7a43846 100644 --- a/dnd5e.css +++ b/dnd5e.css @@ -1427,6 +1427,9 @@ h5 { /* ----------------------------------------- */ /* Scale Value */ /* ----------------------------------------- */ + /* ----------------------------------------- */ + /* Size */ + /* ----------------------------------------- */ } .dnd5e.advancement input[type="text"], .dnd5e.advancement input[type="number"], @@ -1604,6 +1607,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-migration .items-list { margin-block-end: 1em; } @@ -1682,6 +1695,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 6e85d7d1ee..bb1495422f 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 a1cc170858..14069ed646 100644 --- a/less/advancement.less +++ b/less/advancement.less @@ -204,6 +204,24 @@ } } } + + /* ----------------------------------------- */ + /* Size */ + /* ----------------------------------------- */ + + &.size { + .trait-list { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + padding: 0; + list-style: none; + + li { + flex: 1 1 40%; + } + } + } } .dnd5e.advancement-migration { @@ -299,6 +317,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 a77b97e179..127779a7c4 100644 --- a/module/applications/advancement/_module.mjs +++ b/module/applications/advancement/_module.mjs @@ -15,3 +15,5 @@ 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"; 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 c22b1d77bf..e5aa3d31e4 100644 --- a/module/config.mjs +++ b/module/config.mjs @@ -1966,7 +1966,8 @@ DND5E.advancementTypes = { HitPoints: advancement.HitPointsAdvancement, ItemChoice: advancement.ItemChoiceAdvancement, ItemGrant: advancement.ItemGrantAdvancement, - ScaleValue: advancement.ScaleValueAdvancement + ScaleValue: advancement.ScaleValueAdvancement, + Size: advancement.SizeAdvancement }; /* -------------------------------------------- */ 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 cbb5e7b9ed..fceb1ce6bb 100644 --- a/module/documents/advancement/_module.mjs +++ b/module/documents/advancement/_module.mjs @@ -5,3 +5,4 @@ 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"; 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 @@