forked from GitHub-Mirrors/foundry-sw5e
DND5e Core 1.3.5
DND5e Core 1.3.5 modded to SW5e System Combining with DND5e Core 1.3.2 to see one big commit since last core update DND5e Core 1.3.2 modded to SW5e System
This commit is contained in:
parent
c208552f70
commit
b56a074697
147 changed files with 3615 additions and 1875 deletions
|
@ -39,12 +39,12 @@ export default class AbilityUseDialog extends Dialog {
|
|||
// Prepare dialog form data
|
||||
const data = {
|
||||
item: item.data,
|
||||
title: game.i18n.format("SW5E.AbilityUseHint", item.data),
|
||||
title: game.i18n.format("SW5E.AbilityUseHint", {type: game.i18n.localize(`SW5E.ItemType${item.type.capitalize()}`), name: item.name}),
|
||||
note: this._getAbilityUseNote(item.data, uses, recharge),
|
||||
consumePowerSlot: false,
|
||||
consumeRecharge: recharges,
|
||||
consumeResource: !!itemData.consume.target,
|
||||
consumeUses: uses.max,
|
||||
consumeUses: uses.per && (uses.max > 0),
|
||||
canUse: recharges ? recharge.charged : sufficientUses,
|
||||
createTemplate: game.user.can("TEMPLATE_CREATE") && item.hasAreaTarget,
|
||||
errors: []
|
||||
|
@ -59,7 +59,7 @@ export default class AbilityUseDialog extends Dialog {
|
|||
const label = game.i18n.localize("SW5E.AbilityUse" + (data.isPower ? "Cast" : "Use"));
|
||||
return new Promise((resolve) => {
|
||||
const dlg = new this(item, {
|
||||
title: `${item.name}: Usage Configuration`,
|
||||
title: `${item.name}: ${game.i18n.localize("SW5E.AbilityUseConfig")}`,
|
||||
content: html,
|
||||
buttons: {
|
||||
use: {
|
||||
|
@ -133,7 +133,7 @@ export default class AbilityUseDialog extends Dialog {
|
|||
}));
|
||||
|
||||
// Merge power casting data
|
||||
return mergeObject(data, { isPower: true, consumePowerSlot, powerLevels });
|
||||
return foundry.utils.mergeObject(data, { isPower: true, consumePowerSlot, powerLevels });
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -151,7 +151,7 @@ export default class AbilityUseDialog extends Dialog {
|
|||
// Abilities which use Recharge
|
||||
if ( !!recharge.value ) {
|
||||
return game.i18n.format(recharge.charged ? "SW5E.AbilityUseChargedHint" : "SW5E.AbilityUseRechargeHint", {
|
||||
type: item.type,
|
||||
type: game.i18n.localize(`SW5E.ItemType${item.type.capitalize()}`),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -165,7 +165,7 @@ export default class AbilityUseDialog extends Dialog {
|
|||
else if ( item.data.quantity === 1 && uses.autoDestroy ) str = "SW5E.AbilityUseConsumableDestroyHint";
|
||||
else if ( item.data.quantity > 1 ) str = "SW5E.AbilityUseConsumableQuantityHint";
|
||||
return game.i18n.format(str, {
|
||||
type: item.data.consumableType,
|
||||
type: game.i18n.localize(`SW5E.Consumable${item.data.consumableType.capitalize()}`),
|
||||
value: uses.value,
|
||||
quantity: item.data.quantity,
|
||||
max: uses.max,
|
||||
|
@ -176,17 +176,11 @@ export default class AbilityUseDialog extends Dialog {
|
|||
// Other Items
|
||||
else {
|
||||
return game.i18n.format("SW5E.AbilityUseNormalHint", {
|
||||
type: item.type,
|
||||
type: game.i18n.localize(`SW5E.ItemType${item.type.capitalize()}`),
|
||||
value: uses.value,
|
||||
max: uses.max,
|
||||
per: CONFIG.SW5E.limitedUsePeriods[uses.per]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static _handleSubmit(formData, item) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
/**
|
||||
* An application class which provides advanced configuration for special character flags which modify an Actor
|
||||
* @implements {BaseEntitySheet}
|
||||
* @implements {DocumentSheet}
|
||||
*/
|
||||
export default class ActorSheetFlags extends BaseEntitySheet {
|
||||
export default class ActorSheetFlags extends DocumentSheet {
|
||||
static get defaultOptions() {
|
||||
const options = super.defaultOptions;
|
||||
return mergeObject(options, {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
id: "actor-flags",
|
||||
classes: ["sw5e"],
|
||||
template: "systems/sw5e/templates/apps/actor-flags.html",
|
||||
|
@ -27,6 +26,7 @@ export default class ActorSheetFlags extends BaseEntitySheet {
|
|||
getData() {
|
||||
const data = {};
|
||||
data.actor = this.object;
|
||||
data.classes = this._getClasses();
|
||||
data.flags = this._getFlags();
|
||||
data.bonuses = this._getBonuses();
|
||||
return data;
|
||||
|
@ -34,17 +34,33 @@ export default class ActorSheetFlags extends BaseEntitySheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Prepare an object of sorted classes.
|
||||
* @return {object}
|
||||
* @private
|
||||
*/
|
||||
_getClasses() {
|
||||
const classes = this.object.items.filter(i => i.type === "class");
|
||||
return classes.sort((a, b) => a.name.localeCompare(b.name)).reduce((obj, i) => {
|
||||
obj[i.id] = i.name;
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Prepare an object of flags data which groups flags by section
|
||||
* Add some additional data for rendering
|
||||
* @return {object}
|
||||
* @private
|
||||
*/
|
||||
_getFlags() {
|
||||
const flags = {};
|
||||
const baseData = this.entity._data;
|
||||
const baseData = this.document.toJSON();
|
||||
for ( let [k, v] of Object.entries(CONFIG.SW5E.characterFlags) ) {
|
||||
if ( !flags.hasOwnProperty(v.section) ) flags[v.section] = {};
|
||||
let flag = duplicate(v);
|
||||
let flag = foundry.utils.deepClone(v);
|
||||
flag.type = v.type.name;
|
||||
flag.isCheckbox = v.type === Boolean;
|
||||
flag.isSelect = v.hasOwnProperty('choices');
|
||||
|
|
110
module/apps/actor-type.js
Normal file
110
module/apps/actor-type.js
Normal file
|
@ -0,0 +1,110 @@
|
|||
import Actor5e from "../actor/entity.js";
|
||||
|
||||
/**
|
||||
* A specialized form used to select from a checklist of attributes, traits, or properties
|
||||
* @extends {FormApplication}
|
||||
*/
|
||||
export default class ActorTypeConfig extends FormApplication {
|
||||
|
||||
/** @inheritdoc */
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e", "actor-type", "trait-selector"],
|
||||
template: "systems/sw5e/templates/apps/actor-type.html",
|
||||
title: "SW5E.CreatureTypeTitle",
|
||||
width: 280,
|
||||
height: "auto",
|
||||
choices: {},
|
||||
allowCustom: true,
|
||||
minimum: 0,
|
||||
maximum: null
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
get id() {
|
||||
return `actor-type-${this.object.id}`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData(options) {
|
||||
|
||||
// Get current value or new default
|
||||
let attr = foundry.utils.getProperty(this.object.data.data, 'details.type');
|
||||
if ( foundry.utils.getType(attr) !== "Object" ) attr = {
|
||||
value: (attr in CONFIG.SW5E.creatureTypes) ? attr : "humanoid",
|
||||
subtype: "",
|
||||
swarm: "",
|
||||
custom: ""
|
||||
};
|
||||
|
||||
// Populate choices
|
||||
const types = {};
|
||||
for ( let [k, v] of Object.entries(CONFIG.SW5E.creatureTypes) ) {
|
||||
types[k] = {
|
||||
label: game.i18n.localize(v),
|
||||
chosen: attr.value === k
|
||||
}
|
||||
}
|
||||
|
||||
// Return data for rendering
|
||||
return {
|
||||
types: types,
|
||||
custom: {
|
||||
value: attr.custom,
|
||||
label: game.i18n.localize("SW5E.CreatureTypeSelectorCustom"),
|
||||
chosen: attr.value === "custom"
|
||||
},
|
||||
subtype: attr.subtype,
|
||||
swarm: attr.swarm,
|
||||
sizes: Array.from(Object.entries(CONFIG.SW5E.actorSizes)).reverse().reduce((obj, e) => {
|
||||
obj[e[0]] = e[1];
|
||||
return obj;
|
||||
}, {}),
|
||||
preview: Actor5e.formatCreatureType(attr) || "–"
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
async _updateObject(event, formData) {
|
||||
const typeObject = foundry.utils.expandObject(formData);
|
||||
return this.object.update({ 'data.details.type': typeObject });
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Event Listeners and Handlers */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritdoc */
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
html.find("input[name='custom']").focusin(this._onCustomFieldFocused.bind(this));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritdoc */
|
||||
_onChangeInput(event) {
|
||||
super._onChangeInput(event);
|
||||
const typeObject = foundry.utils.expandObject(this._getSubmitData());
|
||||
this.form["preview"].value = Actor5e.formatCreatureType(typeObject) || "—";
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Select the custom radio button when the custom text field is focused.
|
||||
* @param {FocusEvent} event The original focusin event
|
||||
* @private
|
||||
*/
|
||||
_onCustomFieldFocused(event) {
|
||||
this.form.querySelector("input[name='value'][value='custom']").checked = true;
|
||||
this._onChangeInput(event);
|
||||
}
|
||||
}
|
91
module/apps/hit-dice-config.js
Normal file
91
module/apps/hit-dice-config.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* A simple form to set actor hit dice amounts
|
||||
* @implements {DocumentSheet}
|
||||
*/
|
||||
export default class ActorHitDiceConfig extends DocumentSheet {
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e", "hd-config", "dialog"],
|
||||
template: "systems/sw5e/templates/apps/hit-dice-config.html",
|
||||
width: 360,
|
||||
height: "auto"
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
get title() {
|
||||
return `${game.i18n.localize("SW5E.HitDiceConfig")}: ${this.object.name}`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData(options) {
|
||||
return {
|
||||
classes: this.object.items.reduce((classes, item) => {
|
||||
if (item.data.type === "class") {
|
||||
// Add the appropriate data only if this item is a "class"
|
||||
classes.push({
|
||||
classItemId: item.data._id,
|
||||
name: item.data.name,
|
||||
diceDenom: item.data.data.hitDice,
|
||||
currentHitDice: item.data.data.levels - item.data.data.hitDiceUsed,
|
||||
maxHitDice: item.data.data.levels,
|
||||
canRoll: (item.data.data.levels - item.data.data.hitDiceUsed) > 0
|
||||
});
|
||||
}
|
||||
return classes;
|
||||
}, []).sort((a, b) => parseInt(b.diceDenom.slice(1)) - parseInt(a.diceDenom.slice(1)))
|
||||
};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
|
||||
// Hook up -/+ buttons to adjust the current value in the form
|
||||
html.find("button.increment,button.decrement").click(event => {
|
||||
const button = event.currentTarget;
|
||||
const current = button.parentElement.querySelector(".current");
|
||||
const max = button.parentElement.querySelector(".max");
|
||||
const direction = button.classList.contains("increment") ? 1 : -1;
|
||||
current.value = Math.clamped(parseInt(current.value) + direction, 0, parseInt(max.value));
|
||||
});
|
||||
|
||||
html.find("button.roll-hd").click(this._onRollHitDie.bind(this));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
async _updateObject(event, formData) {
|
||||
const actorItems = this.object.items;
|
||||
const classUpdates = Object.entries(formData).map(([id, hd]) => ({
|
||||
_id: id,
|
||||
"data.hitDiceUsed": actorItems.get(id).data.data.levels - hd,
|
||||
}));
|
||||
return this.object.updateEmbeddedDocuments("Item", classUpdates);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Rolls the hit die corresponding with the class row containing the event's target button.
|
||||
* @param {MouseEvent} event
|
||||
* @private
|
||||
*/
|
||||
async _onRollHitDie(event) {
|
||||
event.preventDefault();
|
||||
const button = event.currentTarget;
|
||||
await this.object.rollHitDie(button.dataset.hdDenom);
|
||||
|
||||
// Re-render dialog to reflect changed hit dice quantities
|
||||
this.render();
|
||||
}
|
||||
}
|
|
@ -40,23 +40,21 @@ export default class LongRestDialog extends Dialog {
|
|||
static async longRestDialog({ actor } = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const dlg = new this(actor, {
|
||||
title: "Long Rest",
|
||||
title: game.i18n.localize("SW5E.LongRest"),
|
||||
buttons: {
|
||||
rest: {
|
||||
icon: '<i class="fas fa-bed"></i>',
|
||||
label: "Rest",
|
||||
label: game.i18n.localize("SW5E.Rest"),
|
||||
callback: html => {
|
||||
let newDay = false;
|
||||
if (game.settings.get("sw5e", "restVariant") === "normal")
|
||||
let newDay = true;
|
||||
if (game.settings.get("sw5e", "restVariant") !== "gritty")
|
||||
newDay = html.find('input[name="newDay"]')[0].checked;
|
||||
else if(game.settings.get("sw5e", "restVariant") === "gritty")
|
||||
newDay = true;
|
||||
resolve(newDay);
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-times"></i>',
|
||||
label: "Cancel",
|
||||
label: game.i18n.localize("Cancel"),
|
||||
callback: reject
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
/**
|
||||
* A simple form to set actor movement speeds
|
||||
* @implements {BaseEntitySheet}
|
||||
* @extends {DocumentSheet}
|
||||
*/
|
||||
export default class ActorMovementConfig extends BaseEntitySheet {
|
||||
export default class ActorMovementConfig extends DocumentSheet {
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e"],
|
||||
template: "systems/sw5e/templates/apps/movement-config.html",
|
||||
width: 300,
|
||||
|
@ -18,17 +18,18 @@ export default class ActorMovementConfig extends BaseEntitySheet {
|
|||
|
||||
/** @override */
|
||||
get title() {
|
||||
return `${game.i18n.localize("SW5E.MovementConfig")}: ${this.entity.name}`;
|
||||
return `${game.i18n.localize("SW5E.MovementConfig")}: ${this.document.name}`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData(options) {
|
||||
const sourceMovement = foundry.utils.getProperty(this.document.data._source, "data.attributes.movement") || {};
|
||||
const data = {
|
||||
movement: duplicate(this.entity._data.data.attributes.movement),
|
||||
movement: foundry.utils.deepClone(sourceMovement),
|
||||
units: CONFIG.SW5E.movementUnits
|
||||
}
|
||||
};
|
||||
for ( let [k, v] of Object.entries(data.movement) ) {
|
||||
if ( ["units", "hover"].includes(k) ) continue;
|
||||
data.movement[k] = Number.isNumeric(v) ? v.toNearest(0.1) : 0;
|
||||
|
|
68
module/apps/select-items-prompt.js
Normal file
68
module/apps/select-items-prompt.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* A Dialog to prompt the user to select from a list of items.
|
||||
* @type {Dialog}
|
||||
*/
|
||||
export default class SelectItemsPrompt extends Dialog {
|
||||
constructor(items, dialogData={}, options={}) {
|
||||
super(dialogData, options);
|
||||
this.options.classes = ["sw5e", "dialog", "select-items-prompt", "sheet"];
|
||||
|
||||
/**
|
||||
* Store a reference to the Item entities being used
|
||||
* @type {Array<Item5e>}
|
||||
*/
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
|
||||
// render the item's sheet if its image is clicked
|
||||
html.on('click', '.item-image', (event) => {
|
||||
const item = this.items.find((feature) => feature.id === event.currentTarget.dataset?.itemId);
|
||||
|
||||
item?.sheet.render(true);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructor function which displays the AddItemPrompt app for a given Actor and Item set.
|
||||
* Returns a Promise which resolves to the dialog FormData once the workflow has been completed.
|
||||
* @param {Array<Item5e>} items
|
||||
* @param {Object} options
|
||||
* @param {string} options.hint - Localized hint to display at the top of the prompt
|
||||
* @return {Promise<string[]>} - list of item ids which the user has selected
|
||||
*/
|
||||
static async create(items, {
|
||||
hint
|
||||
}) {
|
||||
// Render the ability usage template
|
||||
const html = await renderTemplate("systems/sw5e/templates/apps/select-items-prompt.html", {items, hint});
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const dlg = new this(items, {
|
||||
title: game.i18n.localize('SW5E.SelectItemsPromptTitle'),
|
||||
content: html,
|
||||
buttons: {
|
||||
apply: {
|
||||
icon: `<i class="fas fa-user-plus"></i>`,
|
||||
label: game.i18n.localize('SW5E.Apply'),
|
||||
callback: html => {
|
||||
const fd = new FormDataExtended(html[0].querySelector("form")).toObject();
|
||||
const selectedIds = Object.keys(fd).filter(itemId => fd[itemId]);
|
||||
resolve(selectedIds);
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-forward"></i>',
|
||||
label: game.i18n.localize('SW5E.Skip'),
|
||||
callback: () => resolve([])
|
||||
}
|
||||
},
|
||||
default: "apply",
|
||||
close: () => resolve([])
|
||||
});
|
||||
dlg.render(true);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
/**
|
||||
* A simple form to set actor movement speeds
|
||||
* @implements {BaseEntitySheet}
|
||||
* A simple form to set Actor movement speeds.
|
||||
* @extends {DocumentSheet}
|
||||
*/
|
||||
export default class ActorSensesConfig extends BaseEntitySheet {
|
||||
export default class ActorSensesConfig extends DocumentSheet {
|
||||
|
||||
/** @override */
|
||||
/** @inheritdoc */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e"],
|
||||
template: "systems/sw5e/templates/apps/senses-config.html",
|
||||
width: 300,
|
||||
|
@ -16,16 +16,16 @@ export default class ActorSensesConfig extends BaseEntitySheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
/** @inheritdoc */
|
||||
get title() {
|
||||
return `${game.i18n.localize("SW5E.SensesConfig")}: ${this.entity.name}`;
|
||||
return `${game.i18n.localize("SW5E.SensesConfig")}: ${this.document.name}`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
/** @inheritdoc */
|
||||
getData(options) {
|
||||
const senses = this.entity._data.data.attributes?.senses ?? {};
|
||||
const senses = foundry.utils.getProperty(this.document.data._source, "data.attributes.senses") || {};
|
||||
const data = {
|
||||
senses: {},
|
||||
special: senses.special ?? "",
|
||||
|
|
|
@ -40,7 +40,7 @@ export default class ShortRestDialog extends Dialog {
|
|||
// Determine Hit Dice
|
||||
data.availableHD = this.actor.data.items.reduce((hd, item) => {
|
||||
if ( item.type === "class" ) {
|
||||
const d = item.data;
|
||||
const d = item.data.data;
|
||||
const denom = d.hitDice || "d6";
|
||||
const available = parseInt(d.levels || 1) - parseInt(d.hitDiceUsed || 0);
|
||||
hd[denom] = denom in hd ? hd[denom] + available : available;
|
||||
|
@ -93,11 +93,11 @@ export default class ShortRestDialog extends Dialog {
|
|||
static async shortRestDialog({actor}={}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const dlg = new this(actor, {
|
||||
title: "Short Rest",
|
||||
title: game.i18n.localize("SW5E.ShortRest"),
|
||||
buttons: {
|
||||
rest: {
|
||||
icon: '<i class="fas fa-bed"></i>',
|
||||
label: "Rest",
|
||||
label: game.i18n.localize("SW5E.Rest"),
|
||||
callback: html => {
|
||||
let newDay = false;
|
||||
if (game.settings.get("sw5e", "restVariant") === "gritty")
|
||||
|
@ -107,7 +107,7 @@ export default class ShortRestDialog extends Dialog {
|
|||
},
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-times"></i>',
|
||||
label: "Cancel",
|
||||
label: game.i18n.localize("Cancel"),
|
||||
callback: reject
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
/**
|
||||
* A specialized form used to select from a checklist of attributes, traits, or properties
|
||||
* @implements {FormApplication}
|
||||
* @extends {DocumentSheet}
|
||||
*/
|
||||
export default class TraitSelector extends FormApplication {
|
||||
export default class TraitSelector extends DocumentSheet {
|
||||
|
||||
/** @override */
|
||||
/** @inheritdoc */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
id: "trait-selector",
|
||||
classes: ["sw5e"],
|
||||
classes: ["sw5e", "trait-selector", "subconfig"],
|
||||
title: "Actor Trait Selection",
|
||||
template: "systems/sw5e/templates/apps/trait-selector.html",
|
||||
width: 320,
|
||||
|
@ -16,7 +16,9 @@ export default class TraitSelector extends FormApplication {
|
|||
choices: {},
|
||||
allowCustom: true,
|
||||
minimum: 0,
|
||||
maximum: null
|
||||
maximum: null,
|
||||
valueKey: "value",
|
||||
customKey: "custom"
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -24,7 +26,7 @@ export default class TraitSelector extends FormApplication {
|
|||
|
||||
/**
|
||||
* Return a reference to the target attribute
|
||||
* @type {String}
|
||||
* @type {string}
|
||||
*/
|
||||
get attribute() {
|
||||
return this.options.name;
|
||||
|
@ -34,52 +36,50 @@ export default class TraitSelector extends FormApplication {
|
|||
|
||||
/** @override */
|
||||
getData() {
|
||||
|
||||
// Get current values
|
||||
let attr = getProperty(this.object._data, this.attribute);
|
||||
if ( getType(attr) !== "Object" ) attr = {value: [], custom: ""};
|
||||
const attr = foundry.utils.getProperty(this.object.data, this.attribute);
|
||||
const o = this.options;
|
||||
const value = (o.valueKey) ? attr[o.valueKey] ?? [] : attr;
|
||||
const custom = (o.customKey) ? attr[o.customKey] ?? "" : "";
|
||||
|
||||
// Populate choices
|
||||
const choices = duplicate(this.options.choices);
|
||||
for ( let [k, v] of Object.entries(choices) ) {
|
||||
choices[k] = {
|
||||
label: v,
|
||||
chosen: attr ? attr.value.includes(k) : false
|
||||
}
|
||||
}
|
||||
const choices = Object.entries(o.choices).reduce((obj, e) => {
|
||||
let [k, v] = e;
|
||||
obj[k] = { label: v, chosen: attr ? value.includes(k) : false };
|
||||
return obj;
|
||||
}, {})
|
||||
|
||||
// Return data
|
||||
return {
|
||||
allowCustom: this.options.allowCustom,
|
||||
allowCustom: o.allowCustom,
|
||||
choices: choices,
|
||||
custom: attr ? attr.custom : ""
|
||||
custom: custom
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_updateObject(event, formData) {
|
||||
const updateData = {};
|
||||
async _updateObject(event, formData) {
|
||||
const o = this.options;
|
||||
|
||||
// Obtain choices
|
||||
const chosen = [];
|
||||
for ( let [k, v] of Object.entries(formData) ) {
|
||||
if ( (k !== "custom") && v ) chosen.push(k);
|
||||
}
|
||||
updateData[`${this.attribute}.value`] = chosen;
|
||||
|
||||
// Object including custom data
|
||||
const updateData = {};
|
||||
if ( o.valueKey ) updateData[`${this.attribute}.${o.valueKey}`] = chosen;
|
||||
else updateData[this.attribute] = chosen;
|
||||
if ( o.allowCustom ) updateData[`${this.attribute}.${o.customKey}`] = formData.custom;
|
||||
|
||||
// Validate the number chosen
|
||||
if ( this.options.minimum && (chosen.length < this.options.minimum) ) {
|
||||
return ui.notifications.error(`You must choose at least ${this.options.minimum} options`);
|
||||
if ( o.minimum && (chosen.length < o.minimum) ) {
|
||||
return ui.notifications.error(`You must choose at least ${o.minimum} options`);
|
||||
}
|
||||
if ( this.options.maximum && (chosen.length > this.options.maximum) ) {
|
||||
return ui.notifications.error(`You may choose no more than ${this.options.maximum} options`);
|
||||
}
|
||||
|
||||
// Include custom
|
||||
if ( this.options.allowCustom ) {
|
||||
updateData[`${this.attribute}.custom`] = formData.custom;
|
||||
if ( o.maximum && (chosen.length > o.maximum) ) {
|
||||
return ui.notifications.error(`You may choose no more than ${o.maximum} options`);
|
||||
}
|
||||
|
||||
// Update the object
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue