forked from GitHub-Mirrors/foundry-sw5e
Formatted js files
This commit is contained in:
parent
d1b123100e
commit
584767b352
41 changed files with 13450 additions and 12704 deletions
|
@ -3,220 +3,225 @@
|
|||
* @type {Dialog}
|
||||
*/
|
||||
export default class AbilityUseDialog extends Dialog {
|
||||
constructor(item, dialogData={}, options={}) {
|
||||
super(dialogData, options);
|
||||
this.options.classes = ["sw5e", "dialog"];
|
||||
constructor(item, dialogData = {}, options = {}) {
|
||||
super(dialogData, options);
|
||||
this.options.classes = ["sw5e", "dialog"];
|
||||
|
||||
/**
|
||||
* Store a reference to the Item entity being used
|
||||
* @type {Item5e}
|
||||
*/
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Rendering */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Store a reference to the Item entity being used
|
||||
* @type {Item5e}
|
||||
* A constructor function which displays the Power Cast Dialog app for a given Actor and Item.
|
||||
* Returns a Promise which resolves to the dialog FormData once the workflow has been completed.
|
||||
* @param {Item5e} item
|
||||
* @return {Promise}
|
||||
*/
|
||||
this.item = item;
|
||||
}
|
||||
static async create(item) {
|
||||
if (!item.isOwned) throw new Error("You cannot display an ability usage dialog for an unowned item");
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Rendering */
|
||||
/* -------------------------------------------- */
|
||||
// Prepare data
|
||||
const actorData = item.actor.data.data;
|
||||
const itemData = item.data.data;
|
||||
const uses = itemData.uses || {};
|
||||
const quantity = itemData.quantity || 0;
|
||||
const recharge = itemData.recharge || {};
|
||||
const recharges = !!recharge.value;
|
||||
const sufficientUses = (quantity > 0 && !uses.value) || uses.value > 0;
|
||||
|
||||
/**
|
||||
* A constructor function which displays the Power Cast Dialog app for a given Actor and Item.
|
||||
* Returns a Promise which resolves to the dialog FormData once the workflow has been completed.
|
||||
* @param {Item5e} item
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async create(item) {
|
||||
if ( !item.isOwned ) throw new Error("You cannot display an ability usage dialog for an unowned item");
|
||||
// Prepare dialog form data
|
||||
const data = {
|
||||
item: 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.per && uses.max > 0,
|
||||
canUse: recharges ? recharge.charged : sufficientUses,
|
||||
createTemplate: game.user.can("TEMPLATE_CREATE") && item.hasAreaTarget,
|
||||
errors: []
|
||||
};
|
||||
if (item.data.type === "power") this._getPowerData(actorData, itemData, data);
|
||||
|
||||
// Prepare data
|
||||
const actorData = item.actor.data.data;
|
||||
const itemData = item.data.data;
|
||||
const uses = itemData.uses || {};
|
||||
const quantity = itemData.quantity || 0;
|
||||
const recharge = itemData.recharge || {};
|
||||
const recharges = !!recharge.value;
|
||||
const sufficientUses = (quantity > 0 && !uses.value) || uses.value > 0;
|
||||
// Render the ability usage template
|
||||
const html = await renderTemplate("systems/sw5e/templates/apps/ability-use.html", data);
|
||||
|
||||
// Prepare dialog form data
|
||||
const data = {
|
||||
item: 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.per && (uses.max > 0),
|
||||
canUse: recharges ? recharge.charged : sufficientUses,
|
||||
createTemplate: game.user.can("TEMPLATE_CREATE") && item.hasAreaTarget,
|
||||
errors: []
|
||||
};
|
||||
if ( item.data.type === "power" ) this._getPowerData(actorData, itemData, data);
|
||||
// Create the Dialog and return data as a Promise
|
||||
const icon = data.isPower ? "fa-magic" : "fa-fist-raised";
|
||||
const label = game.i18n.localize("SW5E.AbilityUse" + (data.isPower ? "Cast" : "Use"));
|
||||
return new Promise((resolve) => {
|
||||
const dlg = new this(item, {
|
||||
title: `${item.name}: ${game.i18n.localize("SW5E.AbilityUseConfig")}`,
|
||||
content: html,
|
||||
buttons: {
|
||||
use: {
|
||||
icon: `<i class="fas ${icon}"></i>`,
|
||||
label: label,
|
||||
callback: (html) => {
|
||||
const fd = new FormDataExtended(html[0].querySelector("form"));
|
||||
resolve(fd.toObject());
|
||||
}
|
||||
}
|
||||
},
|
||||
default: "use",
|
||||
close: () => resolve(null)
|
||||
});
|
||||
dlg.render(true);
|
||||
});
|
||||
}
|
||||
|
||||
// Render the ability usage template
|
||||
const html = await renderTemplate("systems/sw5e/templates/apps/ability-use.html", data);
|
||||
/* -------------------------------------------- */
|
||||
/* Helpers */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
// Create the Dialog and return data as a Promise
|
||||
const icon = data.isPower ? "fa-magic" : "fa-fist-raised";
|
||||
const label = game.i18n.localize("SW5E.AbilityUse" + (data.isPower ? "Cast" : "Use"));
|
||||
return new Promise((resolve) => {
|
||||
const dlg = new this(item, {
|
||||
title: `${item.name}: ${game.i18n.localize("SW5E.AbilityUseConfig")}`,
|
||||
content: html,
|
||||
buttons: {
|
||||
use: {
|
||||
icon: `<i class="fas ${icon}"></i>`,
|
||||
label: label,
|
||||
callback: html => {
|
||||
const fd = new FormDataExtended(html[0].querySelector("form"));
|
||||
resolve(fd.toObject());
|
||||
/**
|
||||
* Get dialog data related to limited power slots
|
||||
* @private
|
||||
*/
|
||||
static _getPowerData(actorData, itemData, data) {
|
||||
// Determine whether the power may be up-cast
|
||||
const lvl = itemData.level;
|
||||
const consumePowerSlot = lvl > 0 && CONFIG.SW5E.powerUpcastModes.includes(itemData.preparation.mode);
|
||||
|
||||
// If can't upcast, return early and don't bother calculating available power slots
|
||||
if (!consumePowerSlot) {
|
||||
mergeObject(data, {isPower: true, consumePowerSlot});
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the levels which are feasible
|
||||
let lmax = 0;
|
||||
let points;
|
||||
let powerType;
|
||||
switch (itemData.school) {
|
||||
case "lgt":
|
||||
case "uni":
|
||||
case "drk": {
|
||||
powerType = "force";
|
||||
points = actorData.attributes.force.points.value + actorData.attributes.force.points.temp;
|
||||
break;
|
||||
}
|
||||
case "tec": {
|
||||
powerType = "tech";
|
||||
points = actorData.attributes.tech.points.value + actorData.attributes.tech.points.temp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
default: "use",
|
||||
close: () => resolve(null)
|
||||
});
|
||||
dlg.render(true);
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Helpers */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Get dialog data related to limited power slots
|
||||
* @private
|
||||
*/
|
||||
static _getPowerData(actorData, itemData, data) {
|
||||
|
||||
// Determine whether the power may be up-cast
|
||||
const lvl = itemData.level;
|
||||
const consumePowerSlot = (lvl > 0) && CONFIG.SW5E.powerUpcastModes.includes(itemData.preparation.mode);
|
||||
|
||||
// If can't upcast, return early and don't bother calculating available power slots
|
||||
if (!consumePowerSlot) {
|
||||
mergeObject(data, { isPower: true, consumePowerSlot });
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the levels which are feasible
|
||||
let lmax = 0;
|
||||
let points;
|
||||
let powerType;
|
||||
switch (itemData.school){
|
||||
case "lgt":
|
||||
case "uni":
|
||||
case "drk": {
|
||||
powerType = "force"
|
||||
points = actorData.attributes.force.points.value + actorData.attributes.force.points.temp;
|
||||
break;
|
||||
}
|
||||
case "tec": {
|
||||
powerType = "tech"
|
||||
points = actorData.attributes.tech.points.value + actorData.attributes.tech.points.temp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// eliminate point usage for innate casters
|
||||
if (actorData.attributes.powercasting === 'innate') points = 999;
|
||||
|
||||
|
||||
let powerLevels
|
||||
if (powerType === "force"){
|
||||
powerLevels = Array.fromRange(10).reduce((arr, i) => {
|
||||
if ( i < lvl ) return arr;
|
||||
const label = CONFIG.SW5E.powerLevels[i];
|
||||
const l = actorData.powers["power"+i] || {fmax: 0, foverride: null};
|
||||
let max = parseInt(l.foverride || l.fmax || 0);
|
||||
let slots = Math.clamped(parseInt(l.fvalue || 0), 0, max);
|
||||
if ( max > 0 ) lmax = i;
|
||||
if ((max > 0) && (slots > 0) && (points > i)){
|
||||
arr.push({
|
||||
level: i,
|
||||
label: i > 0 ? game.i18n.format('SW5E.PowerLevelSlot', {level: label, n: slots}) : label,
|
||||
canCast: max > 0,
|
||||
hasSlots: slots > 0
|
||||
});
|
||||
}
|
||||
return arr;
|
||||
}, []).filter(sl => sl.level <= lmax);
|
||||
}else if (powerType === "tech"){
|
||||
powerLevels = Array.fromRange(10).reduce((arr, i) => {
|
||||
if ( i < lvl ) return arr;
|
||||
const label = CONFIG.SW5E.powerLevels[i];
|
||||
const l = actorData.powers["power"+i] || {tmax: 0, toverride: null};
|
||||
let max = parseInt(l.override || l.tmax || 0);
|
||||
let slots = Math.clamped(parseInt(l.tvalue || 0), 0, max);
|
||||
if ( max > 0 ) lmax = i;
|
||||
if ((max > 0) && (slots > 0) && (points > i)){
|
||||
arr.push({
|
||||
level: i,
|
||||
label: i > 0 ? game.i18n.format('SW5E.PowerLevelSlot', {level: label, n: slots}) : label,
|
||||
canCast: max > 0,
|
||||
hasSlots: slots > 0
|
||||
});
|
||||
|
||||
// eliminate point usage for innate casters
|
||||
if (actorData.attributes.powercasting === "innate") points = 999;
|
||||
|
||||
let powerLevels;
|
||||
if (powerType === "force") {
|
||||
powerLevels = Array.fromRange(10)
|
||||
.reduce((arr, i) => {
|
||||
if (i < lvl) return arr;
|
||||
const label = CONFIG.SW5E.powerLevels[i];
|
||||
const l = actorData.powers["power" + i] || {fmax: 0, foverride: null};
|
||||
let max = parseInt(l.foverride || l.fmax || 0);
|
||||
let slots = Math.clamped(parseInt(l.fvalue || 0), 0, max);
|
||||
if (max > 0) lmax = i;
|
||||
if (max > 0 && slots > 0 && points > i) {
|
||||
arr.push({
|
||||
level: i,
|
||||
label: i > 0 ? game.i18n.format("SW5E.PowerLevelSlot", {level: label, n: slots}) : label,
|
||||
canCast: max > 0,
|
||||
hasSlots: slots > 0
|
||||
});
|
||||
}
|
||||
return arr;
|
||||
}, [])
|
||||
.filter((sl) => sl.level <= lmax);
|
||||
} else if (powerType === "tech") {
|
||||
powerLevels = Array.fromRange(10)
|
||||
.reduce((arr, i) => {
|
||||
if (i < lvl) return arr;
|
||||
const label = CONFIG.SW5E.powerLevels[i];
|
||||
const l = actorData.powers["power" + i] || {tmax: 0, toverride: null};
|
||||
let max = parseInt(l.override || l.tmax || 0);
|
||||
let slots = Math.clamped(parseInt(l.tvalue || 0), 0, max);
|
||||
if (max > 0) lmax = i;
|
||||
if (max > 0 && slots > 0 && points > i) {
|
||||
arr.push({
|
||||
level: i,
|
||||
label: i > 0 ? game.i18n.format("SW5E.PowerLevelSlot", {level: label, n: slots}) : label,
|
||||
canCast: max > 0,
|
||||
hasSlots: slots > 0
|
||||
});
|
||||
}
|
||||
return arr;
|
||||
}, [])
|
||||
.filter((sl) => sl.level <= lmax);
|
||||
}
|
||||
return arr;
|
||||
}, []).filter(sl => sl.level <= lmax);
|
||||
}
|
||||
|
||||
|
||||
|
||||
const canCast = powerLevels.some(l => l.hasSlots);
|
||||
if ( !canCast ) data.errors.push(game.i18n.format("SW5E.PowerCastNoSlots", {
|
||||
level: CONFIG.SW5E.powerLevels[lvl],
|
||||
name: data.item.name
|
||||
}));
|
||||
const canCast = powerLevels.some((l) => l.hasSlots);
|
||||
if (!canCast)
|
||||
data.errors.push(
|
||||
game.i18n.format("SW5E.PowerCastNoSlots", {
|
||||
level: CONFIG.SW5E.powerLevels[lvl],
|
||||
name: data.item.name
|
||||
})
|
||||
);
|
||||
|
||||
// Merge power casting data
|
||||
return foundry.utils.mergeObject(data, { isPower: true, consumePowerSlot, powerLevels });
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Get the ability usage note that is displayed
|
||||
* @private
|
||||
*/
|
||||
static _getAbilityUseNote(item, uses, recharge) {
|
||||
|
||||
// Zero quantity
|
||||
const quantity = item.data.quantity;
|
||||
if ( quantity <= 0 ) return game.i18n.localize("SW5E.AbilityUseUnavailableHint");
|
||||
|
||||
// Abilities which use Recharge
|
||||
if ( !!recharge.value ) {
|
||||
return game.i18n.format(recharge.charged ? "SW5E.AbilityUseChargedHint" : "SW5E.AbilityUseRechargeHint", {
|
||||
type: game.i18n.localize(`SW5E.ItemType${item.type.capitalize()}`),
|
||||
})
|
||||
// Merge power casting data
|
||||
return foundry.utils.mergeObject(data, {isPower: true, consumePowerSlot, powerLevels});
|
||||
}
|
||||
|
||||
// Does not use any resource
|
||||
if ( !uses.per || !uses.max ) return "";
|
||||
/* -------------------------------------------- */
|
||||
|
||||
// Consumables
|
||||
if ( item.type === "consumable" ) {
|
||||
let str = "SW5E.AbilityUseNormalHint";
|
||||
if ( uses.value > 1 ) str = "SW5E.AbilityUseConsumableChargeHint";
|
||||
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: game.i18n.localize(`SW5E.Consumable${item.data.consumableType.capitalize()}`),
|
||||
value: uses.value,
|
||||
quantity: item.data.quantity,
|
||||
max: uses.max,
|
||||
per: CONFIG.SW5E.limitedUsePeriods[uses.per]
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get the ability usage note that is displayed
|
||||
* @private
|
||||
*/
|
||||
static _getAbilityUseNote(item, uses, recharge) {
|
||||
// Zero quantity
|
||||
const quantity = item.data.quantity;
|
||||
if (quantity <= 0) return game.i18n.localize("SW5E.AbilityUseUnavailableHint");
|
||||
|
||||
// Other Items
|
||||
else {
|
||||
return game.i18n.format("SW5E.AbilityUseNormalHint", {
|
||||
type: game.i18n.localize(`SW5E.ItemType${item.type.capitalize()}`),
|
||||
value: uses.value,
|
||||
max: uses.max,
|
||||
per: CONFIG.SW5E.limitedUsePeriods[uses.per]
|
||||
});
|
||||
// Abilities which use Recharge
|
||||
if (!!recharge.value) {
|
||||
return game.i18n.format(recharge.charged ? "SW5E.AbilityUseChargedHint" : "SW5E.AbilityUseRechargeHint", {
|
||||
type: game.i18n.localize(`SW5E.ItemType${item.type.capitalize()}`)
|
||||
});
|
||||
}
|
||||
|
||||
// Does not use any resource
|
||||
if (!uses.per || !uses.max) return "";
|
||||
|
||||
// Consumables
|
||||
if (item.type === "consumable") {
|
||||
let str = "SW5E.AbilityUseNormalHint";
|
||||
if (uses.value > 1) str = "SW5E.AbilityUseConsumableChargeHint";
|
||||
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: game.i18n.localize(`SW5E.Consumable${item.data.consumableType.capitalize()}`),
|
||||
value: uses.value,
|
||||
quantity: item.data.quantity,
|
||||
max: uses.max,
|
||||
per: CONFIG.SW5E.limitedUsePeriods[uses.per]
|
||||
});
|
||||
}
|
||||
|
||||
// Other Items
|
||||
else {
|
||||
return game.i18n.format("SW5E.AbilityUseNormalHint", {
|
||||
type: game.i18n.localize(`SW5E.ItemType${item.type.capitalize()}`),
|
||||
value: uses.value,
|
||||
max: uses.max,
|
||||
per: CONFIG.SW5E.limitedUsePeriods[uses.per]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,135 +3,137 @@
|
|||
* @implements {DocumentSheet}
|
||||
*/
|
||||
export default class ActorSheetFlags extends DocumentSheet {
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
id: "actor-flags",
|
||||
classes: ["sw5e"],
|
||||
template: "systems/sw5e/templates/apps/actor-flags.html",
|
||||
width: 500,
|
||||
closeOnSubmit: true
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
get title() {
|
||||
return `${game.i18n.localize('SW5E.FlagsTitle')}: ${this.object.name}`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
const data = {};
|
||||
data.actor = this.object;
|
||||
data.classes = this._getClasses();
|
||||
data.flags = this._getFlags();
|
||||
data.bonuses = this._getBonuses();
|
||||
return data;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* 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.document.toJSON();
|
||||
for ( let [k, v] of Object.entries(CONFIG.SW5E.characterFlags) ) {
|
||||
if ( !flags.hasOwnProperty(v.section) ) flags[v.section] = {};
|
||||
let flag = foundry.utils.deepClone(v);
|
||||
flag.type = v.type.name;
|
||||
flag.isCheckbox = v.type === Boolean;
|
||||
flag.isSelect = v.hasOwnProperty('choices');
|
||||
flag.value = getProperty(baseData.flags, `sw5e.${k}`);
|
||||
flags[v.section][`flags.sw5e.${k}`] = flag;
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
id: "actor-flags",
|
||||
classes: ["sw5e"],
|
||||
template: "systems/sw5e/templates/apps/actor-flags.html",
|
||||
width: 500,
|
||||
closeOnSubmit: true
|
||||
});
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Get the bonuses fields and their localization strings
|
||||
* @return {Array<object>}
|
||||
* @private
|
||||
*/
|
||||
_getBonuses() {
|
||||
const bonuses = [
|
||||
{name: "data.bonuses.mwak.attack", label: "SW5E.BonusMWAttack"},
|
||||
{name: "data.bonuses.mwak.damage", label: "SW5E.BonusMWDamage"},
|
||||
{name: "data.bonuses.rwak.attack", label: "SW5E.BonusRWAttack"},
|
||||
{name: "data.bonuses.rwak.damage", label: "SW5E.BonusRWDamage"},
|
||||
{name: "data.bonuses.mpak.attack", label: "SW5E.BonusMPAttack"},
|
||||
{name: "data.bonuses.mpak.damage", label: "SW5E.BonusMPDamage"},
|
||||
{name: "data.bonuses.rpak.attack", label: "SW5E.BonusRPAttack"},
|
||||
{name: "data.bonuses.rpak.damage", label: "SW5E.BonusRPDamage"},
|
||||
{name: "data.bonuses.abilities.check", label: "SW5E.BonusAbilityCheck"},
|
||||
{name: "data.bonuses.abilities.save", label: "SW5E.BonusAbilitySave"},
|
||||
{name: "data.bonuses.abilities.skill", label: "SW5E.BonusAbilitySkill"},
|
||||
{name: "data.bonuses.power.dc", label: "SW5E.BonusPowerDC"},
|
||||
{name: "data.bonuses.power.forceLightDC", label: "SW5E.BonusForceLightPowerDC"},
|
||||
{name: "data.bonuses.power.forceDarkDC", label: "SW5E.BonusForceDarkPowerDC"},
|
||||
{name: "data.bonuses.power.forceUnivDC", label: "SW5E.BonusForceUnivPowerDC"},
|
||||
{name: "data.bonuses.power.techDC", label: "SW5E.BonusTechPowerDC"}
|
||||
];
|
||||
for ( let b of bonuses ) {
|
||||
b.value = getProperty(this.object._data, b.name) || "";
|
||||
/** @override */
|
||||
get title() {
|
||||
return `${game.i18n.localize("SW5E.FlagsTitle")}: ${this.object.name}`;
|
||||
}
|
||||
return bonuses;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
async _updateObject(event, formData) {
|
||||
const actor = this.object;
|
||||
let updateData = expandObject(formData);
|
||||
/** @override */
|
||||
getData() {
|
||||
const data = {};
|
||||
data.actor = this.object;
|
||||
data.classes = this._getClasses();
|
||||
data.flags = this._getFlags();
|
||||
data.bonuses = this._getBonuses();
|
||||
return data;
|
||||
}
|
||||
|
||||
// Unset any flags which are "false"
|
||||
let unset = false;
|
||||
const flags = updateData.flags.sw5e;
|
||||
//clone flags to dnd5e for module compatability
|
||||
updateData.flags.dnd5e = updateData.flags.sw5e
|
||||
for ( let [k, v] of Object.entries(flags) ) {
|
||||
if ( [undefined, null, "", false, 0].includes(v) ) {
|
||||
delete flags[k];
|
||||
if ( hasProperty(actor._data.flags, `sw5e.${k}`) ) {
|
||||
unset = true;
|
||||
flags[`-=${k}`] = null;
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* 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.document.toJSON();
|
||||
for (let [k, v] of Object.entries(CONFIG.SW5E.characterFlags)) {
|
||||
if (!flags.hasOwnProperty(v.section)) flags[v.section] = {};
|
||||
let flag = foundry.utils.deepClone(v);
|
||||
flag.type = v.type.name;
|
||||
flag.isCheckbox = v.type === Boolean;
|
||||
flag.isSelect = v.hasOwnProperty("choices");
|
||||
flag.value = getProperty(baseData.flags, `sw5e.${k}`);
|
||||
flags[v.section][`flags.sw5e.${k}`] = flag;
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
// Clear any bonuses which are whitespace only
|
||||
for ( let b of Object.values(updateData.data.bonuses ) ) {
|
||||
for ( let [k, v] of Object.entries(b) ) {
|
||||
b[k] = v.trim();
|
||||
}
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Get the bonuses fields and their localization strings
|
||||
* @return {Array<object>}
|
||||
* @private
|
||||
*/
|
||||
_getBonuses() {
|
||||
const bonuses = [
|
||||
{name: "data.bonuses.mwak.attack", label: "SW5E.BonusMWAttack"},
|
||||
{name: "data.bonuses.mwak.damage", label: "SW5E.BonusMWDamage"},
|
||||
{name: "data.bonuses.rwak.attack", label: "SW5E.BonusRWAttack"},
|
||||
{name: "data.bonuses.rwak.damage", label: "SW5E.BonusRWDamage"},
|
||||
{name: "data.bonuses.mpak.attack", label: "SW5E.BonusMPAttack"},
|
||||
{name: "data.bonuses.mpak.damage", label: "SW5E.BonusMPDamage"},
|
||||
{name: "data.bonuses.rpak.attack", label: "SW5E.BonusRPAttack"},
|
||||
{name: "data.bonuses.rpak.damage", label: "SW5E.BonusRPDamage"},
|
||||
{name: "data.bonuses.abilities.check", label: "SW5E.BonusAbilityCheck"},
|
||||
{name: "data.bonuses.abilities.save", label: "SW5E.BonusAbilitySave"},
|
||||
{name: "data.bonuses.abilities.skill", label: "SW5E.BonusAbilitySkill"},
|
||||
{name: "data.bonuses.power.dc", label: "SW5E.BonusPowerDC"},
|
||||
{name: "data.bonuses.power.forceLightDC", label: "SW5E.BonusForceLightPowerDC"},
|
||||
{name: "data.bonuses.power.forceDarkDC", label: "SW5E.BonusForceDarkPowerDC"},
|
||||
{name: "data.bonuses.power.forceUnivDC", label: "SW5E.BonusForceUnivPowerDC"},
|
||||
{name: "data.bonuses.power.techDC", label: "SW5E.BonusTechPowerDC"}
|
||||
];
|
||||
for (let b of bonuses) {
|
||||
b.value = getProperty(this.object._data, b.name) || "";
|
||||
}
|
||||
return bonuses;
|
||||
}
|
||||
|
||||
// Diff the data against any applied overrides and apply
|
||||
await actor.update(updateData, {diff: false});
|
||||
}
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
async _updateObject(event, formData) {
|
||||
const actor = this.object;
|
||||
let updateData = expandObject(formData);
|
||||
|
||||
// Unset any flags which are "false"
|
||||
let unset = false;
|
||||
const flags = updateData.flags.sw5e;
|
||||
//clone flags to dnd5e for module compatability
|
||||
updateData.flags.dnd5e = updateData.flags.sw5e;
|
||||
for (let [k, v] of Object.entries(flags)) {
|
||||
if ([undefined, null, "", false, 0].includes(v)) {
|
||||
delete flags[k];
|
||||
if (hasProperty(actor._data.flags, `sw5e.${k}`)) {
|
||||
unset = true;
|
||||
flags[`-=${k}`] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear any bonuses which are whitespace only
|
||||
for (let b of Object.values(updateData.data.bonuses)) {
|
||||
for (let [k, v] of Object.entries(b)) {
|
||||
b[k] = v.trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Diff the data against any applied overrides and apply
|
||||
await actor.update(updateData, {diff: false});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import Actor5e from "../actor/entity.js";
|
|||
* @extends {FormApplication}
|
||||
*/
|
||||
export default class ActorTypeConfig extends FormApplication {
|
||||
|
||||
/** @inheritdoc */
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
|
@ -32,23 +31,23 @@ export default class ActorTypeConfig extends FormApplication {
|
|||
|
||||
/** @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: ""
|
||||
};
|
||||
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) ) {
|
||||
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
|
||||
|
@ -61,12 +60,14 @@ export default class ActorTypeConfig extends FormApplication {
|
|||
},
|
||||
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;
|
||||
}, {}),
|
||||
sizes: Array.from(Object.entries(CONFIG.SW5E.actorSizes))
|
||||
.reverse()
|
||||
.reduce((obj, e) => {
|
||||
obj[e[0]] = e[1];
|
||||
return obj;
|
||||
}, {}),
|
||||
preview: Actor5e.formatCreatureType(attr) || "–"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -74,7 +75,7 @@ export default class ActorTypeConfig extends FormApplication {
|
|||
/** @override */
|
||||
async _updateObject(event, formData) {
|
||||
const typeObject = foundry.utils.expandObject(formData);
|
||||
return this.object.update({ 'data.details.type': typeObject });
|
||||
return this.object.update({"data.details.type": typeObject});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
* @implements {DocumentSheet}
|
||||
*/
|
||||
export default class ActorHitDiceConfig extends DocumentSheet {
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
|
@ -26,20 +25,22 @@ export default class ActorHitDiceConfig extends DocumentSheet {
|
|||
/** @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)))
|
||||
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)))
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -50,7 +51,7 @@ export default class ActorHitDiceConfig extends DocumentSheet {
|
|||
super.activateListeners(html);
|
||||
|
||||
// Hook up -/+ buttons to adjust the current value in the form
|
||||
html.find("button.increment,button.decrement").click(event => {
|
||||
html.find("button.increment,button.decrement").click((event) => {
|
||||
const button = event.currentTarget;
|
||||
const current = button.parentElement.querySelector(".current");
|
||||
const max = button.parentElement.querySelector(".max");
|
||||
|
@ -67,8 +68,8 @@ export default class ActorHitDiceConfig extends DocumentSheet {
|
|||
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,
|
||||
"_id": id,
|
||||
"data.hitDiceUsed": actorItems.get(id).data.data.levels - hd
|
||||
}));
|
||||
return this.object.updateEmbeddedDocuments("Item", classUpdates);
|
||||
}
|
||||
|
|
|
@ -3,65 +3,65 @@
|
|||
* @extends {Dialog}
|
||||
*/
|
||||
export default class LongRestDialog extends Dialog {
|
||||
constructor(actor, dialogData = {}, options = {}) {
|
||||
super(dialogData, options);
|
||||
this.actor = actor;
|
||||
}
|
||||
constructor(actor, dialogData = {}, options = {}) {
|
||||
super(dialogData, options);
|
||||
this.actor = actor;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
template: "systems/sw5e/templates/apps/long-rest.html",
|
||||
classes: ["sw5e", "dialog"]
|
||||
});
|
||||
}
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
template: "systems/sw5e/templates/apps/long-rest.html",
|
||||
classes: ["sw5e", "dialog"]
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
const data = super.getData();
|
||||
const variant = game.settings.get("sw5e", "restVariant");
|
||||
data.promptNewDay = variant !== "gritty"; // It's always a new day when resting 1 week
|
||||
data.newDay = variant === "normal"; // It's probably a new day when resting normally (8 hours)
|
||||
return data;
|
||||
}
|
||||
/** @override */
|
||||
getData() {
|
||||
const data = super.getData();
|
||||
const variant = game.settings.get("sw5e", "restVariant");
|
||||
data.promptNewDay = variant !== "gritty"; // It's always a new day when resting 1 week
|
||||
data.newDay = variant === "normal"; // It's probably a new day when resting normally (8 hours)
|
||||
return data;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A helper constructor function which displays the Long Rest confirmation dialog and returns a Promise once it's
|
||||
* workflow has been resolved.
|
||||
* @param {Actor5e} actor
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async longRestDialog({ actor } = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const dlg = new this(actor, {
|
||||
title: game.i18n.localize("SW5E.LongRest"),
|
||||
buttons: {
|
||||
rest: {
|
||||
icon: '<i class="fas fa-bed"></i>',
|
||||
label: game.i18n.localize("SW5E.Rest"),
|
||||
callback: html => {
|
||||
let newDay = true;
|
||||
if (game.settings.get("sw5e", "restVariant") !== "gritty")
|
||||
newDay = html.find('input[name="newDay"]')[0].checked;
|
||||
resolve(newDay);
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-times"></i>',
|
||||
label: game.i18n.localize("Cancel"),
|
||||
callback: reject
|
||||
}
|
||||
},
|
||||
default: 'rest',
|
||||
close: reject
|
||||
});
|
||||
dlg.render(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* A helper constructor function which displays the Long Rest confirmation dialog and returns a Promise once it's
|
||||
* workflow has been resolved.
|
||||
* @param {Actor5e} actor
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async longRestDialog({actor} = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const dlg = new this(actor, {
|
||||
title: game.i18n.localize("SW5E.LongRest"),
|
||||
buttons: {
|
||||
rest: {
|
||||
icon: '<i class="fas fa-bed"></i>',
|
||||
label: game.i18n.localize("SW5E.Rest"),
|
||||
callback: (html) => {
|
||||
let newDay = true;
|
||||
if (game.settings.get("sw5e", "restVariant") !== "gritty")
|
||||
newDay = html.find('input[name="newDay"]')[0].checked;
|
||||
resolve(newDay);
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-times"></i>',
|
||||
label: game.i18n.localize("Cancel"),
|
||||
callback: reject
|
||||
}
|
||||
},
|
||||
default: "rest",
|
||||
close: reject
|
||||
});
|
||||
dlg.render(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,37 +3,36 @@
|
|||
* @extends {DocumentSheet}
|
||||
*/
|
||||
export default class ActorMovementConfig extends DocumentSheet {
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e"],
|
||||
template: "systems/sw5e/templates/apps/movement-config.html",
|
||||
width: 300,
|
||||
height: "auto"
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
get title() {
|
||||
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: 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;
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e"],
|
||||
template: "systems/sw5e/templates/apps/movement-config.html",
|
||||
width: 300,
|
||||
height: "auto"
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
get title() {
|
||||
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: 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;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* @type {Dialog}
|
||||
*/
|
||||
export default class SelectItemsPrompt extends Dialog {
|
||||
constructor(items, dialogData={}, options={}) {
|
||||
constructor(items, dialogData = {}, options = {}) {
|
||||
super(dialogData, options);
|
||||
this.options.classes = ["sw5e", "dialog", "select-items-prompt", "sheet"];
|
||||
|
||||
|
@ -18,11 +18,11 @@ export default class SelectItemsPrompt extends Dialog {
|
|||
super.activateListeners(html);
|
||||
|
||||
// render the item's sheet if its image is clicked
|
||||
html.on('click', '.item-image', (event) => {
|
||||
html.on("click", ".item-image", (event) => {
|
||||
const item = this.items.find((feature) => feature.id === event.currentTarget.dataset?.itemId);
|
||||
|
||||
item?.sheet.render(true);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,29 +33,27 @@ export default class SelectItemsPrompt extends Dialog {
|
|||
* @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
|
||||
}) {
|
||||
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'),
|
||||
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 => {
|
||||
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]);
|
||||
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'),
|
||||
label: game.i18n.localize("SW5E.Skip"),
|
||||
callback: () => resolve([])
|
||||
}
|
||||
},
|
||||
|
|
|
@ -3,41 +3,41 @@
|
|||
* @extends {DocumentSheet}
|
||||
*/
|
||||
export default class ActorSensesConfig extends DocumentSheet {
|
||||
|
||||
/** @inheritdoc */
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e"],
|
||||
template: "systems/sw5e/templates/apps/senses-config.html",
|
||||
width: 300,
|
||||
height: "auto"
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritdoc */
|
||||
get title() {
|
||||
return `${game.i18n.localize("SW5E.SensesConfig")}: ${this.document.name}`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritdoc */
|
||||
getData(options) {
|
||||
const senses = foundry.utils.getProperty(this.document.data._source, "data.attributes.senses") || {};
|
||||
const data = {
|
||||
senses: {},
|
||||
special: senses.special ?? "",
|
||||
units: senses.units, movementUnits: CONFIG.SW5E.movementUnits
|
||||
};
|
||||
for ( let [name, label] of Object.entries(CONFIG.SW5E.senses) ) {
|
||||
const v = senses[name];
|
||||
data.senses[name] = {
|
||||
label: game.i18n.localize(label),
|
||||
value: Number.isNumeric(v) ? v.toNearest(0.1) : 0
|
||||
}
|
||||
/** @inheritdoc */
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e"],
|
||||
template: "systems/sw5e/templates/apps/senses-config.html",
|
||||
width: 300,
|
||||
height: "auto"
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritdoc */
|
||||
get title() {
|
||||
return `${game.i18n.localize("SW5E.SensesConfig")}: ${this.document.name}`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritdoc */
|
||||
getData(options) {
|
||||
const senses = foundry.utils.getProperty(this.document.data._source, "data.attributes.senses") || {};
|
||||
const data = {
|
||||
senses: {},
|
||||
special: senses.special ?? "",
|
||||
units: senses.units,
|
||||
movementUnits: CONFIG.SW5E.movementUnits
|
||||
};
|
||||
for (let [name, label] of Object.entries(CONFIG.SW5E.senses)) {
|
||||
const v = senses[name];
|
||||
data.senses[name] = {
|
||||
label: game.i18n.localize(label),
|
||||
value: Number.isNumeric(v) ? v.toNearest(0.1) : 0
|
||||
};
|
||||
}
|
||||
return data;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,129 +5,130 @@ import LongRestDialog from "./long-rest.js";
|
|||
* @extends {Dialog}
|
||||
*/
|
||||
export default class ShortRestDialog extends Dialog {
|
||||
constructor(actor, dialogData={}, options={}) {
|
||||
super(dialogData, options);
|
||||
constructor(actor, dialogData = {}, options = {}) {
|
||||
super(dialogData, options);
|
||||
|
||||
/**
|
||||
* Store a reference to the Actor entity which is resting
|
||||
* @type {Actor}
|
||||
*/
|
||||
this.actor = actor;
|
||||
/**
|
||||
* Store a reference to the Actor entity which is resting
|
||||
* @type {Actor}
|
||||
*/
|
||||
this.actor = actor;
|
||||
|
||||
/**
|
||||
* Track the most recently used HD denomination for re-rendering the form
|
||||
* @type {string}
|
||||
*/
|
||||
this._denom = null;
|
||||
}
|
||||
/**
|
||||
* Track the most recently used HD denomination for re-rendering the form
|
||||
* @type {string}
|
||||
*/
|
||||
this._denom = null;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
template: "systems/sw5e/templates/apps/short-rest.html",
|
||||
classes: ["sw5e", "dialog"]
|
||||
});
|
||||
}
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
template: "systems/sw5e/templates/apps/short-rest.html",
|
||||
classes: ["sw5e", "dialog"]
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
const data = super.getData();
|
||||
/** @override */
|
||||
getData() {
|
||||
const data = super.getData();
|
||||
|
||||
// Determine Hit Dice
|
||||
data.availableHD = this.actor.data.items.reduce((hd, item) => {
|
||||
if ( item.type === "class" ) {
|
||||
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;
|
||||
}
|
||||
return hd;
|
||||
}, {});
|
||||
data.canRoll = this.actor.data.data.attributes.hd > 0;
|
||||
data.denomination = this._denom;
|
||||
|
||||
// Determine rest type
|
||||
const variant = game.settings.get("sw5e", "restVariant");
|
||||
data.promptNewDay = variant !== "epic"; // It's never a new day when only resting 1 minute
|
||||
data.newDay = false; // It may be a new day, but not by default
|
||||
return data;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/** @override */
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
let btn = html.find("#roll-hd");
|
||||
btn.click(this._onRollHitDie.bind(this));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle rolling a Hit Die as part of a Short Rest action
|
||||
* @param {Event} event The triggering click event
|
||||
* @private
|
||||
*/
|
||||
async _onRollHitDie(event) {
|
||||
event.preventDefault();
|
||||
const btn = event.currentTarget;
|
||||
this._denom = btn.form.hd.value;
|
||||
await this.actor.rollHitDie(this._denom);
|
||||
this.render();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A helper constructor function which displays the Short Rest dialog and returns a Promise once it's workflow has
|
||||
* been resolved.
|
||||
* @param {Actor5e} actor
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async shortRestDialog({actor}={}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const dlg = new this(actor, {
|
||||
title: game.i18n.localize("SW5E.ShortRest"),
|
||||
buttons: {
|
||||
rest: {
|
||||
icon: '<i class="fas fa-bed"></i>',
|
||||
label: game.i18n.localize("SW5E.Rest"),
|
||||
callback: html => {
|
||||
let newDay = false;
|
||||
if (game.settings.get("sw5e", "restVariant") === "gritty")
|
||||
newDay = html.find('input[name="newDay"]')[0].checked;
|
||||
resolve(newDay);
|
||||
// Determine Hit Dice
|
||||
data.availableHD = this.actor.data.items.reduce((hd, item) => {
|
||||
if (item.type === "class") {
|
||||
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;
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-times"></i>',
|
||||
label: game.i18n.localize("Cancel"),
|
||||
callback: reject
|
||||
}
|
||||
},
|
||||
close: reject
|
||||
});
|
||||
dlg.render(true);
|
||||
});
|
||||
}
|
||||
return hd;
|
||||
}, {});
|
||||
data.canRoll = this.actor.data.data.attributes.hd > 0;
|
||||
data.denomination = this._denom;
|
||||
|
||||
/* -------------------------------------------- */
|
||||
// Determine rest type
|
||||
const variant = game.settings.get("sw5e", "restVariant");
|
||||
data.promptNewDay = variant !== "epic"; // It's never a new day when only resting 1 minute
|
||||
data.newDay = false; // It may be a new day, but not by default
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper constructor function which displays the Long Rest confirmation dialog and returns a Promise once it's
|
||||
* workflow has been resolved.
|
||||
* @deprecated
|
||||
* @param {Actor5e} actor
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async longRestDialog({actor}={}) {
|
||||
console.warn("WARNING! ShortRestDialog.longRestDialog has been deprecated, use LongRestDialog.longRestDialog instead.");
|
||||
return LongRestDialog.longRestDialog(...arguments);
|
||||
}
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
let btn = html.find("#roll-hd");
|
||||
btn.click(this._onRollHitDie.bind(this));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle rolling a Hit Die as part of a Short Rest action
|
||||
* @param {Event} event The triggering click event
|
||||
* @private
|
||||
*/
|
||||
async _onRollHitDie(event) {
|
||||
event.preventDefault();
|
||||
const btn = event.currentTarget;
|
||||
this._denom = btn.form.hd.value;
|
||||
await this.actor.rollHitDie(this._denom);
|
||||
this.render();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A helper constructor function which displays the Short Rest dialog and returns a Promise once it's workflow has
|
||||
* been resolved.
|
||||
* @param {Actor5e} actor
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async shortRestDialog({actor} = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const dlg = new this(actor, {
|
||||
title: game.i18n.localize("SW5E.ShortRest"),
|
||||
buttons: {
|
||||
rest: {
|
||||
icon: '<i class="fas fa-bed"></i>',
|
||||
label: game.i18n.localize("SW5E.Rest"),
|
||||
callback: (html) => {
|
||||
let newDay = false;
|
||||
if (game.settings.get("sw5e", "restVariant") === "gritty")
|
||||
newDay = html.find('input[name="newDay"]')[0].checked;
|
||||
resolve(newDay);
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-times"></i>',
|
||||
label: game.i18n.localize("Cancel"),
|
||||
callback: reject
|
||||
}
|
||||
},
|
||||
close: reject
|
||||
});
|
||||
dlg.render(true);
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A helper constructor function which displays the Long Rest confirmation dialog and returns a Promise once it's
|
||||
* workflow has been resolved.
|
||||
* @deprecated
|
||||
* @param {Actor5e} actor
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async longRestDialog({actor} = {}) {
|
||||
console.warn(
|
||||
"WARNING! ShortRestDialog.longRestDialog has been deprecated, use LongRestDialog.longRestDialog instead."
|
||||
);
|
||||
return LongRestDialog.longRestDialog(...arguments);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,86 +3,85 @@
|
|||
* @extends {DocumentSheet}
|
||||
*/
|
||||
export default class TraitSelector extends DocumentSheet {
|
||||
|
||||
/** @inheritdoc */
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
id: "trait-selector",
|
||||
classes: ["sw5e", "trait-selector", "subconfig"],
|
||||
title: "Actor Trait Selection",
|
||||
template: "systems/sw5e/templates/apps/trait-selector.html",
|
||||
width: 320,
|
||||
height: "auto",
|
||||
choices: {},
|
||||
allowCustom: true,
|
||||
minimum: 0,
|
||||
maximum: null,
|
||||
valueKey: "value",
|
||||
customKey: "custom"
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Return a reference to the target attribute
|
||||
* @type {string}
|
||||
*/
|
||||
get attribute() {
|
||||
return this.options.name;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
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 = 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: o.allowCustom,
|
||||
choices: choices,
|
||||
custom: custom
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
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);
|
||||
/** @inheritdoc */
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
id: "trait-selector",
|
||||
classes: ["sw5e", "trait-selector", "subconfig"],
|
||||
title: "Actor Trait Selection",
|
||||
template: "systems/sw5e/templates/apps/trait-selector.html",
|
||||
width: 320,
|
||||
height: "auto",
|
||||
choices: {},
|
||||
allowCustom: true,
|
||||
minimum: 0,
|
||||
maximum: null,
|
||||
valueKey: "value",
|
||||
customKey: "custom"
|
||||
});
|
||||
}
|
||||
|
||||
// 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 ( o.minimum && (chosen.length < o.minimum) ) {
|
||||
return ui.notifications.error(`You must choose at least ${o.minimum} options`);
|
||||
}
|
||||
if ( o.maximum && (chosen.length > o.maximum) ) {
|
||||
return ui.notifications.error(`You may choose no more than ${o.maximum} options`);
|
||||
/**
|
||||
* Return a reference to the target attribute
|
||||
* @type {string}
|
||||
*/
|
||||
get attribute() {
|
||||
return this.options.name;
|
||||
}
|
||||
|
||||
// Update the object
|
||||
this.object.update(updateData);
|
||||
}
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
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 = 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: o.allowCustom,
|
||||
choices: choices,
|
||||
custom: custom
|
||||
};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
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);
|
||||
}
|
||||
|
||||
// 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 (o.minimum && chosen.length < o.minimum) {
|
||||
return ui.notifications.error(`You must choose at least ${o.minimum} options`);
|
||||
}
|
||||
if (o.maximum && chosen.length > o.maximum) {
|
||||
return ui.notifications.error(`You may choose no more than ${o.maximum} options`);
|
||||
}
|
||||
|
||||
// Update the object
|
||||
this.object.update(updateData);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue