Skip to content

Commit

Permalink
Merge pull request #2542 from arbron/advancement/size
Browse files Browse the repository at this point in the history
[#2220] Add SizeAdvancement
  • Loading branch information
arbron committed Nov 6, 2023
2 parents 5c34309 + bf3e373 commit 1226f76
Show file tree
Hide file tree
Showing 16 changed files with 282 additions and 1 deletion.
21 changes: 21 additions & 0 deletions dnd5e.css
Original file line number Diff line number Diff line change
Expand Up @@ -1434,6 +1434,9 @@ h5 {
/* Scale Value */
/* ----------------------------------------- */
/* ----------------------------------------- */
/* Size */
/* ----------------------------------------- */
/* ----------------------------------------- */
/* Traits */
/* ----------------------------------------- */
}
Expand Down Expand Up @@ -1613,6 +1616,19 @@ 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: grid;
grid-template-columns: 1fr 1fr;
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;
}
Expand Down Expand Up @@ -1737,6 +1753,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;
Expand Down
1 change: 1 addition & 0 deletions icons/LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions icons/svg/size.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@
"DND5E.AdvancementScaleValueTypeString": "Anything",
"DND5E.AdvancementSelectionCreateButton": "Create Advancement",
"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).",
Expand Down
29 changes: 29 additions & 0 deletions less/advancement.less
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,27 @@
}
}

/* ----------------------------------------- */
/* Size */
/* ----------------------------------------- */
&.size {
textarea {
flex: 1 0 100%;
}

.trait-list {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.25rem;
padding: 0;
list-style: none;

li {
flex: 1 1 40%;
}
}
}

/* ----------------------------------------- */
/* Traits */
/* ----------------------------------------- */
Expand Down Expand Up @@ -357,6 +378,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;
Expand Down
2 changes: 2 additions & 0 deletions module/applications/advancement/_module.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
40 changes: 40 additions & 0 deletions module/applications/advancement/size-config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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"],
template: "systems/dnd5e/templates/advancement/size-config.hbs"
});
}

/* -------------------------------------------- */

/** @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) };
return obj;
}, {})
});
}

/* -------------------------------------------- */

/** @inheritdoc */
async prepareConfigurationUpdate(configuration) {
configuration.sizes = filteredKeys(configuration.sizes ?? {});
return configuration;
}
}
30 changes: 30 additions & 0 deletions module/applications/advancement/size-flow.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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() {
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(sizes).reduce((obj, key) => {
obj[key] = CONFIG.DND5E.actorSizes[key];
return obj;
}, {})
});
}
}
1 change: 1 addition & 0 deletions module/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2005,6 +2005,7 @@ DND5E.advancementTypes = {
ItemChoice: advancement.ItemChoiceAdvancement,
ItemGrant: advancement.ItemGrantAdvancement,
ScaleValue: advancement.ScaleValueAdvancement,
Size: advancement.SizeAdvancement,
Trait: advancement.TraitAdvancement
};

Expand Down
1 change: 1 addition & 0 deletions module/data/advancement/_module.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
26 changes: 26 additions & 0 deletions module/data/advancement/size.mjs
Original file line number Diff line number Diff line change
@@ -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"})
};
}
}
1 change: 1 addition & 0 deletions module/documents/advancement/_module.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
100 changes: 100 additions & 0 deletions module/documents/advancement/size.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
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 */
/* -------------------------------------------- */

/**
* 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];
}

/* -------------------------------------------- */
/* 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 => `<span class="tag">${CONFIG.DND5E.actorSizes[s]}</span>`).join("");
}

/* -------------------------------------------- */
/* Editing Methods */
/* -------------------------------------------- */

/** @inheritdoc */
static availableForItem(item) {
return !item.advancement.byType.Size?.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 });
}
}
10 changes: 10 additions & 0 deletions templates/advancement/size-config.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<form autocomplete="off">
{{> "dnd5e.advancement-controls"}}

<div class="form-group">
<label>{{localize "DND5E.AdvancementHint"}}</label>
<textarea name="configuration.hint" placeholder="{{default.hint}}">{{configuration.hint}}</textarea>
</div>

{{> "dnd5e.trait-list" choices=sizes prefix="configuration.sizes"}}
</form>
13 changes: 13 additions & 0 deletions templates/advancement/size-flow.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<form id="{{appId}}" data-level="{{level}}" data-id="{{advancement.id}}" data-type="{{type}}">
<h3>{{{this.title}}}</h3>

<p>{{hint}}</p>

{{#if singleSize}}
<input type="hidden" name="size" value="{{singleSize}}">
{{else}}
<select name="size">
{{selectOptions sizes selected=selectedSize}}
</select>
{{/if}}
</form>
3 changes: 2 additions & 1 deletion templates/items/race.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

<ul class="summary flexrow">
<li>
<input type="text" name="system.source" value="{{system.source}}" placeholder="{{localize 'DND5E.Source'}}">
<input type="text" name="system.source.custom" value="{{system.source.custom}}"
placeholder="{{localize 'DND5E.Source'}}">
</li>
</ul>
</div>
Expand Down

0 comments on commit 1226f76

Please sign in to comment.