Initial styling config and auto-format of files

This commit is contained in:
TJ 2021-03-24 19:41:50 -05:00
parent e8d4153333
commit 42ddf4b0d0
33 changed files with 2965 additions and 2566 deletions

7
.prettierrc Normal file
View file

@ -0,0 +1,7 @@
{
"printWidth": 120,
"tabWidth": 2,
"trailingComma": "none",
"bracketSpacing": false,
"arrowParens": "avoid"
}

View file

@ -1,13 +1,12 @@
import {d20Roll, damageRoll} from "../dice.js"; import {d20Roll, damageRoll} from "../dice.js";
import ShortRestDialog from "../apps/short-rest.js"; import ShortRestDialog from "../apps/short-rest.js";
import LongRestDialog from "../apps/long-rest.js"; import LongRestDialog from "../apps/long-rest.js";
import {SW5E} from '../config.js'; import {SW5E} from "../config.js";
/** /**
* Extend the base Actor class to implement additional system-specific logic for SW5e. * Extend the base Actor class to implement additional system-specific logic for SW5e.
*/ */
export default class Actor5e extends Actor { export default class Actor5e extends Actor {
/** /**
* Is this Actor currently polymorphed into some other creature? * Is this Actor currently polymorphed into some other creature?
* @return {boolean} * @return {boolean}
@ -43,8 +42,8 @@ export default class Actor5e extends Actor {
let originalSaves = null; let originalSaves = null;
let originalSkills = null; let originalSkills = null;
if (this.isPolymorphed) { if (this.isPolymorphed) {
const transformOptions = this.getFlag('sw5e', 'transformOptions'); const transformOptions = this.getFlag("sw5e", "transformOptions");
const original = game.actors?.get(this.getFlag('sw5e', 'originalActor')); const original = game.actors?.get(this.getFlag("sw5e", "originalActor"));
if (original) { if (original) {
if (transformOptions.mergeSaves) { if (transformOptions.mergeSaves) {
originalSaves = original.data.data.abilities; originalSaves = original.data.data.abilities;
@ -94,7 +93,8 @@ export default class Actor5e extends Actor {
// Prepare power-casting data // Prepare power-casting data
data.attributes.powerForceLightDC = 8 + data.abilities.wis.mod + data.attributes.prof ?? 10; data.attributes.powerForceLightDC = 8 + data.abilities.wis.mod + data.attributes.prof ?? 10;
data.attributes.powerForceDarkDC = 8 + data.abilities.cha.mod + data.attributes.prof ?? 10; data.attributes.powerForceDarkDC = 8 + data.abilities.cha.mod + data.attributes.prof ?? 10;
data.attributes.powerForceUnivDC = Math.max(data.attributes.powerForceLightDC,data.attributes.powerForceDarkDC) ?? 10; data.attributes.powerForceUnivDC =
Math.max(data.attributes.powerForceLightDC, data.attributes.powerForceDarkDC) ?? 10;
data.attributes.powerTechDC = 8 + data.abilities.int.mod + data.attributes.prof ?? 10; data.attributes.powerTechDC = 8 + data.abilities.int.mod + data.attributes.prof ?? 10;
this._computePowercastingProgression(this.data); this._computePowercastingProgression(this.data);
@ -166,14 +166,14 @@ export default class Actor5e extends Actor {
let ids = []; let ids = [];
for (let [l, f] of Object.entries(clsConfig.features || {})) { for (let [l, f] of Object.entries(clsConfig.features || {})) {
l = parseInt(l); l = parseInt(l);
if ( (l <= level) && (l > priorLevel) ) ids = ids.concat(f); if (l <= level && l > priorLevel) ids = ids.concat(f);
} }
// Acquire archetype features // Acquire archetype features
const archConfig = clsConfig.archetypes[archetypeName] || {}; const archConfig = clsConfig.archetypes[archetypeName] || {};
for (let [l, f] of Object.entries(archConfig.features || {})) { for (let [l, f] of Object.entries(archConfig.features || {})) {
l = parseInt(l); l = parseInt(l);
if ( (l <= level) && (l > priorLevel) ) ids = ids.concat(f); if (l <= level && l > priorLevel) ids = ids.concat(f);
} }
// Load item data for all identified features // Load item data for all identified features
@ -213,18 +213,18 @@ export default class Actor5e extends Actor {
let toCreate = []; let toCreate = [];
for (let u of updated instanceof Array ? updated : [updated]) { for (let u of updated instanceof Array ? updated : [updated]) {
const item = this.items.get(u._id); const item = this.items.get(u._id);
if (!item || (item.data.type !== "class")) continue; if (!item || item.data.type !== "class") continue;
const updateData = expandObject(u); const updateData = expandObject(u);
const config = { const config = {
className: updateData.name || item.data.name, className: updateData.name || item.data.name,
archetypeName: getProperty(updateData, "data.archetype") || item.data.data.archetype, archetypeName: getProperty(updateData, "data.archetype") || item.data.data.archetype,
level: getProperty(updateData, "data.levels"), level: getProperty(updateData, "data.levels"),
priorLevel: item ? item.data.data.levels : 0 priorLevel: item ? item.data.data.levels : 0
} };
// Get and create features for an increased class level // Get and create features for an increased class level
let changed = false; let changed = false;
if ( config.level && (config.level > config.priorLevel)) changed = true; if (config.level && config.level > config.priorLevel) changed = true;
if (config.archetypeName !== item.data.data.archetype) changed = true; if (config.archetypeName !== item.data.data.archetype) changed = true;
// Get features to create // Get features to create
@ -236,7 +236,7 @@ export default class Actor5e extends Actor {
} }
} }
} }
return toCreate return toCreate;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -250,14 +250,17 @@ export default class Actor5e extends Actor {
const data = actorData.data; const data = actorData.data;
// Determine character level and available hit dice based on owned Class items // Determine character level and available hit dice based on owned Class items
const [level, hd] = actorData.items.reduce((arr, item) => { const [level, hd] = actorData.items.reduce(
(arr, item) => {
if (item.type === "class") { if (item.type === "class") {
const classLevels = parseInt(item.data.levels) || 1; const classLevels = parseInt(item.data.levels) || 1;
arr[0] += classLevels; arr[0] += classLevels;
arr[1] += classLevels - (parseInt(item.data.hitDiceUsed) || 0); arr[1] += classLevels - (parseInt(item.data.hitDiceUsed) || 0);
} }
return arr; return arr;
}, [0, 0]); },
[0, 0]
);
data.details.level = level; data.details.level = level;
data.attributes.hd = hd; data.attributes.hd = hd;
@ -269,7 +272,7 @@ export default class Actor5e extends Actor {
xp.max = this.getLevelExp(level || 1); xp.max = this.getLevelExp(level || 1);
const prior = this.getLevelExp(level - 1 || 0); const prior = this.getLevelExp(level - 1 || 0);
const required = xp.max - prior; const required = xp.max - prior;
const pct = Math.round((xp.value - prior) * 100 / required); const pct = Math.round(((xp.value - prior) * 100) / required);
xp.pct = Math.clamped(pct, 0, 100); xp.pct = Math.clamped(pct, 0, 100);
} }
@ -313,7 +316,7 @@ export default class Actor5e extends Actor {
* @private * @private
*/ */
_prepareSkills(actorData, bonuses, checkBonus, originalSkills) { _prepareSkills(actorData, bonuses, checkBonus, originalSkills) {
if (actorData.type === 'vehicle') return; if (actorData.type === "vehicle") return;
const data = actorData.data; const data = actorData.data;
const flags = actorData.flags.sw5e || {}; const flags = actorData.flags.sw5e || {};
@ -329,13 +332,13 @@ export default class Actor5e extends Actor {
let round = Math.floor; let round = Math.floor;
// Remarkable // Remarkable
if ( athlete && (skl.value < 0.5) && feats.remarkableAthlete.abilities.includes(skl.ability) ) { if (athlete && skl.value < 0.5 && feats.remarkableAthlete.abilities.includes(skl.ability)) {
skl.value = 0.5; skl.value = 0.5;
round = Math.ceil; round = Math.ceil;
} }
// Jack of All Trades // Jack of All Trades
if ( joat && (skl.value < 0.5) ) { if (joat && skl.value < 0.5) {
skl.value = 0.5; skl.value = 0.5;
} }
@ -351,7 +354,7 @@ export default class Actor5e extends Actor {
skl.total = skl.mod + skl.prof + skl.bonus; skl.total = skl.mod + skl.prof + skl.bonus;
// Compute passive bonus // Compute passive bonus
const passive = observant && (feats.observantFeat.skills.includes(id)) ? 5 : 0; const passive = observant && feats.observantFeat.skills.includes(id) ? 5 : 0;
skl.passive = 10 + skl.total + passive; skl.passive = 10 + skl.total + passive;
} }
} }
@ -363,9 +366,9 @@ export default class Actor5e extends Actor {
* @private * @private
*/ */
_computePowercastingProgression(actorData) { _computePowercastingProgression(actorData) {
if (actorData.type === 'vehicle') return; if (actorData.type === "vehicle") return;
const powers = actorData.data.powers; const powers = actorData.data.powers;
const isNPC = actorData.type === 'npc'; const isNPC = actorData.type === "npc";
// Translate the list of classes into force and tech power-casting progression // Translate the list of classes into force and tech power-casting progression
const forceProgression = { const forceProgression = {
@ -401,92 +404,93 @@ export default class Actor5e extends Actor {
const prog = d.powercasting; const prog = d.powercasting;
switch (prog) { switch (prog) {
case 'consular': case "consular":
priority = 3; priority = 3;
forceProgression.levels += levels; forceProgression.levels += levels;
forceProgression.multi += (SW5E.powerMaxLevel['consular'][19]/9)*levels; forceProgression.multi += (SW5E.powerMaxLevel["consular"][19] / 9) * levels;
forceProgression.classes++; forceProgression.classes++;
// see if class controls high level forcecasting // see if class controls high level forcecasting
if ((levels >= forceProgression.maxClassLevels) && (priority > forceProgression.maxClassPriority)){ if (levels >= forceProgression.maxClassLevels && priority > forceProgression.maxClassPriority) {
forceProgression.maxClass = 'consular'; forceProgression.maxClass = "consular";
forceProgression.maxClassLevels = levels; forceProgression.maxClassLevels = levels;
forceProgression.maxClassPriority = priority; forceProgression.maxClassPriority = priority;
forceProgression.maxClassPowerLevel = SW5E.powerMaxLevel['consular'][Math.clamped((levels - 1), 0, 20)]; forceProgression.maxClassPowerLevel = SW5E.powerMaxLevel["consular"][Math.clamped(levels - 1, 0, 20)];
} }
// calculate points and powers known // calculate points and powers known
forceProgression.powersKnown += SW5E.powersKnown['consular'][Math.clamped((levels - 1), 0, 20)]; forceProgression.powersKnown += SW5E.powersKnown["consular"][Math.clamped(levels - 1, 0, 20)];
forceProgression.points += SW5E.powerPoints['consular'][Math.clamped((levels - 1), 0, 20)]; forceProgression.points += SW5E.powerPoints["consular"][Math.clamped(levels - 1, 0, 20)];
break; break;
case 'engineer': case "engineer":
priority = 2 priority = 2;
techProgression.levels += levels; techProgression.levels += levels;
techProgression.multi += (SW5E.powerMaxLevel['engineer'][19]/9)*levels; techProgression.multi += (SW5E.powerMaxLevel["engineer"][19] / 9) * levels;
techProgression.classes++; techProgression.classes++;
// see if class controls high level techcasting // see if class controls high level techcasting
if ((levels >= techProgression.maxClassLevels) && (priority > techProgression.maxClassPriority)){ if (levels >= techProgression.maxClassLevels && priority > techProgression.maxClassPriority) {
techProgression.maxClass = 'engineer'; techProgression.maxClass = "engineer";
techProgression.maxClassLevels = levels; techProgression.maxClassLevels = levels;
techProgression.maxClassPriority = priority; techProgression.maxClassPriority = priority;
techProgression.maxClassPowerLevel = SW5E.powerMaxLevel['engineer'][Math.clamped((levels - 1), 0, 20)]; techProgression.maxClassPowerLevel = SW5E.powerMaxLevel["engineer"][Math.clamped(levels - 1, 0, 20)];
} }
techProgression.powersKnown += SW5E.powersKnown['engineer'][Math.clamped((levels - 1), 0, 20)]; techProgression.powersKnown += SW5E.powersKnown["engineer"][Math.clamped(levels - 1, 0, 20)];
techProgression.points += SW5E.powerPoints['engineer'][Math.clamped((levels - 1), 0, 20)]; techProgression.points += SW5E.powerPoints["engineer"][Math.clamped(levels - 1, 0, 20)];
break; break;
case 'guardian': case "guardian":
priority = 1; priority = 1;
forceProgression.levels += levels; forceProgression.levels += levels;
forceProgression.multi += (SW5E.powerMaxLevel['guardian'][19]/9)*levels; forceProgression.multi += (SW5E.powerMaxLevel["guardian"][19] / 9) * levels;
forceProgression.classes++; forceProgression.classes++;
// see if class controls high level forcecasting // see if class controls high level forcecasting
if ((levels >= forceProgression.maxClassLevels) && (priority > forceProgression.maxClassPriority)){ if (levels >= forceProgression.maxClassLevels && priority > forceProgression.maxClassPriority) {
forceProgression.maxClass = 'guardian'; forceProgression.maxClass = "guardian";
forceProgression.maxClassLevels = levels; forceProgression.maxClassLevels = levels;
forceProgression.maxClassPriority = priority; forceProgression.maxClassPriority = priority;
forceProgression.maxClassPowerLevel = SW5E.powerMaxLevel['guardian'][Math.clamped((levels - 1), 0, 20)]; forceProgression.maxClassPowerLevel = SW5E.powerMaxLevel["guardian"][Math.clamped(levels - 1, 0, 20)];
} }
forceProgression.powersKnown += SW5E.powersKnown['guardian'][Math.clamped((levels - 1), 0, 20)]; forceProgression.powersKnown += SW5E.powersKnown["guardian"][Math.clamped(levels - 1, 0, 20)];
forceProgression.points += SW5E.powerPoints['guardian'][Math.clamped((levels - 1), 0, 20)]; forceProgression.points += SW5E.powerPoints["guardian"][Math.clamped(levels - 1, 0, 20)];
break; break;
case 'scout': case "scout":
priority = 1; priority = 1;
techProgression.levels += levels; techProgression.levels += levels;
techProgression.multi += (SW5E.powerMaxLevel['scout'][19]/9)*levels; techProgression.multi += (SW5E.powerMaxLevel["scout"][19] / 9) * levels;
techProgression.classes++; techProgression.classes++;
// see if class controls high level techcasting // see if class controls high level techcasting
if ((levels >= techProgression.maxClassLevels) && (priority > techProgression.maxClassPriority)){ if (levels >= techProgression.maxClassLevels && priority > techProgression.maxClassPriority) {
techProgression.maxClass = 'scout'; techProgression.maxClass = "scout";
techProgression.maxClassLevels = levels; techProgression.maxClassLevels = levels;
techProgression.maxClassPriority = priority; techProgression.maxClassPriority = priority;
techProgression.maxClassPowerLevel = SW5E.powerMaxLevel['scout'][Math.clamped((levels - 1), 0, 20)]; techProgression.maxClassPowerLevel = SW5E.powerMaxLevel["scout"][Math.clamped(levels - 1, 0, 20)];
} }
techProgression.powersKnown += SW5E.powersKnown['scout'][Math.clamped((levels - 1), 0, 20)]; techProgression.powersKnown += SW5E.powersKnown["scout"][Math.clamped(levels - 1, 0, 20)];
techProgression.points += SW5E.powerPoints['scout'][Math.clamped((levels - 1), 0, 20)]; techProgression.points += SW5E.powerPoints["scout"][Math.clamped(levels - 1, 0, 20)];
break; break;
case 'sentinel': case "sentinel":
priority = 2; priority = 2;
forceProgression.levels += levels; forceProgression.levels += levels;
forceProgression.multi += (SW5E.powerMaxLevel['sentinel'][19]/9)*levels; forceProgression.multi += (SW5E.powerMaxLevel["sentinel"][19] / 9) * levels;
forceProgression.classes++; forceProgression.classes++;
// see if class controls high level forcecasting // see if class controls high level forcecasting
if ((levels >= forceProgression.maxClassLevels) && (priority > forceProgression.maxClassPriority)){ if (levels >= forceProgression.maxClassLevels && priority > forceProgression.maxClassPriority) {
forceProgression.maxClass = 'sentinel'; forceProgression.maxClass = "sentinel";
forceProgression.maxClassLevels = levels; forceProgression.maxClassLevels = levels;
forceProgression.maxClassPriority = priority; forceProgression.maxClassPriority = priority;
forceProgression.maxClassPowerLevel = SW5E.powerMaxLevel['sentinel'][Math.clamped((levels - 1), 0, 20)]; forceProgression.maxClassPowerLevel = SW5E.powerMaxLevel["sentinel"][Math.clamped(levels - 1, 0, 20)];
}
forceProgression.powersKnown += SW5E.powersKnown["sentinel"][Math.clamped(levels - 1, 0, 20)];
forceProgression.points += SW5E.powerPoints["sentinel"][Math.clamped(levels - 1, 0, 20)];
break;
} }
forceProgression.powersKnown += SW5E.powersKnown['sentinel'][Math.clamped((levels - 1), 0, 20)];
forceProgression.points += SW5E.powerPoints['sentinel'][Math.clamped((levels - 1), 0, 20)];
break; }
} }
// EXCEPTION: multi-classed progression uses multi rounded down rather than levels // EXCEPTION: multi-classed progression uses multi rounded down rather than levels
if (!isNPC && forceProgression.classes > 1) { if (!isNPC && forceProgression.classes > 1) {
forceProgression.levels = Math.floor(forceProgression.multi); forceProgression.levels = Math.floor(forceProgression.multi);
forceProgression.maxClassPowerLevel = SW5E.powerMaxLevel['multi'][forceProgression.levels - 1]; forceProgression.maxClassPowerLevel = SW5E.powerMaxLevel["multi"][forceProgression.levels - 1];
} }
if (!isNPC && techProgression.classes > 1) { if (!isNPC && techProgression.classes > 1) {
techProgression.levels = Math.floor(techProgression.multi); techProgression.levels = Math.floor(techProgression.multi);
techProgression.maxClassPowerLevel = SW5E.powerMaxLevel['multi'][techProgression.levels - 1]; techProgression.maxClassPowerLevel = SW5E.powerMaxLevel["multi"][techProgression.levels - 1];
} }
// EXCEPTION: NPC with an explicit power-caster level // EXCEPTION: NPC with an explicit power-caster level
@ -494,18 +498,20 @@ export default class Actor5e extends Actor {
forceProgression.levels = actorData.data.details.powerForceLevel; forceProgression.levels = actorData.data.details.powerForceLevel;
actorData.data.attributes.force.level = forceProgression.levels; actorData.data.attributes.force.level = forceProgression.levels;
forceProgression.maxClass = actorData.data.attributes.powercasting; forceProgression.maxClass = actorData.data.attributes.powercasting;
forceProgression.maxClassPowerLevel = SW5E.powerMaxLevel[forceProgression.maxClass][Math.clamped((forceProgression.levels - 1), 0, 20)]; forceProgression.maxClassPowerLevel =
SW5E.powerMaxLevel[forceProgression.maxClass][Math.clamped(forceProgression.levels - 1, 0, 20)];
} }
if (isNPC && actorData.data.details.powerTechLevel) { if (isNPC && actorData.data.details.powerTechLevel) {
techProgression.levels = actorData.data.details.powerTechLevel; techProgression.levels = actorData.data.details.powerTechLevel;
actorData.data.attributes.tech.level = techProgression.levels; actorData.data.attributes.tech.level = techProgression.levels;
techProgression.maxClass = actorData.data.attributes.powercasting; techProgression.maxClass = actorData.data.attributes.powercasting;
techProgression.maxClassPowerLevel = SW5E.powerMaxLevel[techProgression.maxClass][Math.clamped((techProgression.levels - 1), 0, 20)]; techProgression.maxClassPowerLevel =
SW5E.powerMaxLevel[techProgression.maxClass][Math.clamped(techProgression.levels - 1, 0, 20)];
} }
// Look up the number of slots per level from the powerLimit table // Look up the number of slots per level from the powerLimit table
let forcePowerLimit = Array.from(SW5E.powerLimit['none']); let forcePowerLimit = Array.from(SW5E.powerLimit["none"]);
for (let i = 0; i < (forceProgression.maxClassPowerLevel); i++) { for (let i = 0; i < forceProgression.maxClassPowerLevel; i++) {
forcePowerLimit[i] = SW5E.powerLimit[forceProgression.maxClass][i]; forcePowerLimit[i] = SW5E.powerLimit[forceProgression.maxClass][i];
} }
@ -521,8 +527,8 @@ export default class Actor5e extends Actor {
} }
} }
let techPowerLimit = Array.from(SW5E.powerLimit['none']); let techPowerLimit = Array.from(SW5E.powerLimit["none"]);
for (let i = 0; i < (techProgression.maxClassPowerLevel); i++) { for (let i = 0; i < techProgression.maxClassPowerLevel; i++) {
techPowerLimit[i] = SW5E.powerLimit[techProgression.maxClass][i]; techPowerLimit[i] = SW5E.powerLimit[techProgression.maxClass][i];
} }
@ -541,7 +547,8 @@ export default class Actor5e extends Actor {
// Set Force and tech power for PC Actors // Set Force and tech power for PC Actors
if (!isNPC && forceProgression.levels) { if (!isNPC && forceProgression.levels) {
actorData.data.attributes.force.known.max = forceProgression.powersKnown; actorData.data.attributes.force.known.max = forceProgression.powersKnown;
actorData.data.attributes.force.points.max = forceProgression.points + Math.max(actorData.data.abilities.wis.mod,actorData.data.abilities.cha.mod); actorData.data.attributes.force.points.max =
forceProgression.points + Math.max(actorData.data.abilities.wis.mod, actorData.data.abilities.cha.mod);
actorData.data.attributes.force.level = forceProgression.levels; actorData.data.attributes.force.level = forceProgression.levels;
} }
if (!isNPC && techProgression.levels) { if (!isNPC && techProgression.levels) {
@ -589,25 +596,25 @@ export default class Actor5e extends Actor {
* @private * @private
*/ */
_computeEncumbrance(actorData) { _computeEncumbrance(actorData) {
// Get the total weight from items // Get the total weight from items
const physicalItems = ["weapon", "equipment", "consumable", "tool", "backpack", "loot"]; const physicalItems = ["weapon", "equipment", "consumable", "tool", "backpack", "loot"];
let weight = actorData.items.reduce((weight, i) => { let weight = actorData.items.reduce((weight, i) => {
if (!physicalItems.includes(i.type)) return weight; if (!physicalItems.includes(i.type)) return weight;
const q = i.data.quantity || 0; const q = i.data.quantity || 0;
const w = i.data.weight || 0; const w = i.data.weight || 0;
return weight + (q * w); return weight + q * w;
}, 0); }, 0);
// [Optional] add Currency Weight (for non-transformed actors) // [Optional] add Currency Weight (for non-transformed actors)
if (game.settings.get("sw5e", "currencyWeight") && actorData.data.currency) { if (game.settings.get("sw5e", "currencyWeight") && actorData.data.currency) {
const currency = actorData.data.currency; const currency = actorData.data.currency;
const numCoins = Object.values(currency).reduce((val, denom) => val += Math.max(denom, 0), 0); const numCoins = Object.values(currency).reduce((val, denom) => (val += Math.max(denom, 0)), 0);
weight += numCoins / CONFIG.SW5E.encumbrance.currencyPerWeight; weight += numCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
} }
// Determine the encumbrance size class // Determine the encumbrance size class
let mod = { let mod =
{
tiny: 0.5, tiny: 0.5,
sm: 1, sm: 1,
med: 1, med: 1,
@ -621,7 +628,7 @@ export default class Actor5e extends Actor {
weight = weight.toNearest(0.1); weight = weight.toNearest(0.1);
const max = actorData.data.abilities.str.value * CONFIG.SW5E.encumbrance.strMultiplier * mod; const max = actorData.data.abilities.str.value * CONFIG.SW5E.encumbrance.strMultiplier * mod;
const pct = Math.clamped((weight * 100) / max, 0, 100); const pct = Math.clamped((weight * 100) / max, 0, 100);
return { value: weight.toNearest(0.1), max, pct, encumbered: pct > (2/3) }; return {value: weight.toNearest(0.1), max, pct, encumbered: pct > 2 / 3};
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -632,13 +639,17 @@ export default class Actor5e extends Actor {
static async create(data, options = {}) { static async create(data, options = {}) {
data.token = data.token || {}; data.token = data.token || {};
if (data.type === "character") { if (data.type === "character") {
mergeObject(data.token, { mergeObject(
data.token,
{
vision: true, vision: true,
dimSight: 30, dimSight: 30,
brightSight: 0, brightSight: 0,
actorLink: true, actorLink: true,
disposition: 1 disposition: 1
}, {overwrite: false}); },
{overwrite: false}
);
} }
return super.create(data, options); return super.create(data, options);
} }
@ -647,10 +658,9 @@ export default class Actor5e extends Actor {
/** @override */ /** @override */
async update(data, options = {}) { async update(data, options = {}) {
// Apply changes in Actor size to Token width/height // Apply changes in Actor size to Token width/height
const newSize = getProperty(data, "data.traits.size"); const newSize = getProperty(data, "data.traits.size");
if ( newSize && (newSize !== getProperty(this.data, "data.traits.size")) ) { if (newSize && newSize !== getProperty(this.data, "data.traits.size")) {
let size = CONFIG.SW5E.tokenSizes[newSize]; let size = CONFIG.SW5E.tokenSizes[newSize];
if (this.isToken) this.token.update({height: size, width: size}); if (this.isToken) this.token.update({height: size, width: size});
else if (!data["token.width"] && !hasProperty(data, "token.width")) { else if (!data["token.width"] && !hasProperty(data, "token.width")) {
@ -660,7 +670,7 @@ export default class Actor5e extends Actor {
} }
// Reset death save counters // Reset death save counters
if ( (this.data.data.attributes.hp.value <= 0) && (getProperty(data, "data.attributes.hp.value") > 0) ) { if (this.data.data.attributes.hp.value <= 0 && getProperty(data, "data.attributes.hp.value") > 0) {
setProperty(data, "data.attributes.death.success", 0); setProperty(data, "data.attributes.death.success", 0);
setProperty(data, "data.attributes.death.failure", 0); setProperty(data, "data.attributes.death.failure", 0);
} }
@ -673,7 +683,6 @@ export default class Actor5e extends Actor {
/** @override */ /** @override */
async createEmbeddedEntity(embeddedName, itemData, options = {}) { async createEmbeddedEntity(embeddedName, itemData, options = {}) {
// Pre-creation steps for owned items // Pre-creation steps for owned items
if (embeddedName === "OwnedItem") this._preCreateOwnedItem(itemData, options); if (embeddedName === "OwnedItem") this._preCreateOwnedItem(itemData, options);
@ -691,10 +700,9 @@ export default class Actor5e extends Actor {
*/ */
_preCreateOwnedItem(itemData, options) { _preCreateOwnedItem(itemData, options) {
if (this.data.type === "vehicle") return; if (this.data.type === "vehicle") return;
const isNPC = this.data.type === 'npc'; const isNPC = this.data.type === "npc";
let initial = {}; let initial = {};
switch (itemData.type) { switch (itemData.type) {
case "weapon": case "weapon":
if (getProperty(itemData, "data.equipped") === undefined) { if (getProperty(itemData, "data.equipped") === undefined) {
initial["data.equipped"] = isNPC; // NPCs automatically equip weapons initial["data.equipped"] = isNPC; // NPCs automatically equip weapons
@ -704,16 +712,16 @@ export default class Actor5e extends Actor {
initial["data.proficient"] = true; // NPCs automatically have equipment proficiency initial["data.proficient"] = true; // NPCs automatically have equipment proficiency
} else { } else {
const weaponProf = { const weaponProf = {
"natural": true, natural: true,
"simpleVW": "sim", simpleVW: "sim",
"simpleB": "sim", simpleB: "sim",
"simpleLW": "sim", simpleLW: "sim",
"martialVW": "mar", martialVW: "mar",
"martialB": "mar", martialB: "mar",
"martialLW": "mar" martialLW: "mar"
}[itemData.data?.weaponType]; // Player characters check proficiency }[itemData.data?.weaponType]; // Player characters check proficiency
const actorWeaponProfs = this.data.data.traits?.weaponProf?.value || []; const actorWeaponProfs = this.data.data.traits?.weaponProf?.value || [];
const hasWeaponProf = (weaponProf === true) || actorWeaponProfs.includes(weaponProf); const hasWeaponProf = weaponProf === true || actorWeaponProfs.includes(weaponProf);
initial["data.proficient"] = hasWeaponProf; initial["data.proficient"] = hasWeaponProf;
} }
} }
@ -728,15 +736,15 @@ export default class Actor5e extends Actor {
initial["data.proficient"] = true; // NPCs automatically have equipment proficiency initial["data.proficient"] = true; // NPCs automatically have equipment proficiency
} else { } else {
const armorProf = { const armorProf = {
"natural": true, natural: true,
"clothing": true, clothing: true,
"light": "lgt", light: "lgt",
"medium": "med", medium: "med",
"heavy": "hvy", heavy: "hvy",
"shield": "shl" shield: "shl"
}[itemData.data?.armor?.type]; // Player characters check proficiency }[itemData.data?.armor?.type]; // Player characters check proficiency
const actorArmorProfs = this.data.data.traits?.armorProf?.value || []; const actorArmorProfs = this.data.data.traits?.armorProf?.value || [];
const hasEquipmentProf = (armorProf === true) || actorArmorProfs.includes(armorProf); const hasEquipmentProf = armorProf === true || actorArmorProfs.includes(armorProf);
initial["data.proficient"] = hasEquipmentProf; initial["data.proficient"] = hasEquipmentProf;
} }
} }
@ -757,7 +765,7 @@ export default class Actor5e extends Actor {
async modifyTokenAttribute(attribute, value, isDelta, isBar) { async modifyTokenAttribute(attribute, value, isDelta, isBar) {
if (attribute === "attributes.hp") { if (attribute === "attributes.hp") {
const hp = getProperty(this.data.data, attribute); const hp = getProperty(this.data.data, attribute);
const delta = isDelta ? (-1 * value) : (hp.value + hp.temp) - value; const delta = isDelta ? -1 * value : hp.value + hp.temp - value;
return this.applyDamage(delta); return this.applyDamage(delta);
} }
return super.modifyTokenAttribute(attribute, value, isDelta, isBar); return super.modifyTokenAttribute(attribute, value, isDelta, isBar);
@ -791,12 +799,16 @@ export default class Actor5e extends Actor {
// Delegate damage application to a hook // Delegate damage application to a hook
// TODO replace this in the future with a better modifyTokenAttribute function in the core // TODO replace this in the future with a better modifyTokenAttribute function in the core
const allowed = Hooks.call("modifyTokenAttribute", { const allowed = Hooks.call(
"modifyTokenAttribute",
{
attribute: "attributes.hp", attribute: "attributes.hp",
value: amount, value: amount,
isDelta: false, isDelta: false,
isBar: true isBar: true
}, updates); },
updates
);
return allowed !== false ? this.update(updates) : this; return allowed !== false ? this.update(updates) : this;
} }
@ -835,7 +847,7 @@ export default class Actor5e extends Actor {
} }
// Reliable Talent applies to any skill check we have full or better proficiency in // Reliable Talent applies to any skill check we have full or better proficiency in
const reliableTalent = (skl.value >= 1 && this.getFlag("sw5e", "reliableTalent")); const reliableTalent = skl.value >= 1 && this.getFlag("sw5e", "reliableTalent");
// Roll and return // Roll and return
const rollData = mergeObject(options, { const rollData = mergeObject(options, {
@ -898,8 +910,7 @@ export default class Actor5e extends Actor {
if (feats.remarkableAthlete && SW5E.characterFlags.remarkableAthlete.abilities.includes(abilityId)) { if (feats.remarkableAthlete && SW5E.characterFlags.remarkableAthlete.abilities.includes(abilityId)) {
parts.push("@proficiency"); parts.push("@proficiency");
data.proficiency = Math.ceil(0.5 * this.data.data.attributes.prof); data.proficiency = Math.ceil(0.5 * this.data.data.attributes.prof);
} } else if (feats.jackOfAllTrades) {
else if ( feats.jackOfAllTrades ) {
parts.push("@proficiency"); parts.push("@proficiency");
data.proficiency = Math.floor(0.5 * this.data.data.attributes.prof); data.proficiency = Math.floor(0.5 * this.data.data.attributes.prof);
} }
@ -983,10 +994,9 @@ export default class Actor5e extends Actor {
* @return {Promise<Roll|null>} A Promise which resolves to the Roll instance * @return {Promise<Roll|null>} A Promise which resolves to the Roll instance
*/ */
async rollDeathSave(options = {}) { async rollDeathSave(options = {}) {
// Display a warning if we are not at zero HP or if we already have reached 3 // Display a warning if we are not at zero HP or if we already have reached 3
const death = this.data.data.attributes.death; const death = this.data.data.attributes.death;
if ( (this.data.data.attributes.hp.value > 0) || (death.failure >= 3) || (death.success >= 3)) { if (this.data.data.attributes.hp.value > 0 || death.failure >= 3 || death.success >= 3) {
ui.notifications.warn(game.i18n.localize("SW5E.DeathSaveUnnecessary")); ui.notifications.warn(game.i18n.localize("SW5E.DeathSaveUnnecessary"));
return null; return null;
} }
@ -1032,7 +1042,10 @@ export default class Actor5e extends Actor {
"data.attributes.death.failure": 0, "data.attributes.death.failure": 0,
"data.attributes.hp.value": 1 "data.attributes.hp.value": 1
}); });
await ChatMessage.create({content: game.i18n.format("SW5E.DeathSaveCriticalSuccess", {name: this.name}), speaker}); await ChatMessage.create({
content: game.i18n.format("SW5E.DeathSaveCriticalSuccess", {name: this.name}),
speaker
});
} }
// 3 Successes = survive and reset checks // 3 Successes = survive and reset checks
@ -1052,7 +1065,8 @@ export default class Actor5e extends Actor {
else { else {
let failures = (death.failure || 0) + (d20 === 1 ? 2 : 1); let failures = (death.failure || 0) + (d20 === 1 ? 2 : 1);
await this.update({"data.attributes.death.failure": Math.clamped(failures, 0, 3)}); await this.update({"data.attributes.death.failure": Math.clamped(failures, 0, 3)});
if ( failures >= 3 ) { // 3 Failures = death if (failures >= 3) {
// 3 Failures = death
await ChatMessage.create({content: game.i18n.format("SW5E.DeathSaveFailure", {name: this.name}), speaker}); await ChatMessage.create({content: game.i18n.format("SW5E.DeathSaveFailure", {name: this.name}), speaker});
} }
} }
@ -1071,7 +1085,6 @@ export default class Actor5e extends Actor {
* @return {Promise<Roll|null>} The created Roll instance, or null if no hit die was rolled * @return {Promise<Roll|null>} The created Roll instance, or null if no hit die was rolled
*/ */
async rollHitDie(denomination, {dialog = true} = {}) { async rollHitDie(denomination, {dialog = true} = {}) {
// If no denomination was provided, choose the first available // If no denomination was provided, choose the first available
let cls = null; let cls = null;
if (!denomination) { if (!denomination) {
@ -1084,7 +1097,7 @@ export default class Actor5e extends Actor {
else { else {
cls = this.items.find(i => { cls = this.items.find(i => {
const d = i.data.data; const d = i.data.data;
return (d.hitDice === denomination) && ((d.hitDiceUsed || 0) < (d.levels || 1)); return d.hitDice === denomination && (d.hitDiceUsed || 0) < (d.levels || 1);
}); });
} }
@ -1133,7 +1146,6 @@ export default class Actor5e extends Actor {
* @return {Promise} A Promise which resolves once the short rest workflow has completed * @return {Promise} A Promise which resolves once the short rest workflow has completed
*/ */
async shortRest({dialog = true, chat = true, autoHD = false, autoHDThreshold = 3} = {}) { async shortRest({dialog = true, chat = true, autoHD = false, autoHDThreshold = 3} = {}) {
// Take note of the initial hit points and number of hit dice the Actor has // Take note of the initial hit points and number of hit dice the Actor has
const hp = this.data.data.attributes.hp; const hp = this.data.data.attributes.hp;
const hd0 = this.data.data.attributes.hd; const hd0 = this.data.data.attributes.hd;
@ -1151,7 +1163,7 @@ export default class Actor5e extends Actor {
// Automatically spend hit dice // Automatically spend hit dice
else if (autoHD) { else if (autoHD) {
while ( (hp.value + autoHDThreshold) <= hp.max ) { while (hp.value + autoHDThreshold <= hp.max) {
const r = await this.rollHitDie(undefined, {dialog: false}); const r = await this.rollHitDie(undefined, {dialog: false});
if (r === null) break; if (r === null) break;
} }
@ -1186,18 +1198,23 @@ export default class Actor5e extends Actor {
// Display a Chat Message summarizing the rest effects // Display a Chat Message summarizing the rest effects
if (chat) { if (chat) {
// Summarize the rest duration // Summarize the rest duration
let restFlavor; let restFlavor;
switch (game.settings.get("sw5e", "restVariant")) { switch (game.settings.get("sw5e", "restVariant")) {
case 'normal': restFlavor = game.i18n.localize("SW5E.ShortRestNormal"); break; case "normal":
case 'gritty': restFlavor = game.i18n.localize(newDay ? "SW5E.ShortRestOvernight" : "SW5E.ShortRestGritty"); break; restFlavor = game.i18n.localize("SW5E.ShortRestNormal");
case 'epic': restFlavor = game.i18n.localize("SW5E.ShortRestEpic"); break; break;
case "gritty":
restFlavor = game.i18n.localize(newDay ? "SW5E.ShortRestOvernight" : "SW5E.ShortRestGritty");
break;
case "epic":
restFlavor = game.i18n.localize("SW5E.ShortRestEpic");
break;
} }
// Summarize the health effects // Summarize the health effects
let srMessage = "SW5E.ShortRestResultShort"; let srMessage = "SW5E.ShortRestResultShort";
if ((dhd !== 0) && (dhp !== 0)){ if (dhd !== 0 && dhp !== 0) {
if (dtp !== 0) { if (dtp !== 0) {
srMessage = "SW5E.ShortRestResultWithTech"; srMessage = "SW5E.ShortRestResultWithTech";
} else { } else {
@ -1226,7 +1243,7 @@ export default class Actor5e extends Actor {
updateData: updateData, updateData: updateData,
updateItems: updateItems, updateItems: updateItems,
newDay: newDay newDay: newDay
} };
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -1275,23 +1292,26 @@ export default class Actor5e extends Actor {
// Recover power slots // Recover power slots
for (let [k, v] of Object.entries(data.powers)) { for (let [k, v] of Object.entries(data.powers)) {
updateData[`data.powers.${k}.fvalue`] = Number.isNumeric(v.foverride) ? v.foverride : (v.fmax ?? 0); updateData[`data.powers.${k}.fvalue`] = Number.isNumeric(v.foverride) ? v.foverride : v.fmax ?? 0;
} }
for (let [k, v] of Object.entries(data.powers)) { for (let [k, v] of Object.entries(data.powers)) {
updateData[`data.powers.${k}.tvalue`] = Number.isNumeric(v.toverride) ? v.toverride : (v.tmax ?? 0); updateData[`data.powers.${k}.tvalue`] = Number.isNumeric(v.toverride) ? v.toverride : v.tmax ?? 0;
} }
// Determine the number of hit dice which may be recovered // Determine the number of hit dice which may be recovered
let recoverHD = Math.max(Math.floor(data.details.level / 2), 1); let recoverHD = Math.max(Math.floor(data.details.level / 2), 1);
let dhd = 0; let dhd = 0;
// Sort classes which can recover HD, assuming players prefer recovering larger HD first. // Sort classes which can recover HD, assuming players prefer recovering larger HD first.
const updateItems = this.items.filter(item => item.data.type === "class").sort((a, b) => { const updateItems = this.items
.filter(item => item.data.type === "class")
.sort((a, b) => {
let da = parseInt(a.data.data.hitDice.slice(1)) || 0; let da = parseInt(a.data.data.hitDice.slice(1)) || 0;
let db = parseInt(b.data.data.hitDice.slice(1)) || 0; let db = parseInt(b.data.data.hitDice.slice(1)) || 0;
return db - da; return db - da;
}).reduce((updates, item) => { })
.reduce((updates, item) => {
const d = item.data.data; const d = item.data.data;
if ( (recoverHD > 0) && (d.hitDiceUsed > 0) ) { if (recoverHD > 0 && d.hitDiceUsed > 0) {
let delta = Math.min(d.hitDiceUsed || 0, recoverHD); let delta = Math.min(d.hitDiceUsed || 0, recoverHD);
recoverHD -= delta; recoverHD -= delta;
dhd += delta; dhd += delta;
@ -1306,8 +1326,7 @@ export default class Actor5e extends Actor {
const d = item.data.data; const d = item.data.data;
if (d.uses && recovery.includes(d.uses.per)) { if (d.uses && recovery.includes(d.uses.per)) {
updateItems.push({_id: item.id, "data.uses.value": d.uses.max}); updateItems.push({_id: item.id, "data.uses.value": d.uses.max});
} } else if (d.recharge && d.recharge.value) {
else if ( d.recharge && d.recharge.value ) {
updateItems.push({_id: item.id, "data.recharge.charged": true}); updateItems.push({_id: item.id, "data.recharge.charged": true});
} }
} }
@ -1319,9 +1338,15 @@ export default class Actor5e extends Actor {
// Display a Chat Message summarizing the rest effects // Display a Chat Message summarizing the rest effects
let restFlavor; let restFlavor;
switch (game.settings.get("sw5e", "restVariant")) { switch (game.settings.get("sw5e", "restVariant")) {
case 'normal': restFlavor = game.i18n.localize(newDay ? "SW5E.LongRestOvernight" : "SW5E.LongRestNormal"); break; case "normal":
case 'gritty': restFlavor = game.i18n.localize("SW5E.LongRestGritty"); break; restFlavor = game.i18n.localize(newDay ? "SW5E.LongRestOvernight" : "SW5E.LongRestNormal");
case 'epic': restFlavor = game.i18n.localize("SW5E.LongRestEpic"); break; break;
case "gritty":
restFlavor = game.i18n.localize("SW5E.LongRestGritty");
break;
case "epic":
restFlavor = game.i18n.localize("SW5E.LongRestEpic");
break;
} }
// Determine the chat message to display // Determine the chat message to display
@ -1348,12 +1373,11 @@ export default class Actor5e extends Actor {
updateData: updateData, updateData: updateData,
updateItems: updateItems, updateItems: updateItems,
newDay: newDay newDay: newDay
} };
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**
* Transform this Actor into another one. * Transform this Actor into another one.
* *
@ -1372,10 +1396,24 @@ export default class Actor5e extends Actor {
* @param {boolean} [keepVision] Keep vision * @param {boolean} [keepVision] Keep vision
* @param {boolean} [transformTokens] Transform linked tokens too * @param {boolean} [transformTokens] Transform linked tokens too
*/ */
async transformInto(target, { keepPhysical=false, keepMental=false, keepSaves=false, keepSkills=false, async transformInto(
mergeSaves=false, mergeSkills=false, keepClass=false, keepFeats=false, keepPowers=false, target,
keepItems=false, keepBio=false, keepVision=false, transformTokens=true}={}) { {
keepPhysical = false,
keepMental = false,
keepSaves = false,
keepSkills = false,
mergeSaves = false,
mergeSkills = false,
keepClass = false,
keepFeats = false,
keepPowers = false,
keepItems = false,
keepBio = false,
keepVision = false,
transformTokens = true
} = {}
) {
// Ensure the player is allowed to polymorph // Ensure the player is allowed to polymorph
const allowed = game.settings.get("sw5e", "allowPolymorphing"); const allowed = game.settings.get("sw5e", "allowPolymorphing");
if (!allowed && !game.user.isGM) { if (!allowed && !game.user.isGM) {
@ -1423,7 +1461,7 @@ export default class Actor5e extends Actor {
// Keep Token configurations // Keep Token configurations
const tokenConfig = ["displayName", "vision", "actorLink", "disposition", "displayBars", "bar1", "bar2"]; const tokenConfig = ["displayName", "vision", "actorLink", "disposition", "displayBars", "bar1", "bar2"];
if (keepVision) { if (keepVision) {
tokenConfig.push(...['dimSight', 'brightSight', 'dimLight', 'brightLight', 'vision', 'sightAngle']); tokenConfig.push(...["dimSight", "brightSight", "dimLight", "brightLight", "vision", "sightAngle"]);
} }
for (let c of tokenConfig) { for (let c of tokenConfig) {
d.token[c] = o.token[c]; d.token[c] = o.token[c];
@ -1449,18 +1487,20 @@ export default class Actor5e extends Actor {
} }
// Keep specific items from the original data // Keep specific items from the original data
d.items = d.items.concat(o.items.filter(i => { d.items = d.items.concat(
o.items.filter(i => {
if (i.type === "class") return keepClass; if (i.type === "class") return keepClass;
else if (i.type === "feat") return keepFeats; else if (i.type === "feat") return keepFeats;
else if (i.type === "power") return keepPowers; else if (i.type === "power") return keepPowers;
else return keepItems; else return keepItems;
})); })
);
// Transfer classes for NPCs // Transfer classes for NPCs
if (!keepClass && d.data.details.cr) { if (!keepClass && d.data.details.cr) {
d.items.push({ d.items.push({
type: 'class', type: "class",
name: game.i18n.localize('SW5E.PolymorphTmpClass'), name: game.i18n.localize("SW5E.PolymorphTmpClass"),
data: {levels: d.data.details.cr} data: {levels: d.data.details.cr}
}); });
} }
@ -1485,9 +1525,20 @@ export default class Actor5e extends Actor {
// Update regular Actors by creating a new Actor with the Polymorphed data // Update regular Actors by creating a new Actor with the Polymorphed data
await this.sheet.close(); await this.sheet.close();
Hooks.callAll('sw5e.transformActor', this, target, d, { Hooks.callAll("sw5e.transformActor", this, target, d, {
keepPhysical, keepMental, keepSaves, keepSkills, mergeSaves, mergeSkills, keepPhysical,
keepClass, keepFeats, keepPowers, keepItems, keepBio, keepVision, transformTokens keepMental,
keepSaves,
keepSkills,
mergeSaves,
mergeSkills,
keepClass,
keepFeats,
keepPowers,
keepItems,
keepBio,
keepVision,
transformTokens
}); });
const newActor = await this.constructor.create(d, {renderSheet: true}); const newActor = await this.constructor.create(d, {renderSheet: true});
@ -1526,7 +1577,7 @@ export default class Actor5e extends Actor {
} }
// Obtain a reference to the original actor // Obtain a reference to the original actor
const original = game.actors.get(this.getFlag('sw5e', 'originalActor')); const original = game.actors.get(this.getFlag("sw5e", "originalActor"));
if (!original) return; if (!original) return;
// Get the Tokens which represent this actor // Get the Tokens which represent this actor
@ -1557,16 +1608,16 @@ export default class Actor5e extends Actor {
*/ */
static addDirectoryContextOptions(html, entryOptions) { static addDirectoryContextOptions(html, entryOptions) {
entryOptions.push({ entryOptions.push({
name: 'SW5E.PolymorphRestoreTransformation', name: "SW5E.PolymorphRestoreTransformation",
icon: '<i class="fas fa-backward"></i>', icon: '<i class="fas fa-backward"></i>',
callback: li => { callback: li => {
const actor = game.actors.get(li.data('entityId')); const actor = game.actors.get(li.data("entityId"));
return actor.revertOriginalForm(); return actor.revertOriginalForm();
}, },
condition: li => { condition: li => {
const allowed = game.settings.get("sw5e", "allowPolymorphing"); const allowed = game.settings.get("sw5e", "allowPolymorphing");
if (!allowed && !game.user.isGM) return false; if (!allowed && !game.user.isGM) return false;
const actor = game.actors.get(li.data('entityId')); const actor = game.actors.get(li.data("entityId"));
return actor && actor.isPolymorphed; return actor && actor.isPolymorphed;
} }
}); });
@ -1580,7 +1631,9 @@ export default class Actor5e extends Actor {
* @deprecated since sw5e 0.97 * @deprecated since sw5e 0.97
*/ */
getPowerDC(ability) { getPowerDC(ability) {
console.warn(`The Actor5e#getPowerDC(ability) method has been deprecated in favor of Actor5e#data.data.abilities[ability].dc`); console.warn(
`The Actor5e#getPowerDC(ability) method has been deprecated in favor of Actor5e#data.data.abilities[ability].dc`
);
return this.data.data.abilities[ability]?.dc; return this.data.data.abilities[ability]?.dc;
} }

View file

@ -3,7 +3,7 @@ import TraitSelector from "../../../apps/trait-selector.js";
import ActorSheetFlags from "../../../apps/actor-flags.js"; import ActorSheetFlags from "../../../apps/actor-flags.js";
import ActorMovementConfig from "../../../apps/movement-config.js"; import ActorMovementConfig from "../../../apps/movement-config.js";
import ActorSensesConfig from "../../../apps/senses-config.js"; import ActorSensesConfig from "../../../apps/senses-config.js";
import {SW5E} from '../../../config.js'; import {SW5E} from "../../../config.js";
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js"; import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js";
/** /**
@ -48,7 +48,8 @@ export default class ActorSheet5e extends ActorSheet {
/** @override */ /** @override */
get template() { get template() {
if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/newActor/expanded-limited-sheet.html"; if (!game.user.isGM && this.actor.limited)
return "systems/sw5e/templates/actors/newActor/expanded-limited-sheet.html";
return `systems/sw5e/templates/actors/newActor/${this.actor.data.type}-sheet.html`; return `systems/sw5e/templates/actors/newActor/${this.actor.data.type}-sheet.html`;
} }
@ -56,7 +57,6 @@ export default class ActorSheet5e extends ActorSheet {
/** @override */ /** @override */
getData() { getData() {
// Basic data // Basic data
let isOwner = this.entity.owner; let isOwner = this.entity.owner;
const data = { const data = {
@ -67,8 +67,8 @@ export default class ActorSheet5e extends ActorSheet {
cssClass: isOwner ? "editable" : "locked", cssClass: isOwner ? "editable" : "locked",
isCharacter: this.entity.data.type === "character", isCharacter: this.entity.data.type === "character",
isNPC: this.entity.data.type === "npc", isNPC: this.entity.data.type === "npc",
isVehicle: this.entity.data.type === 'vehicle', isVehicle: this.entity.data.type === "vehicle",
config: CONFIG.SW5E, config: CONFIG.SW5E
}; };
// The Actor and its Items // The Actor and its Items
@ -115,7 +115,7 @@ export default class ActorSheet5e extends ActorSheet {
data.effects = prepareActiveEffectCategories(this.entity.effects); data.effects = prepareActiveEffectCategories(this.entity.effects);
// Return data to the sheet // Return data to the sheet
return data return data;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -134,9 +134,13 @@ export default class ActorSheet5e extends ActorSheet {
let speeds = [ let speeds = [
[movement.burrow, `${game.i18n.localize("SW5E.MovementBurrow")} ${movement.burrow}`], [movement.burrow, `${game.i18n.localize("SW5E.MovementBurrow")} ${movement.burrow}`],
[movement.climb, `${game.i18n.localize("SW5E.MovementClimb")} ${movement.climb}`], [movement.climb, `${game.i18n.localize("SW5E.MovementClimb")} ${movement.climb}`],
[movement.fly, `${game.i18n.localize("SW5E.MovementFly")} ${movement.fly}` + (movement.hover ? ` (${game.i18n.localize("SW5E.MovementHover")})` : "")], [
movement.fly,
`${game.i18n.localize("SW5E.MovementFly")} ${movement.fly}` +
(movement.hover ? ` (${game.i18n.localize("SW5E.MovementHover")})` : "")
],
[movement.swim, `${game.i18n.localize("SW5E.MovementSwim")} ${movement.swim}`] [movement.swim, `${game.i18n.localize("SW5E.MovementSwim")} ${movement.swim}`]
] ];
if (largestPrimary) { if (largestPrimary) {
speeds.push([movement.walk, `${game.i18n.localize("SW5E.MovementWalk")} ${movement.walk}`]); speeds.push([movement.walk, `${game.i18n.localize("SW5E.MovementWalk")} ${movement.walk}`]);
} }
@ -150,7 +154,7 @@ export default class ActorSheet5e extends ActorSheet {
return { return {
primary: `${primary ? primary[1] : "0"} ${movement.units}`, primary: `${primary ? primary[1] : "0"} ${movement.units}`,
special: speeds.map(s => s[1]).join(", ") special: speeds.map(s => s[1]).join(", ")
} };
} }
// Case 2: Walk as primary // Case 2: Walk as primary
@ -158,7 +162,7 @@ export default class ActorSheet5e extends ActorSheet {
return { return {
primary: `${movement.walk || 0} ${movement.units}`, primary: `${movement.walk || 0} ${movement.units}`,
special: speeds.length ? speeds.map(s => s[1]).join(", ") : "" special: speeds.length ? speeds.map(s => s[1]).join(", ") : ""
} };
} }
} }
@ -168,7 +172,7 @@ export default class ActorSheet5e extends ActorSheet {
const senses = actorData.data.attributes.senses || {}; const senses = actorData.data.attributes.senses || {};
const tags = {}; const tags = {};
for (let [k, label] of Object.entries(CONFIG.SW5E.senses)) { for (let [k, label] of Object.entries(CONFIG.SW5E.senses)) {
const v = senses[k] ?? 0 const v = senses[k] ?? 0;
if (v === 0) continue; if (v === 0) continue;
tags[k] = `${game.i18n.localize(label)} ${v} ${senses.units}`; tags[k] = `${game.i18n.localize(label)} ${v} ${senses.units}`;
} }
@ -185,14 +189,14 @@ export default class ActorSheet5e extends ActorSheet {
*/ */
_prepareTraits(traits) { _prepareTraits(traits) {
const map = { const map = {
"dr": CONFIG.SW5E.damageResistanceTypes, dr: CONFIG.SW5E.damageResistanceTypes,
"di": CONFIG.SW5E.damageResistanceTypes, di: CONFIG.SW5E.damageResistanceTypes,
"dv": CONFIG.SW5E.damageResistanceTypes, dv: CONFIG.SW5E.damageResistanceTypes,
"ci": CONFIG.SW5E.conditionTypes, ci: CONFIG.SW5E.conditionTypes,
"languages": CONFIG.SW5E.languages, languages: CONFIG.SW5E.languages,
"armorProf": CONFIG.SW5E.armorProficiencies, armorProf: CONFIG.SW5E.armorProficiencies,
"weaponProf": CONFIG.SW5E.weaponProficiencies, weaponProf: CONFIG.SW5E.weaponProficiencies,
"toolProf": CONFIG.SW5E.toolProficiencies toolProf: CONFIG.SW5E.toolProficiencies
}; };
for (let [t, choices] of Object.entries(map)) { for (let [t, choices] of Object.entries(map)) {
const trait = traits[t]; const trait = traits[t];
@ -208,7 +212,7 @@ export default class ActorSheet5e extends ActorSheet {
// Add custom entry // Add custom entry
if (trait.custom) { if (trait.custom) {
trait.custom.split(";").forEach((c, i) => trait.selected[`custom${i+1}`] = c.trim()); trait.custom.split(";").forEach((c, i) => (trait.selected[`custom${i + 1}`] = c.trim()));
} }
trait.cssClass = !isObjectEmpty(trait.selected) ? "" : "inactive"; trait.cssClass = !isObjectEmpty(trait.selected) ? "" : "inactive";
} }
@ -229,15 +233,15 @@ export default class ActorSheet5e extends ActorSheet {
// Define some mappings // Define some mappings
const sections = { const sections = {
"atwill": -20, atwill: -20,
"innate": -10, innate: -10
}; };
// Label power slot uses headers // Label power slot uses headers
const useLabels = { const useLabels = {
"-20": "-", "-20": "-",
"-10": "-", "-10": "-",
"0": "&infin;" 0: "&infin;"
}; };
// Format a powerbook entry for a certain indexed level // Format a powerbook entry for a certain indexed level
@ -247,12 +251,12 @@ export default class ActorSheet5e extends ActorSheet {
label: label, label: label,
usesSlots: i > 0, usesSlots: i > 0,
canCreate: owner, canCreate: owner,
canPrepare: (data.actor.type === "character") && (i >= 1), canPrepare: data.actor.type === "character" && i >= 1,
powers: [], powers: [],
uses: useLabels[i] || value || 0, uses: useLabels[i] || value || 0,
slots: useLabels[i] || max || 0, slots: useLabels[i] || max || 0,
override: override || 0, override: override || 0,
dataset: {"type": "power", "level": prepMode in sections ? 1 : i, "preparation.mode": prepMode, "school": school}, dataset: {type: "power", level: prepMode in sections ? 1 : i, "preparation.mode": prepMode, school: school},
prop: sl prop: sl
}; };
}; };
@ -261,7 +265,7 @@ export default class ActorSheet5e extends ActorSheet {
const maxLevel = Array.fromRange(10).reduce((max, i) => { const maxLevel = Array.fromRange(10).reduce((max, i) => {
if (i === 0) return max; if (i === 0) return max;
const level = levels[`power${i}`]; const level = levels[`power${i}`];
if ( (level.max || level.override ) && ( i > max ) ) max = i; if ((level.max || level.override) && i > max) max = i;
return max; return max;
}, 0); }, 0);
@ -324,7 +328,7 @@ export default class ActorSheet5e extends ActorSheet {
// Action usage // Action usage
for (let f of ["action", "bonus", "reaction"]) { for (let f of ["action", "bonus", "reaction"]) {
if (filters.has(f)) { if (filters.has(f)) {
if ((data.activation && (data.activation.type !== f))) return false; if (data.activation && data.activation.type !== f) return false;
} }
} }
@ -374,41 +378,42 @@ export default class ActorSheet5e extends ActorSheet {
* @param html {HTML} The prepared HTML object ready to be rendered into the DOM * @param html {HTML} The prepared HTML object ready to be rendered into the DOM
*/ */
activateListeners(html) { activateListeners(html) {
// Activate Item Filters // Activate Item Filters
const filterLists = html.find(".filter-list"); const filterLists = html.find(".filter-list");
filterLists.each(this._initializeFilterItemList.bind(this)); filterLists.each(this._initializeFilterItemList.bind(this));
filterLists.on("click", ".filter-item", this._onToggleFilter.bind(this)); filterLists.on("click", ".filter-item", this._onToggleFilter.bind(this));
// Item summaries // Item summaries
html.find('.item .item-name.rollable h4').click(event => this._onItemSummary(event)); html.find(".item .item-name.rollable h4").click(event => this._onItemSummary(event));
// Editable Only Listeners // Editable Only Listeners
if (this.isEditable) { if (this.isEditable) {
// Input focus and update // Input focus and update
const inputs = html.find("input"); const inputs = html.find("input");
inputs.focus(ev => ev.currentTarget.select()); inputs.focus(ev => ev.currentTarget.select());
inputs.addBack().find('[data-dtype="Number"]').change(this._onChangeInputDelta.bind(this)); inputs.addBack().find('[data-dtype="Number"]').change(this._onChangeInputDelta.bind(this));
// Ability Proficiency // Ability Proficiency
html.find('.ability-proficiency').click(this._onToggleAbilityProficiency.bind(this)); html.find(".ability-proficiency").click(this._onToggleAbilityProficiency.bind(this));
// Toggle Skill Proficiency // Toggle Skill Proficiency
html.find('.skill-proficiency').on("click contextmenu", this._onCycleSkillProficiency.bind(this)); html.find(".skill-proficiency").on("click contextmenu", this._onCycleSkillProficiency.bind(this));
// Trait Selector // Trait Selector
html.find('.trait-selector').click(this._onTraitSelector.bind(this)); html.find(".trait-selector").click(this._onTraitSelector.bind(this));
// Configure Special Flags // Configure Special Flags
html.find('.config-button').click(this._onConfigMenu.bind(this)); html.find(".config-button").click(this._onConfigMenu.bind(this));
// Owned Item management // Owned Item management
html.find('.item-create').click(this._onItemCreate.bind(this)); html.find(".item-create").click(this._onItemCreate.bind(this));
html.find('.item-edit').click(this._onItemEdit.bind(this)); html.find(".item-edit").click(this._onItemEdit.bind(this));
html.find('.item-delete').click(this._onItemDelete.bind(this)); html.find(".item-delete").click(this._onItemDelete.bind(this));
html.find('.item-uses input').click(ev => ev.target.select()).change(this._onUsesChange.bind(this)); html
html.find('.slot-max-override').click(this._onPowerSlotOverride.bind(this)); .find(".item-uses input")
.click(ev => ev.target.select())
.change(this._onUsesChange.bind(this));
html.find(".slot-max-override").click(this._onPowerSlotOverride.bind(this));
// Active Effect management // Active Effect management
html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.entity)); html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.entity));
@ -416,17 +421,15 @@ export default class ActorSheet5e extends ActorSheet {
// Owner Only Listeners // Owner Only Listeners
if (this.actor.owner) { if (this.actor.owner) {
// Ability Checks // Ability Checks
html.find('.ability-name').click(this._onRollAbilityTest.bind(this)); html.find(".ability-name").click(this._onRollAbilityTest.bind(this));
// Roll Skill Checks // Roll Skill Checks
html.find('.skill-name').click(this._onRollSkillCheck.bind(this)); html.find(".skill-name").click(this._onRollSkillCheck.bind(this));
// Item Rolling // Item Rolling
html.find('.item .item-image').click(event => this._onItemRoll(event)); html.find(".item .item-image").click(event => this._onItemRoll(event));
html.find('.item .item-recharge').click(event => this._onItemRecharge(event)); html.find(".item .item-recharge").click(event => this._onItemRecharge(event));
} }
// Otherwise remove rollable classes // Otherwise remove rollable classes
@ -513,9 +516,9 @@ export default class ActorSheet5e extends ActorSheet {
// Toggle next level - forward on click, backwards on right // Toggle next level - forward on click, backwards on right
if (event.type === "click") { if (event.type === "click") {
field.val(levels[(idx === levels.length - 1) ? 0 : idx + 1]); field.val(levels[idx === levels.length - 1 ? 0 : idx + 1]);
} else if (event.type === "contextmenu") { } else if (event.type === "contextmenu") {
field.val(levels[(idx === 0) ? levels.length - 1 : idx - 1]); field.val(levels[idx === 0 ? levels.length - 1 : idx - 1]);
} }
// Update the field value and save the form // Update the field value and save the form
@ -526,7 +529,7 @@ export default class ActorSheet5e extends ActorSheet {
/** @override */ /** @override */
async _onDropActor(event, data) { async _onDropActor(event, data) {
const canPolymorph = game.user.isGM || (this.actor.owner && game.settings.get('sw5e', 'allowPolymorphing')); const canPolymorph = game.user.isGM || (this.actor.owner && game.settings.get("sw5e", "allowPolymorphing"));
if (!canPolymorph) return false; if (!canPolymorph) return false;
// Get the target actor // Get the target actor
@ -542,33 +545,35 @@ export default class ActorSheet5e extends ActorSheet {
// Define a function to record polymorph settings for future use // Define a function to record polymorph settings for future use
const rememberOptions = html => { const rememberOptions = html => {
const options = {}; const options = {};
html.find('input').each((i, el) => { html.find("input").each((i, el) => {
options[el.name] = el.checked; options[el.name] = el.checked;
}); });
const settings = mergeObject(game.settings.get('sw5e', 'polymorphSettings') || {}, options); const settings = mergeObject(game.settings.get("sw5e", "polymorphSettings") || {}, options);
game.settings.set('sw5e', 'polymorphSettings', settings); game.settings.set("sw5e", "polymorphSettings", settings);
return settings; return settings;
}; };
// Create and render the Dialog // Create and render the Dialog
return new Dialog({ return new Dialog(
title: game.i18n.localize('SW5E.PolymorphPromptTitle'), {
title: game.i18n.localize("SW5E.PolymorphPromptTitle"),
content: { content: {
options: game.settings.get('sw5e', 'polymorphSettings'), options: game.settings.get("sw5e", "polymorphSettings"),
i18n: SW5E.polymorphSettings, i18n: SW5E.polymorphSettings,
isToken: this.actor.isToken isToken: this.actor.isToken
}, },
default: 'accept', default: "accept",
buttons: { buttons: {
accept: { accept: {
icon: '<i class="fas fa-check"></i>', icon: '<i class="fas fa-check"></i>',
label: game.i18n.localize('SW5E.PolymorphAcceptSettings'), label: game.i18n.localize("SW5E.PolymorphAcceptSettings"),
callback: html => this.actor.transformInto(sourceActor, rememberOptions(html)) callback: html => this.actor.transformInto(sourceActor, rememberOptions(html))
}, },
wildshape: { wildshape: {
icon: '<i class="fas fa-paw"></i>', icon: '<i class="fas fa-paw"></i>',
label: game.i18n.localize('SW5E.PolymorphWildShape'), label: game.i18n.localize("SW5E.PolymorphWildShape"),
callback: html => this.actor.transformInto(sourceActor, { callback: html =>
this.actor.transformInto(sourceActor, {
keepBio: true, keepBio: true,
keepClass: true, keepClass: true,
keepMental: true, keepMental: true,
@ -579,30 +584,32 @@ export default class ActorSheet5e extends ActorSheet {
}, },
polymorph: { polymorph: {
icon: '<i class="fas fa-pastafarianism"></i>', icon: '<i class="fas fa-pastafarianism"></i>',
label: game.i18n.localize('SW5E.Polymorph'), label: game.i18n.localize("SW5E.Polymorph"),
callback: html => this.actor.transformInto(sourceActor, { callback: html =>
this.actor.transformInto(sourceActor, {
transformTokens: rememberOptions(html).transformTokens transformTokens: rememberOptions(html).transformTokens
}) })
}, },
cancel: { cancel: {
icon: '<i class="fas fa-times"></i>', icon: '<i class="fas fa-times"></i>',
label: game.i18n.localize('Cancel') label: game.i18n.localize("Cancel")
} }
} }
}, { },
classes: ['dialog', 'sw5e'], {
classes: ["dialog", "sw5e"],
width: 600, width: 600,
template: 'systems/sw5e/templates/apps/polymorph-prompt.html' template: "systems/sw5e/templates/apps/polymorph-prompt.html"
}).render(true); }
).render(true);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
async _onDropItemCreate(itemData) { async _onDropItemCreate(itemData) {
// Create a Consumable power scroll on the Inventory tab // Create a Consumable power scroll on the Inventory tab
if ( (itemData.type === "power") && (this._tabs[0].active === "inventory") ) { if (itemData.type === "power" && this._tabs[0].active === "inventory") {
const scroll = await Item5e.createScrollFromPower(itemData); const scroll = await Item5e.createScrollFromPower(itemData);
itemData = scroll.data; itemData = scroll.data;
} }
@ -653,7 +660,7 @@ export default class ActorSheet5e extends ActorSheet {
const item = this.actor.getOwnedItem(itemId); const item = this.actor.getOwnedItem(itemId);
const uses = Math.clamped(0, parseInt(event.target.value), item.data.data.uses.max); const uses = Math.clamped(0, parseInt(event.target.value), item.data.data.uses.max);
event.target.value = uses; event.target.value = uses;
return item.update({ 'data.uses.value': uses }); return item.update({"data.uses.value": uses});
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -681,7 +688,7 @@ export default class ActorSheet5e extends ActorSheet {
const itemId = event.currentTarget.closest(".item").dataset.itemId; const itemId = event.currentTarget.closest(".item").dataset.itemId;
const item = this.actor.getOwnedItem(itemId); const item = this.actor.getOwnedItem(itemId);
return item.rollRecharge(); return item.rollRecharge();
}; }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -826,7 +833,7 @@ export default class ActorSheet5e extends ActorSheet {
const label = a.parentElement.querySelector("label"); const label = a.parentElement.querySelector("label");
const choices = CONFIG.SW5E[a.dataset.options]; const choices = CONFIG.SW5E[a.dataset.options];
const options = {name: a.dataset.target, title: label.innerText, choices}; const options = {name: a.dataset.target, title: label.innerText, choices};
new TraitSelector(this.actor, options).render(true) new TraitSelector(this.actor, options).render(true);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -838,7 +845,7 @@ export default class ActorSheet5e extends ActorSheet {
// Add button to revert polymorph // Add button to revert polymorph
if (!this.actor.isPolymorphed || this.actor.isToken) return buttons; if (!this.actor.isPolymorphed || this.actor.isToken) return buttons;
buttons.unshift({ buttons.unshift({
label: 'SW5E.PolymorphRestoreTransformation', label: "SW5E.PolymorphRestoreTransformation",
class: "restore-transformation", class: "restore-transformation",
icon: "fas fa-backward", icon: "fas fa-backward",
onclick: ev => this.actor.revertOriginalForm() onclick: ev => this.actor.revertOriginalForm()

View file

@ -7,7 +7,6 @@ import Actor5e from "../../entity.js";
* @type {ActorSheet5e} * @type {ActorSheet5e}
*/ */
export default class ActorSheet5eCharacterNew extends ActorSheet5e { export default class ActorSheet5eCharacterNew extends ActorSheet5e {
get template() { get template() {
if (!game.user.isGM && this.actor.limited) return "systems/sw5e/templates/actors/newActor/limited-sheet.html"; if (!game.user.isGM && this.actor.limited) return "systems/sw5e/templates/actors/newActor/limited-sheet.html";
return "systems/sw5e/templates/actors/newActor/character-sheet.html"; return "systems/sw5e/templates/actors/newActor/character-sheet.html";
@ -17,17 +16,18 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
* @return {Object} * @return {Object}
*/ */
static get defaultOptions() { static get defaultOptions() {
return mergeObject(super.defaultOptions, { return mergeObject(super.defaultOptions, {
classes: ["swalt", "sw5e", "sheet", "actor", "character"], classes: ["swalt", "sw5e", "sheet", "actor", "character"],
blockFavTab: true, blockFavTab: true,
subTabs: null, subTabs: null,
width: 800, width: 800,
tabs: [{ tabs: [
{
navSelector: ".root-tabs", navSelector: ".root-tabs",
contentSelector: ".sheet-body", contentSelector: ".sheet-body",
initial: "attributes" initial: "attributes"
}], }
]
}); });
} }
@ -57,9 +57,11 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
// Experience Tracking // Experience Tracking
sheetData["disableExperience"] = game.settings.get("sw5e", "disableExperienceTracking"); sheetData["disableExperience"] = game.settings.get("sw5e", "disableExperienceTracking");
sheetData["classLabels"] = this.actor.itemTypes.class.map(c => c.name).join(", "); sheetData["classLabels"] = this.actor.itemTypes.class.map(c => c.name).join(", ");
sheetData["multiclassLabels"] = this.actor.itemTypes.class.map(c => { sheetData["multiclassLabels"] = this.actor.itemTypes.class
return [c.data.data.archetype, c.name, c.data.data.levels].filterJoin(' ') .map(c => {
}).join(', '); return [c.data.data.archetype, c.name, c.data.data.levels].filterJoin(" ");
})
.join(", ");
// Return data for rendering // Return data for rendering
return sheetData; return sheetData;
@ -72,7 +74,6 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
* @private * @private
*/ */
_prepareItems(data) { _prepareItems(data) {
// Categorize items as inventory, powerbook, features, and classes // Categorize items as inventory, powerbook, features, and classes
const inventory = { const inventory = {
weapon: {label: "SW5E.ItemTypeWeaponPl", items: [], dataset: {type: "weapon"}}, weapon: {label: "SW5E.ItemTypeWeaponPl", items: [], dataset: {type: "weapon"}},
@ -84,11 +85,24 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
}; };
// Partition items by category // Partition items by category
let [items, forcepowers, techpowers, feats, classes, species, archetypes, classfeatures, backgrounds, fightingstyles, fightingmasteries, lightsaberforms] = data.items.reduce((arr, item) => { let [
items,
forcepowers,
techpowers,
feats,
classes,
species,
archetypes,
classfeatures,
backgrounds,
fightingstyles,
fightingmasteries,
lightsaberforms
] = data.items.reduce(
(arr, item) => {
// Item details // Item details
item.img = item.img || DEFAULT_TOKEN; item.img = item.img || DEFAULT_TOKEN;
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1); item.isStack = Number.isNumeric(item.data.quantity) && item.data.quantity !== 1;
item.attunement = { item.attunement = {
[CONFIG.SW5E.attunementTypes.REQUIRED]: { [CONFIG.SW5E.attunementTypes.REQUIRED]: {
icon: "fa-sun", icon: "fa-sun",
@ -103,10 +117,10 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
}[item.data.attunement]; }[item.data.attunement];
// Item usage // Item usage
item.hasUses = item.data.uses && (item.data.uses.max > 0); item.hasUses = item.data.uses && item.data.uses.max > 0;
item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false); item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && item.data.recharge.charged === false;
item.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0)); item.isDepleted = item.isOnCooldown && item.data.uses.per && item.data.uses.value > 0;
item.hasTarget = !!item.data.target && !(["none",""].includes(item.data.target.type)); item.hasTarget = !!item.data.target && !["none", ""].includes(item.data.target.type);
// Item toggle state // Item toggle state
this._prepareItemToggleState(item); this._prepareItemToggleState(item);
@ -125,7 +139,9 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
else if (item.type === "lightsaberform") arr[11].push(item); else if (item.type === "lightsaberform") arr[11].push(item);
else if (Object.keys(inventory).includes(item.type)) arr[0].push(item); else if (Object.keys(inventory).includes(item.type)) arr[0].push(item);
return arr; return arr;
}, [[], [], [], [], [], [], [], [], [], [], [], []]); },
[[], [], [], [], [], [], [], [], [], [], [], []]
);
// Apply active item filters // Apply active item filters
items = this._filterItems(items, this._filters.inventory); items = this._filterItems(items, this._filters.inventory);
@ -148,14 +164,61 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
// Organize Features // Organize Features
const features = { const features = {
classes: {label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true}, classes: {label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true},
classfeatures: { label: "SW5E.ItemTypeClassFeats", items: [], hasActions: true, dataset: {type: "classfeature"}, isClassfeature: true }, classfeatures: {
archetype: { label: "SW5E.ItemTypeArchetype", items: [], hasActions: false, dataset: {type: "archetype"}, isArchetype: true }, label: "SW5E.ItemTypeClassFeats",
species: { label: "SW5E.ItemTypeSpecies", items: [], hasActions: false, dataset: {type: "species"}, isSpecies: true }, items: [],
background: { label: "SW5E.ItemTypeBackground", items: [], hasActions: false, dataset: {type: "background"}, isBackground: true }, hasActions: true,
fightingstyles: { label: "SW5E.ItemTypeFightingStylePl", items: [], hasActions: false, dataset: {type: "fightingstyle"}, isFightingstyle: true }, dataset: {type: "classfeature"},
fightingmasteries: { label: "SW5E.ItemTypeFightingMasteryPl", items: [], hasActions: false, dataset: {type: "fightingmastery"}, isFightingmastery: true }, isClassfeature: true
lightsaberforms: { label: "SW5E.ItemTypeLightsaberFormPl", items: [], hasActions: false, dataset: {type: "lightsaberform"}, isLightsaberform: true }, },
active: { label: "SW5E.FeatureActive", items: [], hasActions: true, dataset: {type: "feat", "activation.type": "action"} }, archetype: {
label: "SW5E.ItemTypeArchetype",
items: [],
hasActions: false,
dataset: {type: "archetype"},
isArchetype: true
},
species: {
label: "SW5E.ItemTypeSpecies",
items: [],
hasActions: false,
dataset: {type: "species"},
isSpecies: true
},
background: {
label: "SW5E.ItemTypeBackground",
items: [],
hasActions: false,
dataset: {type: "background"},
isBackground: true
},
fightingstyles: {
label: "SW5E.ItemTypeFightingStylePl",
items: [],
hasActions: false,
dataset: {type: "fightingstyle"},
isFightingstyle: true
},
fightingmasteries: {
label: "SW5E.ItemTypeFightingMasteryPl",
items: [],
hasActions: false,
dataset: {type: "fightingmastery"},
isFightingmastery: true
},
lightsaberforms: {
label: "SW5E.ItemTypeLightsaberFormPl",
items: [],
hasActions: false,
dataset: {type: "lightsaberform"},
isLightsaberform: true
},
active: {
label: "SW5E.FeatureActive",
items: [],
hasActions: true,
dataset: {type: "feat", "activation.type": "action"}
},
passive: {label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"}} passive: {label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"}}
}; };
for (let f of feats) { for (let f of feats) {
@ -195,8 +258,7 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
if (isAlways) item.toggleTitle = CONFIG.SW5E.powerPreparationModes.always; if (isAlways) item.toggleTitle = CONFIG.SW5E.powerPreparationModes.always;
else if (isPrepared) item.toggleTitle = CONFIG.SW5E.powerPreparationModes.prepared; else if (isPrepared) item.toggleTitle = CONFIG.SW5E.powerPreparationModes.prepared;
else item.toggleTitle = game.i18n.localize("SW5E.PowerUnprepared"); else item.toggleTitle = game.i18n.localize("SW5E.PowerUnprepared");
} } else {
else {
const isActive = getProperty(item.data, "equipped"); const isActive = getProperty(item.data, "equipped");
item.toggleClass = isActive ? "active" : ""; item.toggleClass = isActive ? "active" : "";
item.toggleTitle = game.i18n.localize(isActive ? "SW5E.Equipped" : "SW5E.Unequipped"); item.toggleTitle = game.i18n.localize(isActive ? "SW5E.Equipped" : "SW5E.Unequipped");
@ -219,11 +281,11 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
// html.find(".currency-convert").click(this._onConvertCurrency.bind(this)); // html.find(".currency-convert").click(this._onConvertCurrency.bind(this));
// Item State Toggling // Item State Toggling
html.find('.item-toggle').click(this._onToggleItem.bind(this)); html.find(".item-toggle").click(this._onToggleItem.bind(this));
// Short and Long Rest // Short and Long Rest
html.find('.short-rest').click(this._onShortRest.bind(this)); html.find(".short-rest").click(this._onShortRest.bind(this));
html.find('.long-rest').click(this._onLongRest.bind(this)); html.find(".long-rest").click(this._onLongRest.bind(this));
// Rollable sheet actions // Rollable sheet actions
html.find(".rollable[data-action]").click(this._onSheetAction.bind(this)); html.find(".rollable[data-action]").click(this._onSheetAction.bind(this));
@ -263,9 +325,9 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
}); });
// Item Delete Confirmation // Item Delete Confirmation
html.find('.item-delete').off("click"); html.find(".item-delete").off("click");
html.find('.item-delete').click(event => { html.find(".item-delete").click(event => {
let li = $(event.currentTarget).parents('.item'); let li = $(event.currentTarget).parents(".item");
let itemId = li.attr("data-item-id"); let itemId = li.attr("data-item-id");
let item = this.actor.getOwnedItem(itemId); let item = this.actor.getOwnedItem(itemId);
new Dialog({ new Dialog({
@ -274,17 +336,17 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
buttons: { buttons: {
Yes: { Yes: {
icon: '<i class="fa fa-check"></i>', icon: '<i class="fa fa-check"></i>',
label: 'Yes', label: "Yes",
callback: dlg => { callback: dlg => {
this.actor.deleteOwnedItem(itemId); this.actor.deleteOwnedItem(itemId);
} }
}, },
cancel: { cancel: {
icon: '<i class="fas fa-times"></i>', icon: '<i class="fas fa-times"></i>',
label: 'No' label: "No"
}
}, },
}, default: "cancel"
default: 'cancel'
}).render(true); }).render(true);
}); });
} }
@ -309,7 +371,6 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**
* Handle toggling the state of an Owned Item within the Actor * Handle toggling the state of an Owned Item within the Actor
* @param {Event} event The triggering click event * @param {Event} event The triggering click event
@ -353,7 +414,6 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
/** @override */ /** @override */
async _onDropItemCreate(itemData) { async _onDropItemCreate(itemData) {
// Increment the number of class levels a character instead of creating a new item // Increment the number of class levels a character instead of creating a new item
if (itemData.type === "class") { if (itemData.type === "class") {
const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name); const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name);
@ -428,9 +488,9 @@ async function addFavorites(app, html, data) {
value: data.actor.data.powers.power9.value, value: data.actor.data.powers.power9.value,
max: data.actor.data.powers.power9.max max: data.actor.data.powers.power9.max
} }
} };
let powerCount = 0 let powerCount = 0;
let items = data.actor.items; let items = data.actor.items;
for (let item of items) { for (let item of items) {
if (item.type == "class") continue; if (item.type == "class") continue;
@ -441,24 +501,28 @@ async function addFavorites(app, html, data) {
} }
let isFav = item.flags.favtab.isFavourite; let isFav = item.flags.favtab.isFavourite;
if (app.options.editable) { if (app.options.editable) {
let favBtn = $(`<a class="item-control item-toggle item-fav ${isFav ? "active" : ""}" data-fav="${isFav}" title="${isFav ? "Remove from Favourites" : "Add to Favourites"}"><i class="fas fa-star"></i></a>`); let favBtn = $(
`<a class="item-control item-toggle item-fav ${isFav ? "active" : ""}" data-fav="${isFav}" title="${
isFav ? "Remove from Favourites" : "Add to Favourites"
}"><i class="fas fa-star"></i></a>`
);
favBtn.click(ev => { favBtn.click(ev => {
app.actor.getOwnedItem(item._id).update({ app.actor.getOwnedItem(item._id).update({
"flags.favtab.isFavourite": !item.flags.favtab.isFavourite "flags.favtab.isFavourite": !item.flags.favtab.isFavourite
}); });
}); });
html.find(`.item[data-item-id="${item._id}"]`).find('.item-controls').prepend(favBtn); html.find(`.item[data-item-id="${item._id}"]`).find(".item-controls").prepend(favBtn);
} }
if (isFav) { if (isFav) {
item.powerComps = ""; item.powerComps = "";
if (item.data.components) { if (item.data.components) {
let comps = item.data.components; let comps = item.data.components;
let v = (comps.vocal) ? "V" : ""; let v = comps.vocal ? "V" : "";
let s = (comps.somatic) ? "S" : ""; let s = comps.somatic ? "S" : "";
let m = (comps.material) ? "M" : ""; let m = comps.material ? "M" : "";
let c = (comps.concentration) ? true : false; let c = comps.concentration ? true : false;
let r = (comps.ritual) ? true : false; let r = comps.ritual ? true : false;
item.powerComps = `${v}${s}${m}`; item.powerComps = `${v}${s}${m}`;
item.powerCon = c; item.powerCon = c;
item.powerRit = r; item.powerRit = r;
@ -466,15 +530,15 @@ async function addFavorites(app, html, data) {
item.editable = app.options.editable; item.editable = app.options.editable;
switch (item.type) { switch (item.type) {
case 'feat': case "feat":
if (item.flags.favtab.sort === undefined) { if (item.flags.favtab.sort === undefined) {
item.flags.favtab.sort = (favFeats.count + 1) * 100000; // initial sort key if not present item.flags.favtab.sort = (favFeats.count + 1) * 100000; // initial sort key if not present
} }
favFeats.push(item); favFeats.push(item);
break; break;
case 'power': case "power":
if (item.data.preparation.mode) { if (item.data.preparation.mode) {
item.powerPrepMode = ` (${CONFIG.SW5E.powerPreparationModes[item.data.preparation.mode]})` item.powerPrepMode = ` (${CONFIG.SW5E.powerPreparationModes[item.data.preparation.mode]})`;
} }
if (item.data.level) { if (item.data.level) {
favPowers[item.data.level].powers.push(item); favPowers[item.data.level].powers.push(item);
@ -500,58 +564,58 @@ async function addFavorites(app, html, data) {
// html.find('.favourite .item-controls').css('flex', '0 0 22px'); // html.find('.favourite .item-controls').css('flex', '0 0 22px');
// } // }
let tabContainer = html.find('.favtabtarget'); let tabContainer = html.find(".favtabtarget");
data.favItems = favItems.length > 0 ? favItems.sort((a, b) => (a.flags.favtab.sort) - (b.flags.favtab.sort)) : false; data.favItems = favItems.length > 0 ? favItems.sort((a, b) => a.flags.favtab.sort - b.flags.favtab.sort) : false;
data.favFeats = favFeats.length > 0 ? favFeats.sort((a, b) => (a.flags.favtab.sort) - (b.flags.favtab.sort)) : false; data.favFeats = favFeats.length > 0 ? favFeats.sort((a, b) => a.flags.favtab.sort - b.flags.favtab.sort) : false;
data.favPowers = powerCount > 0 ? favPowers : false; data.favPowers = powerCount > 0 ? favPowers : false;
data.editable = app.options.editable; data.editable = app.options.editable;
await loadTemplates(['systems/sw5e/templates/actors/newActor/item.hbs']); await loadTemplates(["systems/sw5e/templates/actors/newActor/item.hbs"]);
let favtabHtml = $(await renderTemplate('systems/sw5e/templates/actors/newActor/template.hbs', data)); let favtabHtml = $(await renderTemplate("systems/sw5e/templates/actors/newActor/template.hbs", data));
favtabHtml.find('.item-name h4').click(event => app._onItemSummary(event)); favtabHtml.find(".item-name h4").click(event => app._onItemSummary(event));
if (app.options.editable) { if (app.options.editable) {
favtabHtml.find('.item-image').click(ev => app._onItemRoll(ev)); favtabHtml.find(".item-image").click(ev => app._onItemRoll(ev));
let handler = ev => app._onDragStart(ev); let handler = ev => app._onDragStart(ev);
favtabHtml.find('.item').each((i, li) => { favtabHtml.find(".item").each((i, li) => {
if (li.classList.contains("inventory-header")) return; if (li.classList.contains("inventory-header")) return;
li.setAttribute("draggable", true); li.setAttribute("draggable", true);
li.addEventListener("dragstart", handler, false); li.addEventListener("dragstart", handler, false);
}); });
//favtabHtml.find('.item-toggle').click(event => app._onToggleItem(event)); //favtabHtml.find('.item-toggle').click(event => app._onToggleItem(event));
favtabHtml.find('.item-edit').click(ev => { favtabHtml.find(".item-edit").click(ev => {
let itemId = $(ev.target).parents('.item')[0].dataset.itemId; let itemId = $(ev.target).parents(".item")[0].dataset.itemId;
app.actor.getOwnedItem(itemId).sheet.render(true); app.actor.getOwnedItem(itemId).sheet.render(true);
}); });
favtabHtml.find('.item-fav').click(ev => { favtabHtml.find(".item-fav").click(ev => {
let itemId = $(ev.target).parents('.item')[0].dataset.itemId; let itemId = $(ev.target).parents(".item")[0].dataset.itemId;
let val = !app.actor.getOwnedItem(itemId).data.flags.favtab.isFavourite let val = !app.actor.getOwnedItem(itemId).data.flags.favtab.isFavourite;
app.actor.getOwnedItem(itemId).update({ app.actor.getOwnedItem(itemId).update({
"flags.favtab.isFavourite": val "flags.favtab.isFavourite": val
}); });
}); });
// Sorting // Sorting
favtabHtml.find('.item').on('drop', ev => { favtabHtml.find(".item").on("drop", ev => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
let dropData = JSON.parse(ev.originalEvent.dataTransfer.getData('text/plain')); let dropData = JSON.parse(ev.originalEvent.dataTransfer.getData("text/plain"));
// if (dropData.actorId !== app.actor.id || dropData.data.type === 'power') return; // if (dropData.actorId !== app.actor.id || dropData.data.type === 'power') return;
if (dropData.actorId !== app.actor.id) return; if (dropData.actorId !== app.actor.id) return;
let list = null; let list = null;
if (dropData.data.type === 'feat') list = favFeats; if (dropData.data.type === "feat") list = favFeats;
else list = favItems; else list = favItems;
let dragSource = list.find(i => i._id === dropData.data._id); let dragSource = list.find(i => i._id === dropData.data._id);
let siblings = list.filter(i => i._id !== dropData.data._id); let siblings = list.filter(i => i._id !== dropData.data._id);
let targetId = ev.target.closest('.item').dataset.itemId; let targetId = ev.target.closest(".item").dataset.itemId;
let dragTarget = siblings.find(s => s._id === targetId); let dragTarget = siblings.find(s => s._id === targetId);
if (dragTarget === undefined) return; if (dragTarget === undefined) return;
const sortUpdates = SortingHelpers.performIntegerSort(dragSource, { const sortUpdates = SortingHelpers.performIntegerSort(dragSource, {
target: dragTarget, target: dragTarget,
siblings: siblings, siblings: siblings,
sortKey: 'flags.favtab.sort' sortKey: "flags.favtab.sort"
}); });
const updateData = sortUpdates.map(u => { const updateData = sortUpdates.map(u => {
const update = u.update; const update = u.update;
@ -582,36 +646,39 @@ async function addSubTabs(app, html, data) {
if (data.options.subTabs == null) { if (data.options.subTabs == null) {
//let subTabs = []; //{subgroup: '', target: '', active: false} //let subTabs = []; //{subgroup: '', target: '', active: false}
data.options.subTabs = {}; data.options.subTabs = {};
html.find('[data-subgroup-selection] [data-subgroup]').each((idx, el) => { html.find("[data-subgroup-selection] [data-subgroup]").each((idx, el) => {
let subgroup = el.getAttribute('data-subgroup'); let subgroup = el.getAttribute("data-subgroup");
let target = el.getAttribute('data-target'); let target = el.getAttribute("data-target");
let targetObj = {target: target, active: el.classList.contains("active")} let targetObj = {target: target, active: el.classList.contains("active")};
if (data.options.subTabs.hasOwnProperty(subgroup)) { if (data.options.subTabs.hasOwnProperty(subgroup)) {
data.options.subTabs[subgroup].push(targetObj); data.options.subTabs[subgroup].push(targetObj);
} else { } else {
data.options.subTabs[subgroup] = []; data.options.subTabs[subgroup] = [];
data.options.subTabs[subgroup].push(targetObj); data.options.subTabs[subgroup].push(targetObj);
} }
}) });
} }
for (const group in data.options.subTabs) { for (const group in data.options.subTabs) {
data.options.subTabs[group].forEach(tab => { data.options.subTabs[group].forEach(tab => {
if (tab.active) { if (tab.active) {
html.find(`[data-subgroup=${group}][data-target=${tab.target}]`).addClass('active'); html.find(`[data-subgroup=${group}][data-target=${tab.target}]`).addClass("active");
} else { } else {
html.find(`[data-subgroup=${group}][data-target=${tab.target}]`).removeClass('active'); html.find(`[data-subgroup=${group}][data-target=${tab.target}]`).removeClass("active");
} }
}) });
} }
html.find('[data-subgroup-selection]').children().on('click', event => { html
let subgroup = event.target.closest('[data-subgroup]').getAttribute('data-subgroup'); .find("[data-subgroup-selection]")
let target = event.target.closest('[data-target]').getAttribute('data-target'); .children()
html.find(`[data-subgroup=${subgroup}]`).removeClass('active'); .on("click", event => {
html.find(`[data-subgroup=${subgroup}][data-target=${target}]`).addClass('active'); let subgroup = event.target.closest("[data-subgroup]").getAttribute("data-subgroup");
let target = event.target.closest("[data-target]").getAttribute("data-target");
html.find(`[data-subgroup=${subgroup}]`).removeClass("active");
html.find(`[data-subgroup=${subgroup}][data-target=${target}]`).addClass("active");
let tabId = data.options.subTabs[subgroup].find(tab => { let tabId = data.options.subTabs[subgroup].find(tab => {
return tab.target == target return tab.target == target;
}); });
data.options.subTabs[subgroup].map(el => { data.options.subTabs[subgroup].map(el => {
if (el.target == target) { if (el.target == target) {
@ -620,12 +687,8 @@ async function addSubTabs(app, html, data) {
el.active = false; el.active = false;
} }
return el; return el;
}) });
});
})
} }
Hooks.on("renderActorSheet5eCharacterNew", (app, html, data) => { Hooks.on("renderActorSheet5eCharacterNew", (app, html, data) => {

View file

@ -6,7 +6,6 @@ import ActorSheet5e from "./base.js";
* @extends {ActorSheet5e} * @extends {ActorSheet5e}
*/ */
export default class ActorSheet5eNPCNew extends ActorSheet5e { export default class ActorSheet5eNPCNew extends ActorSheet5e {
/** @override */ /** @override */
get template() { get template() {
if (!game.user.isGM && this.actor.limited) return "systems/sw5e/templates/actors/newActor/limited-sheet.html"; if (!game.user.isGM && this.actor.limited) return "systems/sw5e/templates/actors/newActor/limited-sheet.html";
@ -17,11 +16,13 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
return mergeObject(super.defaultOptions, { return mergeObject(super.defaultOptions, {
classes: ["sw5e", "sheet", "actor", "npc"], classes: ["sw5e", "sheet", "actor", "npc"],
width: 800, width: 800,
tabs: [{ tabs: [
{
navSelector: ".root-tabs", navSelector: ".root-tabs",
contentSelector: ".sheet-body", contentSelector: ".sheet-body",
initial: "attributes" initial: "attributes"
}], }
]
}); });
} }
@ -32,28 +33,40 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
* @private * @private
*/ */
_prepareItems(data) { _prepareItems(data) {
// Categorize Items as Features and Powers // Categorize Items as Features and Powers
const features = { const features = {
weapons: { label: game.i18n.localize("SW5E.AttackPl"), items: [] , hasActions: true, dataset: {type: "weapon", "weapon-type": "natural"} }, weapons: {
actions: { label: game.i18n.localize("SW5E.ActionPl"), items: [] , hasActions: true, dataset: {type: "feat", "activation.type": "action"} }, label: game.i18n.localize("SW5E.AttackPl"),
items: [],
hasActions: true,
dataset: {type: "weapon", "weapon-type": "natural"}
},
actions: {
label: game.i18n.localize("SW5E.ActionPl"),
items: [],
hasActions: true,
dataset: {type: "feat", "activation.type": "action"}
},
passive: {label: game.i18n.localize("SW5E.Features"), items: [], dataset: {type: "feat"}}, passive: {label: game.i18n.localize("SW5E.Features"), items: [], dataset: {type: "feat"}},
equipment: {label: game.i18n.localize("SW5E.Inventory"), items: [], dataset: {type: "loot"}} equipment: {label: game.i18n.localize("SW5E.Inventory"), items: [], dataset: {type: "loot"}}
}; };
// Start by classifying items into groups for rendering // Start by classifying items into groups for rendering
let [forcepowers, techpowers, other] = data.items.reduce((arr, item) => { let [forcepowers, techpowers, other] = data.items.reduce(
(arr, item) => {
item.img = item.img || DEFAULT_TOKEN; item.img = item.img || DEFAULT_TOKEN;
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1); item.isStack = Number.isNumeric(item.data.quantity) && item.data.quantity !== 1;
item.hasUses = item.data.uses && (item.data.uses.max > 0); item.hasUses = item.data.uses && item.data.uses.max > 0;
item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false); item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && item.data.recharge.charged === false;
item.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0)); item.isDepleted = item.isOnCooldown && item.data.uses.per && item.data.uses.value > 0;
item.hasTarget = !!item.data.target && !(["none",""].includes(item.data.target.type)); item.hasTarget = !!item.data.target && !["none", ""].includes(item.data.target.type);
if (item.type === "power" && ["lgt", "drk", "uni"].includes(item.data.school)) arr[0].push(item); if (item.type === "power" && ["lgt", "drk", "uni"].includes(item.data.school)) arr[0].push(item);
else if (item.type === "power" && ["tec"].includes(item.data.school)) arr[1].push(item); else if (item.type === "power" && ["tec"].includes(item.data.school)) arr[1].push(item);
else arr[2].push(item); else arr[2].push(item);
return arr; return arr;
}, [[], [], []]); },
[[], [], []]
);
// Apply item filters // Apply item filters
forcepowers = this._filterItems(forcepowers, this._filters.forcePowerbook); forcepowers = this._filterItems(forcepowers, this._filters.forcePowerbook);
@ -70,8 +83,7 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
else if (item.type === "feat") { else if (item.type === "feat") {
if (item.data.activation.type) features.actions.items.push(item); if (item.data.activation.type) features.actions.items.push(item);
else features.passive.items.push(item); else features.passive.items.push(item);
} } else features.equipment.items.push(item);
else features.equipment.items.push(item);
} }
// Assign and return // Assign and return
@ -80,7 +92,6 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
data.techPowerbook = techPowerbook; data.techPowerbook = techPowerbook;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
@ -100,7 +111,6 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
/** @override */ /** @override */
_updateObject(event, formData) { _updateObject(event, formData) {
// Format NPC Challenge Rating // Format NPC Challenge Rating
const crs = {"1/8": 0.125, "1/4": 0.25, "1/2": 0.5}; const crs = {"1/8": 0.125, "1/4": 0.25, "1/2": 0.5};
let crv = "data.details.cr"; let crv = "data.details.cr";
@ -138,4 +148,3 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
this.actor.update({"data.attributes.hp.value": hp, "data.attributes.hp.max": hp}); this.actor.update({"data.attributes.hp.value": hp, "data.attributes.hp.max": hp});
} }
} }

View file

@ -25,7 +25,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
*/ */
static get newCargo() { static get newCargo() {
return { return {
name: '', name: "",
quantity: 1 quantity: 1
}; };
} }
@ -40,7 +40,6 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
* @private * @private
*/ */
_computeEncumbrance(totalWeight, actorData) { _computeEncumbrance(totalWeight, actorData) {
// Compute currency weight // Compute currency weight
const totalCoins = Object.values(actorData.data.currency).reduce((acc, denom) => acc + denom, 0); const totalCoins = Object.values(actorData.data.currency).reduce((acc, denom) => acc + denom, 0);
totalWeight += totalCoins / CONFIG.SW5E.encumbrance.currencyPerWeight; totalWeight += totalCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
@ -69,25 +68,24 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
* @private * @private
*/ */
_prepareCrewedItem(item) { _prepareCrewedItem(item) {
// Determine crewed status // Determine crewed status
const isCrewed = item.data.crewed; const isCrewed = item.data.crewed;
item.toggleClass = isCrewed ? 'active' : ''; item.toggleClass = isCrewed ? "active" : "";
item.toggleTitle = game.i18n.localize(`SW5E.${isCrewed ? 'Crewed' : 'Uncrewed'}`); item.toggleTitle = game.i18n.localize(`SW5E.${isCrewed ? "Crewed" : "Uncrewed"}`);
// Handle crew actions // Handle crew actions
if (item.type === 'feat' && item.data.activation.type === 'crew') { if (item.type === "feat" && item.data.activation.type === "crew") {
item.crew = item.data.activation.cost; item.crew = item.data.activation.cost;
item.cover = game.i18n.localize(`SW5E.${item.data.cover ? 'CoverTotal' : 'None'}`); item.cover = game.i18n.localize(`SW5E.${item.data.cover ? "CoverTotal" : "None"}`);
if (item.data.cover === .5) item.cover = '½'; if (item.data.cover === 0.5) item.cover = "½";
else if (item.data.cover === .75) item.cover = '¾'; else if (item.data.cover === 0.75) item.cover = "¾";
else if (item.data.cover === null) item.cover = '—'; else if (item.data.cover === null) item.cover = "—";
if (item.crew < 1 || item.crew === null) item.crew = '—'; if (item.crew < 1 || item.crew === null) item.crew = "—";
} }
// Prepare vehicle weapons // Prepare vehicle weapons
if (item.type === 'equipment' || item.type === 'weapon') { if (item.type === "equipment" || item.type === "weapon") {
item.threshold = item.data.hp.dt ? item.data.hp.dt : '—'; item.threshold = item.data.hp.dt ? item.data.hp.dt : "—";
} }
} }
@ -98,128 +96,140 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
* @private * @private
*/ */
_prepareItems(data) { _prepareItems(data) {
const cargoColumns = [{ const cargoColumns = [
label: game.i18n.localize('SW5E.Quantity'), {
css: 'item-qty', label: game.i18n.localize("SW5E.Quantity"),
property: 'quantity', css: "item-qty",
editable: 'Number' property: "quantity",
}]; editable: "Number"
}
];
const equipmentColumns = [{ const equipmentColumns = [
label: game.i18n.localize('SW5E.Quantity'), {
css: 'item-qty', label: game.i18n.localize("SW5E.Quantity"),
property: 'data.quantity' css: "item-qty",
}, { property: "data.quantity"
label: game.i18n.localize('SW5E.AC'), },
css: 'item-ac', {
property: 'data.armor.value' label: game.i18n.localize("SW5E.AC"),
}, { css: "item-ac",
label: game.i18n.localize('SW5E.HP'), property: "data.armor.value"
css: 'item-hp', },
property: 'data.hp.value', {
editable: 'Number' label: game.i18n.localize("SW5E.HP"),
}, { css: "item-hp",
label: game.i18n.localize('SW5E.Threshold'), property: "data.hp.value",
css: 'item-threshold', editable: "Number"
property: 'threshold' },
}]; {
label: game.i18n.localize("SW5E.Threshold"),
css: "item-threshold",
property: "threshold"
}
];
const features = { const features = {
actions: { actions: {
label: game.i18n.localize('SW5E.ActionPl'), label: game.i18n.localize("SW5E.ActionPl"),
items: [], items: [],
crewable: true, crewable: true,
dataset: {type: 'feat', 'activation.type': 'crew'}, dataset: {type: "feat", "activation.type": "crew"},
columns: [{ columns: [
label: game.i18n.localize('SW5E.VehicleCrew'), {
css: 'item-crew', label: game.i18n.localize("SW5E.VehicleCrew"),
property: 'crew' css: "item-crew",
}, { property: "crew"
label: game.i18n.localize('SW5E.Cover'), },
css: 'item-cover', {
property: 'cover' label: game.i18n.localize("SW5E.Cover"),
}] css: "item-cover",
property: "cover"
}
]
}, },
equipment: { equipment: {
label: game.i18n.localize('SW5E.ItemTypeEquipment'), label: game.i18n.localize("SW5E.ItemTypeEquipment"),
items: [], items: [],
crewable: true, crewable: true,
dataset: {type: 'equipment', 'armor.type': 'vehicle'}, dataset: {type: "equipment", "armor.type": "vehicle"},
columns: equipmentColumns columns: equipmentColumns
}, },
passive: { passive: {
label: game.i18n.localize('SW5E.Features'), label: game.i18n.localize("SW5E.Features"),
items: [], items: [],
dataset: {type: 'feat'} dataset: {type: "feat"}
}, },
reactions: { reactions: {
label: game.i18n.localize('SW5E.ReactionPl'), label: game.i18n.localize("SW5E.ReactionPl"),
items: [], items: [],
dataset: {type: 'feat', 'activation.type': 'reaction'} dataset: {type: "feat", "activation.type": "reaction"}
}, },
weapons: { weapons: {
label: game.i18n.localize('SW5E.ItemTypeWeaponPl'), label: game.i18n.localize("SW5E.ItemTypeWeaponPl"),
items: [], items: [],
crewable: true, crewable: true,
dataset: {type: 'weapon', 'weapon-type': 'siege'}, dataset: {type: "weapon", "weapon-type": "siege"},
columns: equipmentColumns columns: equipmentColumns
} }
}; };
const cargo = { const cargo = {
crew: { crew: {
label: game.i18n.localize('SW5E.VehicleCrew'), label: game.i18n.localize("SW5E.VehicleCrew"),
items: data.data.cargo.crew, items: data.data.cargo.crew,
css: 'cargo-row crew', css: "cargo-row crew",
editableName: true, editableName: true,
dataset: {type: 'crew'}, dataset: {type: "crew"},
columns: cargoColumns columns: cargoColumns
}, },
passengers: { passengers: {
label: game.i18n.localize('SW5E.VehiclePassengers'), label: game.i18n.localize("SW5E.VehiclePassengers"),
items: data.data.cargo.passengers, items: data.data.cargo.passengers,
css: 'cargo-row passengers', css: "cargo-row passengers",
editableName: true, editableName: true,
dataset: {type: 'passengers'}, dataset: {type: "passengers"},
columns: cargoColumns columns: cargoColumns
}, },
cargo: { cargo: {
label: game.i18n.localize('SW5E.VehicleCargo'), label: game.i18n.localize("SW5E.VehicleCargo"),
items: [], items: [],
dataset: {type: 'loot'}, dataset: {type: "loot"},
columns: [{ columns: [
label: game.i18n.localize('SW5E.Quantity'), {
css: 'item-qty', label: game.i18n.localize("SW5E.Quantity"),
property: 'data.quantity', css: "item-qty",
editable: 'Number' property: "data.quantity",
}, { editable: "Number"
label: game.i18n.localize('SW5E.Price'), },
css: 'item-price', {
property: 'data.price', label: game.i18n.localize("SW5E.Price"),
editable: 'Number' css: "item-price",
}, { property: "data.price",
label: game.i18n.localize('SW5E.Weight'), editable: "Number"
css: 'item-weight', },
property: 'data.weight', {
editable: 'Number' label: game.i18n.localize("SW5E.Weight"),
}] css: "item-weight",
property: "data.weight",
editable: "Number"
}
]
} }
}; };
let totalWeight = 0; let totalWeight = 0;
for (const item of data.items) { for (const item of data.items) {
this._prepareCrewedItem(item); this._prepareCrewedItem(item);
if (item.type === 'weapon') features.weapons.items.push(item); if (item.type === "weapon") features.weapons.items.push(item);
else if (item.type === 'equipment') features.equipment.items.push(item); else if (item.type === "equipment") features.equipment.items.push(item);
else if (item.type === 'loot') { else if (item.type === "loot") {
totalWeight += (item.data.weight || 0) * item.data.quantity; totalWeight += (item.data.weight || 0) * item.data.quantity;
cargo.cargo.items.push(item); cargo.cargo.items.push(item);
} } else if (item.type === "feat") {
else if (item.type === 'feat') { if (!item.data.activation.type || item.data.activation.type === "none") {
if (!item.data.activation.type || item.data.activation.type === 'none') {
features.passive.items.push(item); features.passive.items.push(item);
} } else if (item.data.activation.type === "reaction") features.reactions.items.push(item);
else if (item.data.activation.type === 'reaction') features.reactions.items.push(item);
else features.actions.items.push(item); else features.actions.items.push(item);
} }
} }
@ -238,21 +248,24 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
super.activateListeners(html); super.activateListeners(html);
if (!this.options.editable) return; if (!this.options.editable) return;
html.find('.item-toggle').click(this._onToggleItem.bind(this)); html.find(".item-toggle").click(this._onToggleItem.bind(this));
html.find('.item-hp input') html
.find(".item-hp input")
.click(evt => evt.target.select()) .click(evt => evt.target.select())
.change(this._onHPChange.bind(this)); .change(this._onHPChange.bind(this));
html.find('.item:not(.cargo-row) input[data-property]') html
.find(".item:not(.cargo-row) input[data-property]")
.click(evt => evt.target.select()) .click(evt => evt.target.select())
.change(this._onEditInSheet.bind(this)); .change(this._onEditInSheet.bind(this));
html.find('.cargo-row input') html
.find(".cargo-row input")
.click(evt => evt.target.select()) .click(evt => evt.target.select())
.change(this._onCargoRowChange.bind(this)); .change(this._onCargoRowChange.bind(this));
if (this.actor.data.data.attributes.actions.stations) { if (this.actor.data.data.attributes.actions.stations) {
html.find('.counter.actions, .counter.action-thresholds').hide(); html.find(".counter.actions, .counter.action-thresholds").hide();
} }
} }
@ -267,9 +280,9 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
_onCargoRowChange(event) { _onCargoRowChange(event) {
event.preventDefault(); event.preventDefault();
const target = event.currentTarget; const target = event.currentTarget;
const row = target.closest('.item'); const row = target.closest(".item");
const idx = Number(row.dataset.itemId); const idx = Number(row.dataset.itemId);
const property = row.classList.contains('crew') ? 'crew' : 'passengers'; const property = row.classList.contains("crew") ? "crew" : "passengers";
// Get the cargo entry // Get the cargo entry
const cargo = duplicate(this.actor.data.data.cargo[property]); const cargo = duplicate(this.actor.data.data.cargo[property]);
@ -277,10 +290,10 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
if (!entry) return null; if (!entry) return null;
// Update the cargo value // Update the cargo value
const key = target.dataset.property || 'name'; const key = target.dataset.property || "name";
const type = target.dataset.dtype; const type = target.dataset.dtype;
let value = target.value; let value = target.value;
if (type === 'Number') value = Number(value); if (type === "Number") value = Number(value);
entry[key] = value; entry[key] = value;
// Perform the Actor update // Perform the Actor update
@ -297,14 +310,18 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
*/ */
_onEditInSheet(event) { _onEditInSheet(event) {
event.preventDefault(); event.preventDefault();
const itemID = event.currentTarget.closest('.item').dataset.itemId; const itemID = event.currentTarget.closest(".item").dataset.itemId;
const item = this.actor.items.get(itemID); const item = this.actor.items.get(itemID);
const property = event.currentTarget.dataset.property; const property = event.currentTarget.dataset.property;
const type = event.currentTarget.dataset.dtype; const type = event.currentTarget.dataset.dtype;
let value = event.currentTarget.value; let value = event.currentTarget.value;
switch (type) { switch (type) {
case 'Number': value = parseInt(value); break; case "Number":
case 'Boolean': value = value === 'true'; break; value = parseInt(value);
break;
case "Boolean":
value = value === "true";
break;
} }
return item.update({[`${property}`]: value}); return item.update({[`${property}`]: value});
} }
@ -321,7 +338,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
event.preventDefault(); event.preventDefault();
const target = event.currentTarget; const target = event.currentTarget;
const type = target.dataset.type; const type = target.dataset.type;
if (type === 'crew' || type === 'passengers') { if (type === "crew" || type === "passengers") {
const cargo = duplicate(this.actor.data.data.cargo[type]); const cargo = duplicate(this.actor.data.data.cargo[type]);
cargo.push(this.constructor.newCargo); cargo.push(this.constructor.newCargo);
return this.actor.update({[`data.cargo.${type}`]: cargo}); return this.actor.update({[`data.cargo.${type}`]: cargo});
@ -339,10 +356,10 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
*/ */
_onItemDelete(event) { _onItemDelete(event) {
event.preventDefault(); event.preventDefault();
const row = event.currentTarget.closest('.item'); const row = event.currentTarget.closest(".item");
if (row.classList.contains('cargo-row')) { if (row.classList.contains("cargo-row")) {
const idx = Number(row.dataset.itemId); const idx = Number(row.dataset.itemId);
const type = row.classList.contains('crew') ? 'crew' : 'passengers'; const type = row.classList.contains("crew") ? "crew" : "passengers";
const cargo = duplicate(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx); const cargo = duplicate(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx);
return this.actor.update({[`data.cargo.${type}`]: cargo}); return this.actor.update({[`data.cargo.${type}`]: cargo});
} }
@ -360,11 +377,11 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
*/ */
_onHPChange(event) { _onHPChange(event) {
event.preventDefault(); event.preventDefault();
const itemID = event.currentTarget.closest('.item').dataset.itemId; const itemID = event.currentTarget.closest(".item").dataset.itemId;
const item = this.actor.items.get(itemID); const item = this.actor.items.get(itemID);
const hp = Math.clamped(0, parseInt(event.currentTarget.value), item.data.data.hp.max); const hp = Math.clamped(0, parseInt(event.currentTarget.value), item.data.data.hp.max);
event.currentTarget.value = hp; event.currentTarget.value = hp;
return item.update({'data.hp.value': hp}); return item.update({"data.hp.value": hp});
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -377,9 +394,9 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
*/ */
_onToggleItem(event) { _onToggleItem(event) {
event.preventDefault(); event.preventDefault();
const itemID = event.currentTarget.closest('.item').dataset.itemId; const itemID = event.currentTarget.closest(".item").dataset.itemId;
const item = this.actor.items.get(itemID); const item = this.actor.items.get(itemID);
const crewed = !!item.data.data.crewed; const crewed = !!item.data.data.crewed;
return item.update({'data.crewed': !crewed}); return item.update({"data.crewed": !crewed});
}
} }
};

View file

@ -3,7 +3,7 @@ import TraitSelector from "../../../apps/trait-selector.js";
import ActorSheetFlags from "../../../apps/actor-flags.js"; import ActorSheetFlags from "../../../apps/actor-flags.js";
import ActorMovementConfig from "../../../apps/movement-config.js"; import ActorMovementConfig from "../../../apps/movement-config.js";
import ActorSensesConfig from "../../../apps/senses-config.js"; import ActorSensesConfig from "../../../apps/senses-config.js";
import {SW5E} from '../../../config.js'; import {SW5E} from "../../../config.js";
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js"; import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js";
/** /**
@ -54,7 +54,6 @@ export default class ActorSheet5e extends ActorSheet {
/** @override */ /** @override */
getData() { getData() {
// Basic data // Basic data
let isOwner = this.entity.owner; let isOwner = this.entity.owner;
const data = { const data = {
@ -65,8 +64,8 @@ export default class ActorSheet5e extends ActorSheet {
cssClass: isOwner ? "editable" : "locked", cssClass: isOwner ? "editable" : "locked",
isCharacter: this.entity.data.type === "character", isCharacter: this.entity.data.type === "character",
isNPC: this.entity.data.type === "npc", isNPC: this.entity.data.type === "npc",
isVehicle: this.entity.data.type === 'vehicle', isVehicle: this.entity.data.type === "vehicle",
config: CONFIG.SW5E, config: CONFIG.SW5E
}; };
// The Actor and its Items // The Actor and its Items
@ -113,7 +112,7 @@ export default class ActorSheet5e extends ActorSheet {
data.effects = prepareActiveEffectCategories(this.entity.effects); data.effects = prepareActiveEffectCategories(this.entity.effects);
// Return data to the sheet // Return data to the sheet
return data return data;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -132,9 +131,13 @@ export default class ActorSheet5e extends ActorSheet {
let speeds = [ let speeds = [
[movement.burrow, `${game.i18n.localize("SW5E.MovementBurrow")} ${movement.burrow}`], [movement.burrow, `${game.i18n.localize("SW5E.MovementBurrow")} ${movement.burrow}`],
[movement.climb, `${game.i18n.localize("SW5E.MovementClimb")} ${movement.climb}`], [movement.climb, `${game.i18n.localize("SW5E.MovementClimb")} ${movement.climb}`],
[movement.fly, `${game.i18n.localize("SW5E.MovementFly")} ${movement.fly}` + (movement.hover ? ` (${game.i18n.localize("SW5E.MovementHover")})` : "")], [
movement.fly,
`${game.i18n.localize("SW5E.MovementFly")} ${movement.fly}` +
(movement.hover ? ` (${game.i18n.localize("SW5E.MovementHover")})` : "")
],
[movement.swim, `${game.i18n.localize("SW5E.MovementSwim")} ${movement.swim}`] [movement.swim, `${game.i18n.localize("SW5E.MovementSwim")} ${movement.swim}`]
] ];
if (largestPrimary) { if (largestPrimary) {
speeds.push([movement.walk, `${game.i18n.localize("SW5E.MovementWalk")} ${movement.walk}`]); speeds.push([movement.walk, `${game.i18n.localize("SW5E.MovementWalk")} ${movement.walk}`]);
} }
@ -148,7 +151,7 @@ export default class ActorSheet5e extends ActorSheet {
return { return {
primary: `${primary ? primary[1] : "0"} ${movement.units}`, primary: `${primary ? primary[1] : "0"} ${movement.units}`,
special: speeds.map(s => s[1]).join(", ") special: speeds.map(s => s[1]).join(", ")
} };
} }
// Case 2: Walk as primary // Case 2: Walk as primary
@ -156,7 +159,7 @@ export default class ActorSheet5e extends ActorSheet {
return { return {
primary: `${movement.walk || 0} ${movement.units}`, primary: `${movement.walk || 0} ${movement.units}`,
special: speeds.length ? speeds.map(s => s[1]).join(", ") : "" special: speeds.length ? speeds.map(s => s[1]).join(", ") : ""
} };
} }
} }
@ -166,7 +169,7 @@ export default class ActorSheet5e extends ActorSheet {
const senses = actorData.data.attributes.senses || {}; const senses = actorData.data.attributes.senses || {};
const tags = {}; const tags = {};
for (let [k, label] of Object.entries(CONFIG.SW5E.senses)) { for (let [k, label] of Object.entries(CONFIG.SW5E.senses)) {
const v = senses[k] ?? 0 const v = senses[k] ?? 0;
if (v === 0) continue; if (v === 0) continue;
tags[k] = `${game.i18n.localize(label)} ${v} ${senses.units}`; tags[k] = `${game.i18n.localize(label)} ${v} ${senses.units}`;
} }
@ -183,14 +186,14 @@ export default class ActorSheet5e extends ActorSheet {
*/ */
_prepareTraits(traits) { _prepareTraits(traits) {
const map = { const map = {
"dr": CONFIG.SW5E.damageResistanceTypes, dr: CONFIG.SW5E.damageResistanceTypes,
"di": CONFIG.SW5E.damageResistanceTypes, di: CONFIG.SW5E.damageResistanceTypes,
"dv": CONFIG.SW5E.damageResistanceTypes, dv: CONFIG.SW5E.damageResistanceTypes,
"ci": CONFIG.SW5E.conditionTypes, ci: CONFIG.SW5E.conditionTypes,
"languages": CONFIG.SW5E.languages, languages: CONFIG.SW5E.languages,
"armorProf": CONFIG.SW5E.armorProficiencies, armorProf: CONFIG.SW5E.armorProficiencies,
"weaponProf": CONFIG.SW5E.weaponProficiencies, weaponProf: CONFIG.SW5E.weaponProficiencies,
"toolProf": CONFIG.SW5E.toolProficiencies toolProf: CONFIG.SW5E.toolProficiencies
}; };
for (let [t, choices] of Object.entries(map)) { for (let [t, choices] of Object.entries(map)) {
const trait = traits[t]; const trait = traits[t];
@ -206,7 +209,7 @@ export default class ActorSheet5e extends ActorSheet {
// Add custom entry // Add custom entry
if (trait.custom) { if (trait.custom) {
trait.custom.split(";").forEach((c, i) => trait.selected[`custom${i+1}`] = c.trim()); trait.custom.split(";").forEach((c, i) => (trait.selected[`custom${i + 1}`] = c.trim()));
} }
trait.cssClass = !isObjectEmpty(trait.selected) ? "" : "inactive"; trait.cssClass = !isObjectEmpty(trait.selected) ? "" : "inactive";
} }
@ -227,16 +230,16 @@ export default class ActorSheet5e extends ActorSheet {
// Define some mappings // Define some mappings
const sections = { const sections = {
"atwill": -20, atwill: -20,
"innate": -10, innate: -10,
"pact": 0.5 pact: 0.5
}; };
// Label power slot uses headers // Label power slot uses headers
const useLabels = { const useLabels = {
"-20": "-", "-20": "-",
"-10": "-", "-10": "-",
"0": "&infin;" 0: "&infin;"
}; };
// Format a powerbook entry for a certain indexed level // Format a powerbook entry for a certain indexed level
@ -246,12 +249,12 @@ export default class ActorSheet5e extends ActorSheet {
label: label, label: label,
usesSlots: i > 0, usesSlots: i > 0,
canCreate: owner, canCreate: owner,
canPrepare: (data.actor.type === "character") && (i >= 1), canPrepare: data.actor.type === "character" && i >= 1,
powers: [], powers: [],
uses: useLabels[i] || value || 0, uses: useLabels[i] || value || 0,
slots: useLabels[i] || max || 0, slots: useLabels[i] || max || 0,
override: override || 0, override: override || 0,
dataset: {"type": "power", "level": prepMode in sections ? 1 : i, "preparation.mode": prepMode}, dataset: {type: "power", level: prepMode in sections ? 1 : i, "preparation.mode": prepMode},
prop: sl prop: sl
}; };
}; };
@ -260,7 +263,7 @@ export default class ActorSheet5e extends ActorSheet {
const maxLevel = Array.fromRange(10).reduce((max, i) => { const maxLevel = Array.fromRange(10).reduce((max, i) => {
if (i === 0) return max; if (i === 0) return max;
const level = levels[`power${i}`]; const level = levels[`power${i}`];
if ( (level.max || level.override ) && ( i > max ) ) max = i; if ((level.max || level.override) && i > max) max = i;
return max; return max;
}, 0); }, 0);
@ -336,7 +339,7 @@ export default class ActorSheet5e extends ActorSheet {
// Action usage // Action usage
for (let f of ["action", "bonus", "reaction"]) { for (let f of ["action", "bonus", "reaction"]) {
if (filters.has(f)) { if (filters.has(f)) {
if ((data.activation && (data.activation.type !== f))) return false; if (data.activation && data.activation.type !== f) return false;
} }
} }
@ -386,41 +389,42 @@ export default class ActorSheet5e extends ActorSheet {
* @param html {HTML} The prepared HTML object ready to be rendered into the DOM * @param html {HTML} The prepared HTML object ready to be rendered into the DOM
*/ */
activateListeners(html) { activateListeners(html) {
// Activate Item Filters // Activate Item Filters
const filterLists = html.find(".filter-list"); const filterLists = html.find(".filter-list");
filterLists.each(this._initializeFilterItemList.bind(this)); filterLists.each(this._initializeFilterItemList.bind(this));
filterLists.on("click", ".filter-item", this._onToggleFilter.bind(this)); filterLists.on("click", ".filter-item", this._onToggleFilter.bind(this));
// Item summaries // Item summaries
html.find('.item .item-name.rollable h4').click(event => this._onItemSummary(event)); html.find(".item .item-name.rollable h4").click(event => this._onItemSummary(event));
// Editable Only Listeners // Editable Only Listeners
if (this.isEditable) { if (this.isEditable) {
// Input focus and update // Input focus and update
const inputs = html.find("input"); const inputs = html.find("input");
inputs.focus(ev => ev.currentTarget.select()); inputs.focus(ev => ev.currentTarget.select());
inputs.addBack().find('[data-dtype="Number"]').change(this._onChangeInputDelta.bind(this)); inputs.addBack().find('[data-dtype="Number"]').change(this._onChangeInputDelta.bind(this));
// Ability Proficiency // Ability Proficiency
html.find('.ability-proficiency').click(this._onToggleAbilityProficiency.bind(this)); html.find(".ability-proficiency").click(this._onToggleAbilityProficiency.bind(this));
// Toggle Skill Proficiency // Toggle Skill Proficiency
html.find('.skill-proficiency').on("click contextmenu", this._onCycleSkillProficiency.bind(this)); html.find(".skill-proficiency").on("click contextmenu", this._onCycleSkillProficiency.bind(this));
// Trait Selector // Trait Selector
html.find('.trait-selector').click(this._onTraitSelector.bind(this)); html.find(".trait-selector").click(this._onTraitSelector.bind(this));
// Configure Special Flags // Configure Special Flags
html.find('.config-button').click(this._onConfigMenu.bind(this)); html.find(".config-button").click(this._onConfigMenu.bind(this));
// Owned Item management // Owned Item management
html.find('.item-create').click(this._onItemCreate.bind(this)); html.find(".item-create").click(this._onItemCreate.bind(this));
html.find('.item-edit').click(this._onItemEdit.bind(this)); html.find(".item-edit").click(this._onItemEdit.bind(this));
html.find('.item-delete').click(this._onItemDelete.bind(this)); html.find(".item-delete").click(this._onItemDelete.bind(this));
html.find('.item-uses input').click(ev => ev.target.select()).change(this._onUsesChange.bind(this)); html
html.find('.slot-max-override').click(this._onPowerSlotOverride.bind(this)); .find(".item-uses input")
.click(ev => ev.target.select())
.change(this._onUsesChange.bind(this));
html.find(".slot-max-override").click(this._onPowerSlotOverride.bind(this));
// Active Effect management // Active Effect management
html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.entity)); html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.entity));
@ -428,17 +432,15 @@ export default class ActorSheet5e extends ActorSheet {
// Owner Only Listeners // Owner Only Listeners
if (this.actor.owner) { if (this.actor.owner) {
// Ability Checks // Ability Checks
html.find('.ability-name').click(this._onRollAbilityTest.bind(this)); html.find(".ability-name").click(this._onRollAbilityTest.bind(this));
// Roll Skill Checks // Roll Skill Checks
html.find('.skill-name').click(this._onRollSkillCheck.bind(this)); html.find(".skill-name").click(this._onRollSkillCheck.bind(this));
// Item Rolling // Item Rolling
html.find('.item .item-image').click(event => this._onItemRoll(event)); html.find(".item .item-image").click(event => this._onItemRoll(event));
html.find('.item .item-recharge').click(event => this._onItemRecharge(event)); html.find(".item .item-recharge").click(event => this._onItemRecharge(event));
} }
// Otherwise remove rollable classes // Otherwise remove rollable classes
@ -525,9 +527,9 @@ export default class ActorSheet5e extends ActorSheet {
// Toggle next level - forward on click, backwards on right // Toggle next level - forward on click, backwards on right
if (event.type === "click") { if (event.type === "click") {
field.val(levels[(idx === levels.length - 1) ? 0 : idx + 1]); field.val(levels[idx === levels.length - 1 ? 0 : idx + 1]);
} else if (event.type === "contextmenu") { } else if (event.type === "contextmenu") {
field.val(levels[(idx === 0) ? levels.length - 1 : idx - 1]); field.val(levels[idx === 0 ? levels.length - 1 : idx - 1]);
} }
// Update the field value and save the form // Update the field value and save the form
@ -538,7 +540,7 @@ export default class ActorSheet5e extends ActorSheet {
/** @override */ /** @override */
async _onDropActor(event, data) { async _onDropActor(event, data) {
const canPolymorph = game.user.isGM || (this.actor.owner && game.settings.get('sw5e', 'allowPolymorphing')); const canPolymorph = game.user.isGM || (this.actor.owner && game.settings.get("sw5e", "allowPolymorphing"));
if (!canPolymorph) return false; if (!canPolymorph) return false;
// Get the target actor // Get the target actor
@ -554,33 +556,35 @@ export default class ActorSheet5e extends ActorSheet {
// Define a function to record polymorph settings for future use // Define a function to record polymorph settings for future use
const rememberOptions = html => { const rememberOptions = html => {
const options = {}; const options = {};
html.find('input').each((i, el) => { html.find("input").each((i, el) => {
options[el.name] = el.checked; options[el.name] = el.checked;
}); });
const settings = mergeObject(game.settings.get('sw5e', 'polymorphSettings') || {}, options); const settings = mergeObject(game.settings.get("sw5e", "polymorphSettings") || {}, options);
game.settings.set('sw5e', 'polymorphSettings', settings); game.settings.set("sw5e", "polymorphSettings", settings);
return settings; return settings;
}; };
// Create and render the Dialog // Create and render the Dialog
return new Dialog({ return new Dialog(
title: game.i18n.localize('SW5E.PolymorphPromptTitle'), {
title: game.i18n.localize("SW5E.PolymorphPromptTitle"),
content: { content: {
options: game.settings.get('sw5e', 'polymorphSettings'), options: game.settings.get("sw5e", "polymorphSettings"),
i18n: SW5E.polymorphSettings, i18n: SW5E.polymorphSettings,
isToken: this.actor.isToken isToken: this.actor.isToken
}, },
default: 'accept', default: "accept",
buttons: { buttons: {
accept: { accept: {
icon: '<i class="fas fa-check"></i>', icon: '<i class="fas fa-check"></i>',
label: game.i18n.localize('SW5E.PolymorphAcceptSettings'), label: game.i18n.localize("SW5E.PolymorphAcceptSettings"),
callback: html => this.actor.transformInto(sourceActor, rememberOptions(html)) callback: html => this.actor.transformInto(sourceActor, rememberOptions(html))
}, },
wildshape: { wildshape: {
icon: '<i class="fas fa-paw"></i>', icon: '<i class="fas fa-paw"></i>',
label: game.i18n.localize('SW5E.PolymorphWildShape'), label: game.i18n.localize("SW5E.PolymorphWildShape"),
callback: html => this.actor.transformInto(sourceActor, { callback: html =>
this.actor.transformInto(sourceActor, {
keepBio: true, keepBio: true,
keepClass: true, keepClass: true,
keepMental: true, keepMental: true,
@ -591,30 +595,32 @@ export default class ActorSheet5e extends ActorSheet {
}, },
polymorph: { polymorph: {
icon: '<i class="fas fa-pastafarianism"></i>', icon: '<i class="fas fa-pastafarianism"></i>',
label: game.i18n.localize('SW5E.Polymorph'), label: game.i18n.localize("SW5E.Polymorph"),
callback: html => this.actor.transformInto(sourceActor, { callback: html =>
this.actor.transformInto(sourceActor, {
transformTokens: rememberOptions(html).transformTokens transformTokens: rememberOptions(html).transformTokens
}) })
}, },
cancel: { cancel: {
icon: '<i class="fas fa-times"></i>', icon: '<i class="fas fa-times"></i>',
label: game.i18n.localize('Cancel') label: game.i18n.localize("Cancel")
} }
} }
}, { },
classes: ['dialog', 'sw5e'], {
classes: ["dialog", "sw5e"],
width: 600, width: 600,
template: 'systems/sw5e/templates/apps/polymorph-prompt.html' template: "systems/sw5e/templates/apps/polymorph-prompt.html"
}).render(true); }
).render(true);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
async _onDropItemCreate(itemData) { async _onDropItemCreate(itemData) {
// Create a Consumable power scroll on the Inventory tab // Create a Consumable power scroll on the Inventory tab
if ( (itemData.type === "power") && (this._tabs[0].active === "inventory") ) { if (itemData.type === "power" && this._tabs[0].active === "inventory") {
const scroll = await Item5e.createScrollFromPower(itemData); const scroll = await Item5e.createScrollFromPower(itemData);
itemData = scroll.data; itemData = scroll.data;
} }
@ -665,7 +671,7 @@ export default class ActorSheet5e extends ActorSheet {
const item = this.actor.getOwnedItem(itemId); const item = this.actor.getOwnedItem(itemId);
const uses = Math.clamped(0, parseInt(event.target.value), item.data.data.uses.max); const uses = Math.clamped(0, parseInt(event.target.value), item.data.data.uses.max);
event.target.value = uses; event.target.value = uses;
return item.update({ 'data.uses.value': uses }); return item.update({"data.uses.value": uses});
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -693,7 +699,7 @@ export default class ActorSheet5e extends ActorSheet {
const itemId = event.currentTarget.closest(".item").dataset.itemId; const itemId = event.currentTarget.closest(".item").dataset.itemId;
const item = this.actor.getOwnedItem(itemId); const item = this.actor.getOwnedItem(itemId);
return item.rollRecharge(); return item.rollRecharge();
}; }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -838,7 +844,7 @@ export default class ActorSheet5e extends ActorSheet {
const label = a.parentElement.querySelector("label"); const label = a.parentElement.querySelector("label");
const choices = CONFIG.SW5E[a.dataset.options]; const choices = CONFIG.SW5E[a.dataset.options];
const options = {name: a.dataset.target, title: label.innerText, choices}; const options = {name: a.dataset.target, title: label.innerText, choices};
new TraitSelector(this.actor, options).render(true) new TraitSelector(this.actor, options).render(true);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -850,7 +856,7 @@ export default class ActorSheet5e extends ActorSheet {
// Add button to revert polymorph // Add button to revert polymorph
if (!this.actor.isPolymorphed || this.actor.isToken) return buttons; if (!this.actor.isPolymorphed || this.actor.isToken) return buttons;
buttons.unshift({ buttons.unshift({
label: 'SW5E.PolymorphRestoreTransformation', label: "SW5E.PolymorphRestoreTransformation",
class: "restore-transformation", class: "restore-transformation",
icon: "fas fa-backward", icon: "fas fa-backward",
onclick: ev => this.actor.revertOriginalForm() onclick: ev => this.actor.revertOriginalForm()

View file

@ -7,7 +7,6 @@ import Actor5e from "../../entity.js";
* @type {ActorSheet5e} * @type {ActorSheet5e}
*/ */
export default class ActorSheet5eCharacter extends ActorSheet5e { export default class ActorSheet5eCharacter extends ActorSheet5e {
/** /**
* Define default rendering options for the NPC sheet * Define default rendering options for the NPC sheet
* @return {Object} * @return {Object}
@ -46,9 +45,11 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
// Experience Tracking // Experience Tracking
sheetData["disableExperience"] = game.settings.get("sw5e", "disableExperienceTracking"); sheetData["disableExperience"] = game.settings.get("sw5e", "disableExperienceTracking");
sheetData["classLabels"] = this.actor.itemTypes.class.map(c => c.name).join(", "); sheetData["classLabels"] = this.actor.itemTypes.class.map(c => c.name).join(", ");
sheetData["multiclassLabels"] = this.actor.itemTypes.class.map(c => { sheetData["multiclassLabels"] = this.actor.itemTypes.class
return [c.data.data.archetype, c.name, c.data.data.levels].filterJoin(' ') .map(c => {
}).join(', '); return [c.data.data.archetype, c.name, c.data.data.levels].filterJoin(" ");
})
.join(", ");
// Return data for rendering // Return data for rendering
return sheetData; return sheetData;
@ -61,7 +62,6 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
* @private * @private
*/ */
_prepareItems(data) { _prepareItems(data) {
// Categorize items as inventory, powerbook, features, and classes // Categorize items as inventory, powerbook, features, and classes
const inventory = { const inventory = {
weapon: {label: "SW5E.ItemTypeWeaponPl", items: [], dataset: {type: "weapon"}}, weapon: {label: "SW5E.ItemTypeWeaponPl", items: [], dataset: {type: "weapon"}},
@ -73,11 +73,23 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
}; };
// Partition items by category // Partition items by category
let [items, powers, feats, classes, species, archetypes, classfeatures, backgrounds, fightingstyles, fightingmasteries, lightsaberforms] = data.items.reduce((arr, item) => { let [
items,
powers,
feats,
classes,
species,
archetypes,
classfeatures,
backgrounds,
fightingstyles,
fightingmasteries,
lightsaberforms
] = data.items.reduce(
(arr, item) => {
// Item details // Item details
item.img = item.img || DEFAULT_TOKEN; item.img = item.img || DEFAULT_TOKEN;
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1); item.isStack = Number.isNumeric(item.data.quantity) && item.data.quantity !== 1;
item.attunement = { item.attunement = {
[CONFIG.SW5E.attunementTypes.REQUIRED]: { [CONFIG.SW5E.attunementTypes.REQUIRED]: {
icon: "fa-sun", icon: "fa-sun",
@ -92,10 +104,10 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
}[item.data.attunement]; }[item.data.attunement];
// Item usage // Item usage
item.hasUses = item.data.uses && (item.data.uses.max > 0); item.hasUses = item.data.uses && item.data.uses.max > 0;
item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false); item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && item.data.recharge.charged === false;
item.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0)); item.isDepleted = item.isOnCooldown && item.data.uses.per && item.data.uses.value > 0;
item.hasTarget = !!item.data.target && !(["none",""].includes(item.data.target.type)); item.hasTarget = !!item.data.target && !["none", ""].includes(item.data.target.type);
// Item toggle state // Item toggle state
this._prepareItemToggleState(item); this._prepareItemToggleState(item);
@ -113,7 +125,9 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
else if (item.type === "lightsaberform") arr[10].push(item); else if (item.type === "lightsaberform") arr[10].push(item);
else if (Object.keys(inventory).includes(item.type)) arr[0].push(item); else if (Object.keys(inventory).includes(item.type)) arr[0].push(item);
return arr; return arr;
}, [[], [], [], [], [], [], [], [], [], [], []]); },
[[], [], [], [], [], [], [], [], [], [], []]
);
// Apply active item filters // Apply active item filters
items = this._filterItems(items, this._filters.inventory); items = this._filterItems(items, this._filters.inventory);
@ -131,20 +145,67 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
// Organize Powerbook and count the number of prepared powers (excluding always, at will, etc...) // Organize Powerbook and count the number of prepared powers (excluding always, at will, etc...)
const powerbook = this._preparePowerbook(data, powers); const powerbook = this._preparePowerbook(data, powers);
const nPrepared = powers.filter(s => { const nPrepared = powers.filter(s => {
return (s.data.level > 0) && (s.data.preparation.mode === "prepared") && s.data.preparation.prepared; return s.data.level > 0 && s.data.preparation.mode === "prepared" && s.data.preparation.prepared;
}).length; }).length;
// Organize Features // Organize Features
const features = { const features = {
classes: {label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true}, classes: {label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true},
classfeatures: { label: "SW5E.ItemTypeClassFeats", items: [], hasActions: true, dataset: {type: "classfeature"}, isClassfeature: true }, classfeatures: {
archetype: { label: "SW5E.ItemTypeArchetype", items: [], hasActions: false, dataset: {type: "archetype"}, isArchetype: true }, label: "SW5E.ItemTypeClassFeats",
species: { label: "SW5E.ItemTypeSpecies", items: [], hasActions: false, dataset: {type: "species"}, isSpecies: true }, items: [],
background: { label: "SW5E.ItemTypeBackground", items: [], hasActions: false, dataset: {type: "background"}, isBackground: true }, hasActions: true,
fightingstyles: { label: "SW5E.ItemTypeFightingStylePl", items: [], hasActions: false, dataset: {type: "fightingstyle"}, isFightingstyle: true }, dataset: {type: "classfeature"},
fightingmasteries: { label: "SW5E.ItemTypeFightingMasteryPl", items: [], hasActions: false, dataset: {type: "fightingmastery"}, isFightingmastery: true }, isClassfeature: true
lightsaberforms: { label: "SW5E.ItemTypeLightsaberFormPl", items: [], hasActions: false, dataset: {type: "lightsaberform"}, isLightsaberform: true }, },
active: { label: "SW5E.FeatureActive", items: [], hasActions: true, dataset: {type: "feat", "activation.type": "action"} }, archetype: {
label: "SW5E.ItemTypeArchetype",
items: [],
hasActions: false,
dataset: {type: "archetype"},
isArchetype: true
},
species: {
label: "SW5E.ItemTypeSpecies",
items: [],
hasActions: false,
dataset: {type: "species"},
isSpecies: true
},
background: {
label: "SW5E.ItemTypeBackground",
items: [],
hasActions: false,
dataset: {type: "background"},
isBackground: true
},
fightingstyles: {
label: "SW5E.ItemTypeFightingStylePl",
items: [],
hasActions: false,
dataset: {type: "fightingstyle"},
isFightingstyle: true
},
fightingmasteries: {
label: "SW5E.ItemTypeFightingMasteryPl",
items: [],
hasActions: false,
dataset: {type: "fightingmastery"},
isFightingmastery: true
},
lightsaberforms: {
label: "SW5E.ItemTypeLightsaberFormPl",
items: [],
hasActions: false,
dataset: {type: "lightsaberform"},
isLightsaberform: true
},
active: {
label: "SW5E.FeatureActive",
items: [],
hasActions: true,
dataset: {type: "feat", "activation.type": "action"}
},
passive: {label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"}} passive: {label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"}}
}; };
for (let f of feats) { for (let f of feats) {
@ -184,8 +245,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
if (isAlways) item.toggleTitle = CONFIG.SW5E.powerPreparationModes.always; if (isAlways) item.toggleTitle = CONFIG.SW5E.powerPreparationModes.always;
else if (isPrepared) item.toggleTitle = CONFIG.SW5E.powerPreparationModes.prepared; else if (isPrepared) item.toggleTitle = CONFIG.SW5E.powerPreparationModes.prepared;
else item.toggleTitle = game.i18n.localize("SW5E.PowerUnprepared"); else item.toggleTitle = game.i18n.localize("SW5E.PowerUnprepared");
} } else {
else {
const isActive = getProperty(item.data, "equipped"); const isActive = getProperty(item.data, "equipped");
item.toggleClass = isActive ? "active" : ""; item.toggleClass = isActive ? "active" : "";
item.toggleTitle = game.i18n.localize(isActive ? "SW5E.Equipped" : "SW5E.Unequipped"); item.toggleTitle = game.i18n.localize(isActive ? "SW5E.Equipped" : "SW5E.Unequipped");
@ -205,11 +265,11 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
if (!this.options.editable) return; if (!this.options.editable) return;
// Item State Toggling // Item State Toggling
html.find('.item-toggle').click(this._onToggleItem.bind(this)); html.find(".item-toggle").click(this._onToggleItem.bind(this));
// Short and Long Rest // Short and Long Rest
html.find('.short-rest').click(this._onShortRest.bind(this)); html.find(".short-rest").click(this._onShortRest.bind(this));
html.find('.long-rest').click(this._onLongRest.bind(this)); html.find(".long-rest").click(this._onLongRest.bind(this));
// Rollable sheet actions // Rollable sheet actions
html.find(".rollable[data-action]").click(this._onSheetAction.bind(this)); html.find(".rollable[data-action]").click(this._onSheetAction.bind(this));
@ -278,7 +338,6 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
/** @override */ /** @override */
async _onDropItemCreate(itemData) { async _onDropItemCreate(itemData) {
// Increment the number of class levels a character instead of creating a new item // Increment the number of class levels a character instead of creating a new item
if (itemData.type === "class") { if (itemData.type === "class") {
const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name); const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name);

View file

@ -6,7 +6,6 @@ import ActorSheet5e from "./base.js";
* @extends {ActorSheet5e} * @extends {ActorSheet5e}
*/ */
export default class ActorSheet5eNPC extends ActorSheet5e { export default class ActorSheet5eNPC extends ActorSheet5e {
/** @override */ /** @override */
static get defaultOptions() { static get defaultOptions() {
return mergeObject(super.defaultOptions, { return mergeObject(super.defaultOptions, {
@ -23,27 +22,39 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
* @private * @private
*/ */
_prepareItems(data) { _prepareItems(data) {
// Categorize Items as Features and Powers // Categorize Items as Features and Powers
const features = { const features = {
weapons: { label: game.i18n.localize("SW5E.AttackPl"), items: [] , hasActions: true, dataset: {type: "weapon", "weapon-type": "natural"} }, weapons: {
actions: { label: game.i18n.localize("SW5E.ActionPl"), items: [] , hasActions: true, dataset: {type: "feat", "activation.type": "action"} }, label: game.i18n.localize("SW5E.AttackPl"),
items: [],
hasActions: true,
dataset: {type: "weapon", "weapon-type": "natural"}
},
actions: {
label: game.i18n.localize("SW5E.ActionPl"),
items: [],
hasActions: true,
dataset: {type: "feat", "activation.type": "action"}
},
passive: {label: game.i18n.localize("SW5E.Features"), items: [], dataset: {type: "feat"}}, passive: {label: game.i18n.localize("SW5E.Features"), items: [], dataset: {type: "feat"}},
equipment: {label: game.i18n.localize("SW5E.Inventory"), items: [], dataset: {type: "loot"}} equipment: {label: game.i18n.localize("SW5E.Inventory"), items: [], dataset: {type: "loot"}}
}; };
// Start by classifying items into groups for rendering // Start by classifying items into groups for rendering
let [powers, other] = data.items.reduce((arr, item) => { let [powers, other] = data.items.reduce(
(arr, item) => {
item.img = item.img || DEFAULT_TOKEN; item.img = item.img || DEFAULT_TOKEN;
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1); item.isStack = Number.isNumeric(item.data.quantity) && item.data.quantity !== 1;
item.hasUses = item.data.uses && (item.data.uses.max > 0); item.hasUses = item.data.uses && item.data.uses.max > 0;
item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false); item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && item.data.recharge.charged === false;
item.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0)); item.isDepleted = item.isOnCooldown && item.data.uses.per && item.data.uses.value > 0;
item.hasTarget = !!item.data.target && !(["none",""].includes(item.data.target.type)); item.hasTarget = !!item.data.target && !["none", ""].includes(item.data.target.type);
if (item.type === "power") arr[0].push(item); if (item.type === "power") arr[0].push(item);
else arr[1].push(item); else arr[1].push(item);
return arr; return arr;
}, [[], []]); },
[[], []]
);
// Apply item filters // Apply item filters
powers = this._filterItems(powers, this._filters.powerbook); powers = this._filterItems(powers, this._filters.powerbook);
@ -58,8 +69,7 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
else if (item.type === "feat") { else if (item.type === "feat") {
if (item.data.activation.type) features.actions.items.push(item); if (item.data.activation.type) features.actions.items.push(item);
else features.passive.items.push(item); else features.passive.items.push(item);
} } else features.equipment.items.push(item);
else features.equipment.items.push(item);
} }
// Assign and return // Assign and return
@ -67,7 +77,6 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
data.powerbook = powerbook; data.powerbook = powerbook;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
@ -87,7 +96,6 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
/** @override */ /** @override */
_updateObject(event, formData) { _updateObject(event, formData) {
// Format NPC Challenge Rating // Format NPC Challenge Rating
const crs = {"1/8": 0.125, "1/4": 0.25, "1/2": 0.5}; const crs = {"1/8": 0.125, "1/4": 0.25, "1/2": 0.5};
let crv = "data.details.cr"; let crv = "data.details.cr";

View file

@ -25,7 +25,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
*/ */
static get newCargo() { static get newCargo() {
return { return {
name: '', name: "",
quantity: 1 quantity: 1
}; };
} }
@ -40,7 +40,6 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
* @private * @private
*/ */
_computeEncumbrance(totalWeight, actorData) { _computeEncumbrance(totalWeight, actorData) {
// Compute currency weight // Compute currency weight
const totalCoins = Object.values(actorData.data.currency).reduce((acc, denom) => acc + denom, 0); const totalCoins = Object.values(actorData.data.currency).reduce((acc, denom) => acc + denom, 0);
totalWeight += totalCoins / CONFIG.SW5E.encumbrance.currencyPerWeight; totalWeight += totalCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
@ -69,25 +68,24 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
* @private * @private
*/ */
_prepareCrewedItem(item) { _prepareCrewedItem(item) {
// Determine crewed status // Determine crewed status
const isCrewed = item.data.crewed; const isCrewed = item.data.crewed;
item.toggleClass = isCrewed ? 'active' : ''; item.toggleClass = isCrewed ? "active" : "";
item.toggleTitle = game.i18n.localize(`SW5E.${isCrewed ? 'Crewed' : 'Uncrewed'}`); item.toggleTitle = game.i18n.localize(`SW5E.${isCrewed ? "Crewed" : "Uncrewed"}`);
// Handle crew actions // Handle crew actions
if (item.type === 'feat' && item.data.activation.type === 'crew') { if (item.type === "feat" && item.data.activation.type === "crew") {
item.crew = item.data.activation.cost; item.crew = item.data.activation.cost;
item.cover = game.i18n.localize(`SW5E.${item.data.cover ? 'CoverTotal' : 'None'}`); item.cover = game.i18n.localize(`SW5E.${item.data.cover ? "CoverTotal" : "None"}`);
if (item.data.cover === .5) item.cover = '½'; if (item.data.cover === 0.5) item.cover = "½";
else if (item.data.cover === .75) item.cover = '¾'; else if (item.data.cover === 0.75) item.cover = "¾";
else if (item.data.cover === null) item.cover = '—'; else if (item.data.cover === null) item.cover = "—";
if (item.crew < 1 || item.crew === null) item.crew = '—'; if (item.crew < 1 || item.crew === null) item.crew = "—";
} }
// Prepare vehicle weapons // Prepare vehicle weapons
if (item.type === 'equipment' || item.type === 'weapon') { if (item.type === "equipment" || item.type === "weapon") {
item.threshold = item.data.hp.dt ? item.data.hp.dt : '—'; item.threshold = item.data.hp.dt ? item.data.hp.dt : "—";
} }
} }
@ -98,128 +96,140 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
* @private * @private
*/ */
_prepareItems(data) { _prepareItems(data) {
const cargoColumns = [{ const cargoColumns = [
label: game.i18n.localize('SW5E.Quantity'), {
css: 'item-qty', label: game.i18n.localize("SW5E.Quantity"),
property: 'quantity', css: "item-qty",
editable: 'Number' property: "quantity",
}]; editable: "Number"
}
];
const equipmentColumns = [{ const equipmentColumns = [
label: game.i18n.localize('SW5E.Quantity'), {
css: 'item-qty', label: game.i18n.localize("SW5E.Quantity"),
property: 'data.quantity' css: "item-qty",
}, { property: "data.quantity"
label: game.i18n.localize('SW5E.AC'), },
css: 'item-ac', {
property: 'data.armor.value' label: game.i18n.localize("SW5E.AC"),
}, { css: "item-ac",
label: game.i18n.localize('SW5E.HP'), property: "data.armor.value"
css: 'item-hp', },
property: 'data.hp.value', {
editable: 'Number' label: game.i18n.localize("SW5E.HP"),
}, { css: "item-hp",
label: game.i18n.localize('SW5E.Threshold'), property: "data.hp.value",
css: 'item-threshold', editable: "Number"
property: 'threshold' },
}]; {
label: game.i18n.localize("SW5E.Threshold"),
css: "item-threshold",
property: "threshold"
}
];
const features = { const features = {
actions: { actions: {
label: game.i18n.localize('SW5E.ActionPl'), label: game.i18n.localize("SW5E.ActionPl"),
items: [], items: [],
crewable: true, crewable: true,
dataset: {type: 'feat', 'activation.type': 'crew'}, dataset: {type: "feat", "activation.type": "crew"},
columns: [{ columns: [
label: game.i18n.localize('SW5E.VehicleCrew'), {
css: 'item-crew', label: game.i18n.localize("SW5E.VehicleCrew"),
property: 'crew' css: "item-crew",
}, { property: "crew"
label: game.i18n.localize('SW5E.Cover'), },
css: 'item-cover', {
property: 'cover' label: game.i18n.localize("SW5E.Cover"),
}] css: "item-cover",
property: "cover"
}
]
}, },
equipment: { equipment: {
label: game.i18n.localize('SW5E.ItemTypeEquipment'), label: game.i18n.localize("SW5E.ItemTypeEquipment"),
items: [], items: [],
crewable: true, crewable: true,
dataset: {type: 'equipment', 'armor.type': 'vehicle'}, dataset: {type: "equipment", "armor.type": "vehicle"},
columns: equipmentColumns columns: equipmentColumns
}, },
passive: { passive: {
label: game.i18n.localize('SW5E.Features'), label: game.i18n.localize("SW5E.Features"),
items: [], items: [],
dataset: {type: 'feat'} dataset: {type: "feat"}
}, },
reactions: { reactions: {
label: game.i18n.localize('SW5E.ReactionPl'), label: game.i18n.localize("SW5E.ReactionPl"),
items: [], items: [],
dataset: {type: 'feat', 'activation.type': 'reaction'} dataset: {type: "feat", "activation.type": "reaction"}
}, },
weapons: { weapons: {
label: game.i18n.localize('SW5E.ItemTypeWeaponPl'), label: game.i18n.localize("SW5E.ItemTypeWeaponPl"),
items: [], items: [],
crewable: true, crewable: true,
dataset: {type: 'weapon', 'weapon-type': 'siege'}, dataset: {type: "weapon", "weapon-type": "siege"},
columns: equipmentColumns columns: equipmentColumns
} }
}; };
const cargo = { const cargo = {
crew: { crew: {
label: game.i18n.localize('SW5E.VehicleCrew'), label: game.i18n.localize("SW5E.VehicleCrew"),
items: data.data.cargo.crew, items: data.data.cargo.crew,
css: 'cargo-row crew', css: "cargo-row crew",
editableName: true, editableName: true,
dataset: {type: 'crew'}, dataset: {type: "crew"},
columns: cargoColumns columns: cargoColumns
}, },
passengers: { passengers: {
label: game.i18n.localize('SW5E.VehiclePassengers'), label: game.i18n.localize("SW5E.VehiclePassengers"),
items: data.data.cargo.passengers, items: data.data.cargo.passengers,
css: 'cargo-row passengers', css: "cargo-row passengers",
editableName: true, editableName: true,
dataset: {type: 'passengers'}, dataset: {type: "passengers"},
columns: cargoColumns columns: cargoColumns
}, },
cargo: { cargo: {
label: game.i18n.localize('SW5E.VehicleCargo'), label: game.i18n.localize("SW5E.VehicleCargo"),
items: [], items: [],
dataset: {type: 'loot'}, dataset: {type: "loot"},
columns: [{ columns: [
label: game.i18n.localize('SW5E.Quantity'), {
css: 'item-qty', label: game.i18n.localize("SW5E.Quantity"),
property: 'data.quantity', css: "item-qty",
editable: 'Number' property: "data.quantity",
}, { editable: "Number"
label: game.i18n.localize('SW5E.Price'), },
css: 'item-price', {
property: 'data.price', label: game.i18n.localize("SW5E.Price"),
editable: 'Number' css: "item-price",
}, { property: "data.price",
label: game.i18n.localize('SW5E.Weight'), editable: "Number"
css: 'item-weight', },
property: 'data.weight', {
editable: 'Number' label: game.i18n.localize("SW5E.Weight"),
}] css: "item-weight",
property: "data.weight",
editable: "Number"
}
]
} }
}; };
let totalWeight = 0; let totalWeight = 0;
for (const item of data.items) { for (const item of data.items) {
this._prepareCrewedItem(item); this._prepareCrewedItem(item);
if (item.type === 'weapon') features.weapons.items.push(item); if (item.type === "weapon") features.weapons.items.push(item);
else if (item.type === 'equipment') features.equipment.items.push(item); else if (item.type === "equipment") features.equipment.items.push(item);
else if (item.type === 'loot') { else if (item.type === "loot") {
totalWeight += (item.data.weight || 0) * item.data.quantity; totalWeight += (item.data.weight || 0) * item.data.quantity;
cargo.cargo.items.push(item); cargo.cargo.items.push(item);
} } else if (item.type === "feat") {
else if (item.type === 'feat') { if (!item.data.activation.type || item.data.activation.type === "none") {
if (!item.data.activation.type || item.data.activation.type === 'none') {
features.passive.items.push(item); features.passive.items.push(item);
} } else if (item.data.activation.type === "reaction") features.reactions.items.push(item);
else if (item.data.activation.type === 'reaction') features.reactions.items.push(item);
else features.actions.items.push(item); else features.actions.items.push(item);
} }
} }
@ -238,21 +248,24 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
super.activateListeners(html); super.activateListeners(html);
if (!this.options.editable) return; if (!this.options.editable) return;
html.find('.item-toggle').click(this._onToggleItem.bind(this)); html.find(".item-toggle").click(this._onToggleItem.bind(this));
html.find('.item-hp input') html
.find(".item-hp input")
.click(evt => evt.target.select()) .click(evt => evt.target.select())
.change(this._onHPChange.bind(this)); .change(this._onHPChange.bind(this));
html.find('.item:not(.cargo-row) input[data-property]') html
.find(".item:not(.cargo-row) input[data-property]")
.click(evt => evt.target.select()) .click(evt => evt.target.select())
.change(this._onEditInSheet.bind(this)); .change(this._onEditInSheet.bind(this));
html.find('.cargo-row input') html
.find(".cargo-row input")
.click(evt => evt.target.select()) .click(evt => evt.target.select())
.change(this._onCargoRowChange.bind(this)); .change(this._onCargoRowChange.bind(this));
if (this.actor.data.data.attributes.actions.stations) { if (this.actor.data.data.attributes.actions.stations) {
html.find('.counter.actions, .counter.action-thresholds').hide(); html.find(".counter.actions, .counter.action-thresholds").hide();
} }
} }
@ -267,9 +280,9 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
_onCargoRowChange(event) { _onCargoRowChange(event) {
event.preventDefault(); event.preventDefault();
const target = event.currentTarget; const target = event.currentTarget;
const row = target.closest('.item'); const row = target.closest(".item");
const idx = Number(row.dataset.itemId); const idx = Number(row.dataset.itemId);
const property = row.classList.contains('crew') ? 'crew' : 'passengers'; const property = row.classList.contains("crew") ? "crew" : "passengers";
// Get the cargo entry // Get the cargo entry
const cargo = duplicate(this.actor.data.data.cargo[property]); const cargo = duplicate(this.actor.data.data.cargo[property]);
@ -277,10 +290,10 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
if (!entry) return null; if (!entry) return null;
// Update the cargo value // Update the cargo value
const key = target.dataset.property || 'name'; const key = target.dataset.property || "name";
const type = target.dataset.dtype; const type = target.dataset.dtype;
let value = target.value; let value = target.value;
if (type === 'Number') value = Number(value); if (type === "Number") value = Number(value);
entry[key] = value; entry[key] = value;
// Perform the Actor update // Perform the Actor update
@ -297,14 +310,18 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
*/ */
_onEditInSheet(event) { _onEditInSheet(event) {
event.preventDefault(); event.preventDefault();
const itemID = event.currentTarget.closest('.item').dataset.itemId; const itemID = event.currentTarget.closest(".item").dataset.itemId;
const item = this.actor.items.get(itemID); const item = this.actor.items.get(itemID);
const property = event.currentTarget.dataset.property; const property = event.currentTarget.dataset.property;
const type = event.currentTarget.dataset.dtype; const type = event.currentTarget.dataset.dtype;
let value = event.currentTarget.value; let value = event.currentTarget.value;
switch (type) { switch (type) {
case 'Number': value = parseInt(value); break; case "Number":
case 'Boolean': value = value === 'true'; break; value = parseInt(value);
break;
case "Boolean":
value = value === "true";
break;
} }
return item.update({[`${property}`]: value}); return item.update({[`${property}`]: value});
} }
@ -321,7 +338,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
event.preventDefault(); event.preventDefault();
const target = event.currentTarget; const target = event.currentTarget;
const type = target.dataset.type; const type = target.dataset.type;
if (type === 'crew' || type === 'passengers') { if (type === "crew" || type === "passengers") {
const cargo = duplicate(this.actor.data.data.cargo[type]); const cargo = duplicate(this.actor.data.data.cargo[type]);
cargo.push(this.constructor.newCargo); cargo.push(this.constructor.newCargo);
return this.actor.update({[`data.cargo.${type}`]: cargo}); return this.actor.update({[`data.cargo.${type}`]: cargo});
@ -339,10 +356,10 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
*/ */
_onItemDelete(event) { _onItemDelete(event) {
event.preventDefault(); event.preventDefault();
const row = event.currentTarget.closest('.item'); const row = event.currentTarget.closest(".item");
if (row.classList.contains('cargo-row')) { if (row.classList.contains("cargo-row")) {
const idx = Number(row.dataset.itemId); const idx = Number(row.dataset.itemId);
const type = row.classList.contains('crew') ? 'crew' : 'passengers'; const type = row.classList.contains("crew") ? "crew" : "passengers";
const cargo = duplicate(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx); const cargo = duplicate(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx);
return this.actor.update({[`data.cargo.${type}`]: cargo}); return this.actor.update({[`data.cargo.${type}`]: cargo});
} }
@ -360,11 +377,11 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
*/ */
_onHPChange(event) { _onHPChange(event) {
event.preventDefault(); event.preventDefault();
const itemID = event.currentTarget.closest('.item').dataset.itemId; const itemID = event.currentTarget.closest(".item").dataset.itemId;
const item = this.actor.items.get(itemID); const item = this.actor.items.get(itemID);
const hp = Math.clamped(0, parseInt(event.currentTarget.value), item.data.data.hp.max); const hp = Math.clamped(0, parseInt(event.currentTarget.value), item.data.data.hp.max);
event.currentTarget.value = hp; event.currentTarget.value = hp;
return item.update({'data.hp.value': hp}); return item.update({"data.hp.value": hp});
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -377,9 +394,9 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
*/ */
_onToggleItem(event) { _onToggleItem(event) {
event.preventDefault(); event.preventDefault();
const itemID = event.currentTarget.closest('.item').dataset.itemId; const itemID = event.currentTarget.closest(".item").dataset.itemId;
const item = this.actor.items.get(itemID); const item = this.actor.items.get(itemID);
const crewed = !!item.data.data.crewed; const crewed = !!item.data.data.crewed;
return item.update({'data.crewed': !crewed}); return item.update({"data.crewed": !crewed});
}
} }
};

View file

@ -57,7 +57,7 @@ export default class AbilityUseDialog extends Dialog {
// Create the Dialog and return data as a Promise // Create the Dialog and return data as a Promise
const icon = data.isPower ? "fa-magic" : "fa-fist-raised"; const icon = data.isPower ? "fa-magic" : "fa-fist-raised";
const label = game.i18n.localize("SW5E.AbilityUse" + (data.isPower ? "Cast" : "Use")); const label = game.i18n.localize("SW5E.AbilityUse" + (data.isPower ? "Cast" : "Use"));
return new Promise((resolve) => { return new Promise(resolve => {
const dlg = new this(item, { const dlg = new this(item, {
title: `${item.name}: Usage Configuration`, title: `${item.name}: Usage Configuration`,
content: html, content: html,
@ -87,10 +87,9 @@ export default class AbilityUseDialog extends Dialog {
* @private * @private
*/ */
static _getPowerData(actorData, itemData, data) { static _getPowerData(actorData, itemData, data) {
// Determine whether the power may be up-cast // Determine whether the power may be up-cast
const lvl = itemData.level; const lvl = itemData.level;
const consumePowerSlot = (lvl > 0) && CONFIG.SW5E.powerUpcastModes.includes(itemData.preparation.mode); 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 can't upcast, return early and don't bother calculating available power slots
if (!consumePowerSlot) { if (!consumePowerSlot) {
@ -106,67 +105,71 @@ export default class AbilityUseDialog extends Dialog {
case "lgt": case "lgt":
case "uni": case "uni":
case "drk": { case "drk": {
powerType = "force" powerType = "force";
points = actorData.attributes.force.points.value + actorData.attributes.force.points.temp; points = actorData.attributes.force.points.value + actorData.attributes.force.points.temp;
break; break;
} }
case "tec": { case "tec": {
powerType = "tech" powerType = "tech";
points = actorData.attributes.tech.points.value + actorData.attributes.tech.points.temp; points = actorData.attributes.tech.points.value + actorData.attributes.tech.points.temp;
break; break;
} }
} }
// eliminate point usage for innate casters // eliminate point usage for innate casters
if (actorData.attributes.powercasting === 'innate') points = 999; if (actorData.attributes.powercasting === "innate") points = 999;
let powerLevels;
let powerLevels
if (powerType === "force") { if (powerType === "force") {
powerLevels = Array.fromRange(10).reduce((arr, i) => { powerLevels = Array.fromRange(10)
.reduce((arr, i) => {
if (i < lvl) return arr; if (i < lvl) return arr;
const label = CONFIG.SW5E.powerLevels[i]; const label = CONFIG.SW5E.powerLevels[i];
const l = actorData.powers["power" + i] || {fmax: 0, foverride: null}; const l = actorData.powers["power" + i] || {fmax: 0, foverride: null};
let max = parseInt(l.foverride || l.fmax || 0); let max = parseInt(l.foverride || l.fmax || 0);
let slots = Math.clamped(parseInt(l.fvalue || 0), 0, max); let slots = Math.clamped(parseInt(l.fvalue || 0), 0, max);
if (max > 0) lmax = i; if (max > 0) lmax = i;
if ((max > 0) && (slots > 0) && (points > i)){ if (max > 0 && slots > 0 && points > i) {
arr.push({ arr.push({
level: i, level: i,
label: i > 0 ? game.i18n.format('SW5E.PowerLevelSlot', {level: label, n: slots}) : label, label: i > 0 ? game.i18n.format("SW5E.PowerLevelSlot", {level: label, n: slots}) : label,
canCast: max > 0, canCast: max > 0,
hasSlots: slots > 0 hasSlots: slots > 0
}); });
} }
return arr; return arr;
}, []).filter(sl => sl.level <= lmax); }, [])
.filter(sl => sl.level <= lmax);
} else if (powerType === "tech") { } else if (powerType === "tech") {
powerLevels = Array.fromRange(10).reduce((arr, i) => { powerLevels = Array.fromRange(10)
.reduce((arr, i) => {
if (i < lvl) return arr; if (i < lvl) return arr;
const label = CONFIG.SW5E.powerLevels[i]; const label = CONFIG.SW5E.powerLevels[i];
const l = actorData.powers["power" + i] || {tmax: 0, toverride: null}; const l = actorData.powers["power" + i] || {tmax: 0, toverride: null};
let max = parseInt(l.override || l.tmax || 0); let max = parseInt(l.override || l.tmax || 0);
let slots = Math.clamped(parseInt(l.tvalue || 0), 0, max); let slots = Math.clamped(parseInt(l.tvalue || 0), 0, max);
if (max > 0) lmax = i; if (max > 0) lmax = i;
if ((max > 0) && (slots > 0) && (points > i)){ if (max > 0 && slots > 0 && points > i) {
arr.push({ arr.push({
level: i, level: i,
label: i > 0 ? game.i18n.format('SW5E.PowerLevelSlot', {level: label, n: slots}) : label, label: i > 0 ? game.i18n.format("SW5E.PowerLevelSlot", {level: label, n: slots}) : label,
canCast: max > 0, canCast: max > 0,
hasSlots: slots > 0 hasSlots: slots > 0
}); });
} }
return arr; return arr;
}, []).filter(sl => sl.level <= lmax); }, [])
.filter(sl => sl.level <= lmax);
} }
const canCast = powerLevels.some(l => l.hasSlots); const canCast = powerLevels.some(l => l.hasSlots);
if ( !canCast ) data.errors.push(game.i18n.format("SW5E.PowerCastNoSlots", { if (!canCast)
data.errors.push(
game.i18n.format("SW5E.PowerCastNoSlots", {
level: CONFIG.SW5E.powerLevels[lvl], level: CONFIG.SW5E.powerLevels[lvl],
name: data.item.name name: data.item.name
})); })
);
// Merge power casting data // Merge power casting data
return mergeObject(data, {isPower: true, consumePowerSlot, powerLevels}); return mergeObject(data, {isPower: true, consumePowerSlot, powerLevels});
@ -179,7 +182,6 @@ export default class AbilityUseDialog extends Dialog {
* @private * @private
*/ */
static _getAbilityUseNote(item, uses, recharge) { static _getAbilityUseNote(item, uses, recharge) {
// Zero quantity // Zero quantity
const quantity = item.data.quantity; const quantity = item.data.quantity;
if (quantity <= 0) return game.i18n.localize("SW5E.AbilityUseUnavailableHint"); if (quantity <= 0) return game.i18n.localize("SW5E.AbilityUseUnavailableHint");
@ -187,8 +189,8 @@ export default class AbilityUseDialog extends Dialog {
// Abilities which use Recharge // Abilities which use Recharge
if (!!recharge.value) { if (!!recharge.value) {
return game.i18n.format(recharge.charged ? "SW5E.AbilityUseChargedHint" : "SW5E.AbilityUseRechargeHint", { return game.i18n.format(recharge.charged ? "SW5E.AbilityUseChargedHint" : "SW5E.AbilityUseRechargeHint", {
type: item.type, type: item.type
}) });
} }
// Does not use any resource // Does not use any resource
@ -222,7 +224,5 @@ export default class AbilityUseDialog extends Dialog {
/* -------------------------------------------- */ /* -------------------------------------------- */
static _handleSubmit(formData, item) { static _handleSubmit(formData, item) {}
}
} }

View file

@ -18,7 +18,7 @@ export default class ActorSheetFlags extends BaseEntitySheet {
/** @override */ /** @override */
get title() { get title() {
return `${game.i18n.localize('SW5E.FlagsTitle')}: ${this.object.name}`; return `${game.i18n.localize("SW5E.FlagsTitle")}: ${this.object.name}`;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -47,7 +47,7 @@ export default class ActorSheetFlags extends BaseEntitySheet {
let flag = duplicate(v); let flag = duplicate(v);
flag.type = v.type.name; flag.type = v.type.name;
flag.isCheckbox = v.type === Boolean; flag.isCheckbox = v.type === Boolean;
flag.isSelect = v.hasOwnProperty('choices'); flag.isSelect = v.hasOwnProperty("choices");
flag.value = getProperty(baseData.flags, `sw5e.${k}`); flag.value = getProperty(baseData.flags, `sw5e.${k}`);
flags[v.section][`flags.sw5e.${k}`] = flag; flags[v.section][`flags.sw5e.${k}`] = flag;
} }
@ -97,7 +97,7 @@ export default class ActorSheetFlags extends BaseEntitySheet {
let unset = false; let unset = false;
const flags = updateData.flags.sw5e; const flags = updateData.flags.sw5e;
//clone flags to dnd5e for module compatability //clone flags to dnd5e for module compatability
updateData.flags.dnd5e = updateData.flags.sw5e updateData.flags.dnd5e = updateData.flags.sw5e;
for (let [k, v] of Object.entries(flags)) { for (let [k, v] of Object.entries(flags)) {
if ([undefined, null, "", false, 0].includes(v)) { if ([undefined, null, "", false, 0].includes(v)) {
delete flags[k]; delete flags[k];

View file

@ -49,8 +49,7 @@ export default class LongRestDialog extends Dialog {
let newDay = false; let newDay = false;
if (game.settings.get("sw5e", "restVariant") === "normal") if (game.settings.get("sw5e", "restVariant") === "normal")
newDay = html.find('input[name="newDay"]')[0].checked; newDay = html.find('input[name="newDay"]')[0].checked;
else if(game.settings.get("sw5e", "restVariant") === "gritty") else if (game.settings.get("sw5e", "restVariant") === "gritty") newDay = true;
newDay = true;
resolve(newDay); resolve(newDay);
} }
}, },
@ -60,7 +59,7 @@ export default class LongRestDialog extends Dialog {
callback: reject callback: reject
} }
}, },
default: 'rest', default: "rest",
close: reject close: reject
}); });
dlg.render(true); dlg.render(true);

View file

@ -3,7 +3,6 @@
* @implements {BaseEntitySheet} * @implements {BaseEntitySheet}
*/ */
export default class ActorMovementConfig extends BaseEntitySheet { export default class ActorMovementConfig extends BaseEntitySheet {
/** @override */ /** @override */
static get defaultOptions() { static get defaultOptions() {
return mergeObject(super.defaultOptions, { return mergeObject(super.defaultOptions, {
@ -28,7 +27,7 @@ export default class ActorMovementConfig extends BaseEntitySheet {
const data = { const data = {
movement: duplicate(this.entity._data.data.attributes.movement), movement: duplicate(this.entity._data.data.attributes.movement),
units: CONFIG.SW5E.movementUnits units: CONFIG.SW5E.movementUnits
} };
for (let [k, v] of Object.entries(data.movement)) { for (let [k, v] of Object.entries(data.movement)) {
if (["units", "hover"].includes(k)) continue; if (["units", "hover"].includes(k)) continue;
data.movement[k] = Number.isNumeric(v) ? v.toNearest(0.1) : 0; data.movement[k] = Number.isNumeric(v) ? v.toNearest(0.1) : 0;

View file

@ -3,7 +3,6 @@
* @implements {BaseEntitySheet} * @implements {BaseEntitySheet}
*/ */
export default class ActorSensesConfig extends BaseEntitySheet { export default class ActorSensesConfig extends BaseEntitySheet {
/** @override */ /** @override */
static get defaultOptions() { static get defaultOptions() {
return mergeObject(super.defaultOptions, { return mergeObject(super.defaultOptions, {
@ -29,14 +28,15 @@ export default class ActorSensesConfig extends BaseEntitySheet {
const data = { const data = {
senses: {}, senses: {},
special: senses.special ?? "", special: senses.special ?? "",
units: senses.units, movementUnits: CONFIG.SW5E.movementUnits units: senses.units,
movementUnits: CONFIG.SW5E.movementUnits
}; };
for (let [name, label] of Object.entries(CONFIG.SW5E.senses)) { for (let [name, label] of Object.entries(CONFIG.SW5E.senses)) {
const v = senses[name]; const v = senses[name];
data.senses[name] = { data.senses[name] = {
label: game.i18n.localize(label), label: game.i18n.localize(label),
value: Number.isNumeric(v) ? v.toNearest(0.1) : 0 value: Number.isNumeric(v) ? v.toNearest(0.1) : 0
} };
} }
return data; return data;
} }

View file

@ -59,7 +59,6 @@ export default class ShortRestDialog extends Dialog {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
activateListeners(html) { activateListeners(html) {
super.activateListeners(html); super.activateListeners(html);
@ -127,7 +126,9 @@ export default class ShortRestDialog extends Dialog {
* @return {Promise} * @return {Promise}
*/ */
static async longRestDialog({actor} = {}) { static async longRestDialog({actor} = {}) {
console.warn("WARNING! ShortRestDialog.longRestDialog has been deprecated, use LongRestDialog.longRestDialog instead."); console.warn(
"WARNING! ShortRestDialog.longRestDialog has been deprecated, use LongRestDialog.longRestDialog instead."
);
return LongRestDialog.longRestDialog(...arguments); return LongRestDialog.longRestDialog(...arguments);
} }
} }

View file

@ -3,7 +3,6 @@
* @implements {FormApplication} * @implements {FormApplication}
*/ */
export default class TraitSelector extends FormApplication { export default class TraitSelector extends FormApplication {
/** @override */ /** @override */
static get defaultOptions() { static get defaultOptions() {
return mergeObject(super.defaultOptions, { return mergeObject(super.defaultOptions, {
@ -34,7 +33,6 @@ export default class TraitSelector extends FormApplication {
/** @override */ /** @override */
getData() { getData() {
// Get current values // Get current values
let attr = getProperty(this.object._data, this.attribute); let attr = getProperty(this.object._data, this.attribute);
if (getType(attr) !== "Object") attr = {value: [], custom: ""}; if (getType(attr) !== "Object") attr = {value: [], custom: ""};
@ -45,7 +43,7 @@ export default class TraitSelector extends FormApplication {
choices[k] = { choices[k] = {
label: v, label: v,
chosen: attr ? attr.value.includes(k) : false chosen: attr ? attr.value.includes(k) : false
} };
} }
// Return data // Return data
@ -53,7 +51,7 @@ export default class TraitSelector extends FormApplication {
allowCustom: this.options.allowCustom, allowCustom: this.options.allowCustom,
choices: choices, choices: choices,
custom: attr ? attr.custom : "" custom: attr ? attr.custom : ""
} };
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -65,15 +63,15 @@ export default class TraitSelector extends FormApplication {
// Obtain choices // Obtain choices
const chosen = []; const chosen = [];
for (let [k, v] of Object.entries(formData)) { for (let [k, v] of Object.entries(formData)) {
if ( (k !== "custom") && v ) chosen.push(k); if (k !== "custom" && v) chosen.push(k);
} }
updateData[`${this.attribute}.value`] = chosen; updateData[`${this.attribute}.value`] = chosen;
// Validate the number chosen // Validate the number chosen
if ( this.options.minimum && (chosen.length < this.options.minimum) ) { if (this.options.minimum && chosen.length < this.options.minimum) {
return ui.notifications.error(`You must choose at least ${this.options.minimum} options`); return ui.notifications.error(`You must choose at least ${this.options.minimum} options`);
} }
if ( this.options.maximum && (chosen.length > this.options.maximum) ) { if (this.options.maximum && chosen.length > this.options.maximum) {
return ui.notifications.error(`You may choose no more than ${this.options.maximum} options`); return ui.notifications.error(`You may choose no more than ${this.options.maximum} options`);
} }

View file

@ -23,7 +23,7 @@ export const measureDistances = function(segments, options={}) {
// Alternative DMG Movement // Alternative DMG Movement
if (rule === "5105") { if (rule === "5105") {
let nd10 = Math.floor(nDiagonal / 2) - Math.floor((nDiagonal - nd) / 2); let nd10 = Math.floor(nDiagonal / 2) - Math.floor((nDiagonal - nd) / 2);
let spaces = (nd10 * 2) + (nd - nd10) + ns; let spaces = nd10 * 2 + (nd - nd10) + ns;
return spaces * canvas.dimensions.distance; return spaces * canvas.dimensions.distance;
} }
@ -46,7 +46,7 @@ export const measureDistances = function(segments, options={}) {
const _TokenGetBarAttribute = Token.prototype.getBarAttribute; const _TokenGetBarAttribute = Token.prototype.getBarAttribute;
export const getBarAttribute = function (...args) { export const getBarAttribute = function (...args) {
const data = _TokenGetBarAttribute.bind(this)(...args); const data = _TokenGetBarAttribute.bind(this)(...args);
if ( data && (data.attribute === "attributes.hp") ) { if (data && data.attribute === "attributes.hp") {
data.value += parseInt(getProperty(this.actor.data, "data.attributes.hp.temp") || 0); data.value += parseInt(getProperty(this.actor.data, "data.attributes.hp.temp") || 0);
data.max += parseInt(getProperty(this.actor.data, "data.attributes.hp.tempmax") || 0); data.max += parseInt(getProperty(this.actor.data, "data.attributes.hp.tempmax") || 0);
} }

View file

@ -1,5 +1,4 @@
export default class CharacterImporter { export default class CharacterImporter {
// transform JSON from sw5e.com to Foundry friendly format // transform JSON from sw5e.com to Foundry friendly format
// and insert new actor // and insert new actor
static async transform(rawCharacter) { static async transform(rawCharacter) {
@ -9,7 +8,7 @@ export default class CharacterImporter {
species: sourceCharacter.attribs.find(e => e.name == "race").current, species: sourceCharacter.attribs.find(e => e.name == "race").current,
background: sourceCharacter.attribs.find(e => e.name == "background").current, background: sourceCharacter.attribs.find(e => e.name == "background").current,
alignment: sourceCharacter.attribs.find(e => e.name == "alignment").current alignment: sourceCharacter.attribs.find(e => e.name == "alignment").current
} };
const hp = { const hp = {
value: sourceCharacter.attribs.find(e => e.name == "hp").current, value: sourceCharacter.attribs.find(e => e.name == "hp").current,
@ -25,28 +24,28 @@ export default class CharacterImporter {
const abilities = { const abilities = {
str: { str: {
value: sourceCharacter.attribs.find(e => e.name == "strength").current, value: sourceCharacter.attribs.find(e => e.name == "strength").current,
proficient: sourceCharacter.attribs.find(e => e.name == 'strength_save_prof').current ? 1 : 0 proficient: sourceCharacter.attribs.find(e => e.name == "strength_save_prof").current ? 1 : 0
}, },
dex: { dex: {
value: sourceCharacter.attribs.find(e => e.name == "dexterity").current, value: sourceCharacter.attribs.find(e => e.name == "dexterity").current,
proficient: sourceCharacter.attribs.find(e => e.name == 'dexterity_save_prof').current ? 1 : 0 proficient: sourceCharacter.attribs.find(e => e.name == "dexterity_save_prof").current ? 1 : 0
}, },
con: { con: {
value: sourceCharacter.attribs.find(e => e.name == "constitution").current, value: sourceCharacter.attribs.find(e => e.name == "constitution").current,
proficient: sourceCharacter.attribs.find(e => e.name == 'constitution_save_prof').current ? 1 : 0 proficient: sourceCharacter.attribs.find(e => e.name == "constitution_save_prof").current ? 1 : 0
}, },
int: { int: {
value: sourceCharacter.attribs.find(e => e.name == "intelligence").current, value: sourceCharacter.attribs.find(e => e.name == "intelligence").current,
proficient: sourceCharacter.attribs.find(e => e.name == 'intelligence_save_prof').current ? 1 : 0 proficient: sourceCharacter.attribs.find(e => e.name == "intelligence_save_prof").current ? 1 : 0
}, },
wis: { wis: {
value: sourceCharacter.attribs.find(e => e.name == "wisdom").current, value: sourceCharacter.attribs.find(e => e.name == "wisdom").current,
proficient: sourceCharacter.attribs.find(e => e.name == 'wisdom_save_prof').current ? 1 : 0 proficient: sourceCharacter.attribs.find(e => e.name == "wisdom_save_prof").current ? 1 : 0
}, },
cha: { cha: {
value: sourceCharacter.attribs.find(e => e.name == "charisma").current, value: sourceCharacter.attribs.find(e => e.name == "charisma").current,
proficient: sourceCharacter.attribs.find(e => e.name == 'charisma_save_prof').current ? 1 : 0 proficient: sourceCharacter.attribs.find(e => e.name == "charisma_save_prof").current ? 1 : 0
}, }
}; };
const targetCharacter = { const targetCharacter = {
@ -66,12 +65,12 @@ export default class CharacterImporter {
const profession = sourceCharacter.attribs.find(e => e.name == "class").current; const profession = sourceCharacter.attribs.find(e => e.name == "class").current;
let professionLevel = sourceCharacter.attribs.find(e => e.name == "class_display").current; let professionLevel = sourceCharacter.attribs.find(e => e.name == "class_display").current;
professionLevel = parseInt( professionLevel.replace(/[^0-9]/g,'') ); //remove a-z, leaving only integers professionLevel = parseInt(professionLevel.replace(/[^0-9]/g, "")); //remove a-z, leaving only integers
CharacterImporter.addClasses(profession, professionLevel, actor); CharacterImporter.addClasses(profession, professionLevel, actor);
} }
static async addClasses(profession, level, actor) { static async addClasses(profession, level, actor) {
let classes = await game.packs.get('sw5e.classes').getContent(); let classes = await game.packs.get("sw5e.classes").getContent();
let assignedClass = classes.find(c => c.name === profession); let assignedClass = classes.find(c => c.name === profession);
assignedClass.data.data.levels = level; assignedClass.data.data.levels = level;
await actor.createEmbeddedEntity("OwnedItem", assignedClass.data, {displaySheet: false}); await actor.createEmbeddedEntity("OwnedItem", assignedClass.data, {displaySheet: false});
@ -83,38 +82,41 @@ export default class CharacterImporter {
const newImportButtonDiv = $("#actors").children().find("div.header-actions").clone(); const newImportButtonDiv = $("#actors").children().find("div.header-actions").clone();
const newSearch = search.clone(); const newSearch = search.clone();
search.remove(); search.remove();
newImportButtonDiv.attr('id', 'character-sheet-import'); newImportButtonDiv.attr("id", "character-sheet-import");
header.append(newImportButtonDiv); header.append(newImportButtonDiv);
newImportButtonDiv.children("button").remove(); newImportButtonDiv.children("button").remove();
newImportButtonDiv.append("<button class='create-entity' id='cs-import-button'><i class='fas fa-upload'></i> Import Character</button>"); newImportButtonDiv.append(
"<button class='create-entity' id='cs-import-button'><i class='fas fa-upload'></i> Import Character</button>"
);
newSearch.appendTo(header); newSearch.appendTo(header);
let characterImportButton = $("#cs-import-button"); let characterImportButton = $("#cs-import-button");
characterImportButton.click(ev => { characterImportButton.click(ev => {
let content = '<h1>Saved Character JSON Import</h1> ' let content =
+ '<label for="character-json">Paste character JSON here:</label> ' "<h1>Saved Character JSON Import</h1> " +
+ '</br>' '<label for="character-json">Paste character JSON here:</label> ' +
+ '<textarea id="character-json" name="character-json" rows="10" cols="50"></textarea>'; "</br>" +
'<textarea id="character-json" name="character-json" rows="10" cols="50"></textarea>';
let importDialog = new Dialog({ let importDialog = new Dialog({
title: "Import Character from SW5e.com", title: "Import Character from SW5e.com",
content: content, content: content,
buttons: { buttons: {
"Import": { Import: {
icon: '<i class="fas fa-file-import"></i>', icon: '<i class="fas fa-file-import"></i>',
label: "Import Character", label: "Import Character",
callback: (e) => { callback: e => {
let characterData = $('#character-json').val(); let characterData = $("#character-json").val();
console.log('Parsing Character JSON'); console.log("Parsing Character JSON");
CharacterImporter.transform(characterData); CharacterImporter.transform(characterData);
} }
}, },
"Cancel": { Cancel: {
icon: '<i class="fas fa-times-circle"></i>', icon: '<i class="fas fa-times-circle"></i>',
label: "Cancel", label: "Cancel",
callback: () => {}, callback: () => {}
} }
} }
}) });
importDialog.render(true); importDialog.render(true);
}); });
} }

View file

@ -1,4 +1,3 @@
/** /**
* Highlight critical success or failure on d20 rolls * Highlight critical success or failure on d20 rolls
*/ */
@ -11,9 +10,9 @@ export const highlightCriticalSuccessFailure = function(message, html, data) {
const d = roll.dice[0]; const d = roll.dice[0];
// Ensure it is an un-modified d20 roll // Ensure it is an un-modified d20 roll
const isD20 = (d.faces === 20) && ( d.values.length === 1 ); const isD20 = d.faces === 20 && d.values.length === 1;
if (!isD20) return; if (!isD20) return;
const isModifiedRoll = ("success" in d.results[0]) || d.options.marginSuccess || d.options.marginFailure; const isModifiedRoll = "success" in d.results[0] || d.options.marginSuccess || d.options.marginFailure;
if (isModifiedRoll) return; if (isModifiedRoll) return;
// Highlight successes and failures // Highlight successes and failures
@ -41,13 +40,13 @@ export const displayChatActionButtons = function(message, html, data) {
// If the user is the message author or the actor owner, proceed // If the user is the message author or the actor owner, proceed
let actor = game.actors.get(data.message.speaker.actor); let actor = game.actors.get(data.message.speaker.actor);
if (actor && actor.owner) return; if (actor && actor.owner) return;
else if ( game.user.isGM || (data.author.id === game.user.id)) return; else if (game.user.isGM || data.author.id === game.user.id) return;
// Otherwise conceal action buttons except for saving throw // Otherwise conceal action buttons except for saving throw
const buttons = chatCard.find("button[data-action]"); const buttons = chatCard.find("button[data-action]");
buttons.each((i, btn) => { buttons.each((i, btn) => {
if (btn.dataset.action === "save") return; if (btn.dataset.action === "save") return;
btn.style.display = "none" btn.style.display = "none";
}); });
} }
}; };
@ -110,10 +109,12 @@ export const addChatMessageContextOptions = function(html, options) {
function applyChatCardDamage(li, multiplier) { function applyChatCardDamage(li, multiplier) {
const message = game.messages.get(li.data("messageId")); const message = game.messages.get(li.data("messageId"));
const roll = message.roll; const roll = message.roll;
return Promise.all(canvas.tokens.controlled.map(t => { return Promise.all(
canvas.tokens.controlled.map(t => {
const a = t.actor; const a = t.actor;
return a.applyDamage(roll.total, multiplier); return a.applyDamage(roll.total, multiplier);
})); })
);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */

View file

@ -1,4 +1 @@
export const ClassFeatures = { export const ClassFeatures = {};
};

View file

@ -1,4 +1,3 @@
/** /**
* Override the default Initiative formula to customize special behaviors of the SW5e system. * Override the default Initiative formula to customize special behaviors of the SW5e system.
* Apply advantage, proficiency, or bonuses where appropriate * Apply advantage, proficiency, or bonuses where appropriate
@ -19,7 +18,7 @@ export const _getInitiativeFormula = function(combatant) {
mods += "kh"; mods += "kh";
} }
const parts = [`${nd}d20${mods}`, init.mod, (init.prof !== 0) ? init.prof : null, (init.bonus !== 0) ? init.bonus : null]; const parts = [`${nd}d20${mods}`, init.mod, init.prof !== 0 ? init.prof : null, init.bonus !== 0 ? init.bonus : null];
// Optionally apply Dexterity tiebreaker // Optionally apply Dexterity tiebreaker
const tiebreaker = game.settings.get("sw5e", "initiativeDexTiebreaker"); const tiebreaker = game.settings.get("sw5e", "initiativeDexTiebreaker");

File diff suppressed because it is too large Load diff

View file

@ -20,14 +20,19 @@ export function simplifyRollFormula(formula, data, {constantFirst = false} = {})
const constantTerms = []; // Terms that are constant, and their associated operators const constantTerms = []; // Terms that are constant, and their associated operators
let operators = []; // Temporary storage for operators before they are moved to one of the above let operators = []; // Temporary storage for operators before they are moved to one of the above
for (let term of terms) { // For each term for (let term of terms) {
if (["+", "-"].includes(term)) operators.push(term); // If the term is an addition/subtraction operator, push the term into the operators array // For each term
else { // Otherwise the term is not an operator if (["+", "-"].includes(term)) operators.push(term);
if (term instanceof DiceTerm) { // If the term is something rollable // If the term is an addition/subtraction operator, push the term into the operators array
else {
// Otherwise the term is not an operator
if (term instanceof DiceTerm) {
// If the term is something rollable
rollableTerms.push(...operators); // Place all the operators into the rollableTerms array rollableTerms.push(...operators); // Place all the operators into the rollableTerms array
rollableTerms.push(term); // Then place this rollable term into it as well rollableTerms.push(term); // Then place this rollable term into it as well
} // } //
else { // Otherwise, this must be a constant else {
// Otherwise, this must be a constant
constantTerms.push(...operators); // Place the operators into the constantTerms array constantTerms.push(...operators); // Place the operators into the constantTerms array
constantTerms.push(term); // Then also add this constant term to that array. constantTerms.push(term); // Then also add this constant term to that array.
} // } //
@ -40,8 +45,9 @@ export function simplifyRollFormula(formula, data, {constantFirst = false} = {})
const constantPart = roll._safeEval(constantFormula); // Mathematically evaluate the constant formula to produce a single constant term const constantPart = roll._safeEval(constantFormula); // Mathematically evaluate the constant formula to produce a single constant term
const parts = constantFirst ? // Order the rollable and constant terms, either constant first or second depending on the optional argumen const parts = constantFirst // Order the rollable and constant terms, either constant first or second depending on the optional argumen
[constantPart, rollableFormula] : [rollableFormula, constantPart]; ? [constantPart, rollableFormula]
: [rollableFormula, constantPart];
// Join the parts with a + sign, pass them to `Roll` once again to clean up the formula // Join the parts with a + sign, pass them to `Roll` once again to clean up the formula
return new Roll(parts.filterJoin(" + ")).formula; return new Roll(parts.filterJoin(" + ")).formula;
@ -94,12 +100,28 @@ function _isUnsupportedTerm(term) {
* *
* @return {Promise} A Promise which resolves once the roll workflow has completed * @return {Promise} A Promise which resolves once the roll workflow has completed
*/ */
export async function d20Roll({parts=[], data={}, event={}, rollMode=null, template=null, title=null, speaker=null, export async function d20Roll({
flavor=null, fastForward=null, dialogOptions, parts = [],
advantage=null, disadvantage=null, critical=20, fumble=1, targetValue=null, data = {},
elvenAccuracy=false, halflingLucky=false, reliableTalent=false, event = {},
chatMessage=true, messageData={}}={}) { rollMode = null,
template = null,
title = null,
speaker = null,
flavor = null,
fastForward = null,
dialogOptions,
advantage = null,
disadvantage = null,
critical = 20,
fumble = 1,
targetValue = null,
elvenAccuracy = false,
halflingLucky = false,
reliableTalent = false,
chatMessage = true,
messageData = {}
} = {}) {
// Prepare Message Data // Prepare Message Data
messageData.flavor = flavor || title; messageData.flavor = flavor || title;
messageData.speaker = speaker || ChatMessage.getSpeaker(); messageData.speaker = speaker || ChatMessage.getSpeaker();
@ -116,7 +138,6 @@ export async function d20Roll({parts=[], data={}, event={}, rollMode=null, templ
// Define the inner roll function // Define the inner roll function
const _roll = (parts, adv, form) => { const _roll = (parts, adv, form) => {
// Determine the d20 roll and modifiers // Determine the d20 roll and modifiers
let nd = 1; let nd = 1;
let mods = halflingLucky ? "r1=1" : ""; let mods = halflingLucky ? "r1=1" : "";
@ -144,7 +165,7 @@ export async function d20Roll({parts=[], data={}, event={}, rollMode=null, templ
// Optionally include a situational bonus // Optionally include a situational bonus
if (form) { if (form) {
data['bonus'] = form.bonus.value; data["bonus"] = form.bonus.value;
messageOptions.rollMode = form.rollMode.value; messageOptions.rollMode = form.rollMode.value;
} }
if (!data["bonus"]) parts.pop(); if (!data["bonus"]) parts.pop();
@ -189,8 +210,17 @@ export async function d20Roll({parts=[], data={}, event={}, rollMode=null, templ
}; };
// Create the Roll instance // Create the Roll instance
const roll = fastForward ? _roll(parts, adv) : const roll = fastForward
await _d20RollDialog({template, title, parts, data, rollMode: messageOptions.rollMode, dialogOptions, roll: _roll}); ? _roll(parts, adv)
: await _d20RollDialog({
template,
title,
parts,
data,
rollMode: messageOptions.rollMode,
dialogOptions,
roll: _roll
});
// Create a Chat Message // Create a Chat Message
if (roll && chatMessage) roll.toMessage(messageData, messageOptions); if (roll && chatMessage) roll.toMessage(messageData, messageOptions);
@ -205,7 +235,6 @@ export async function d20Roll({parts=[], data={}, event={}, rollMode=null, templ
* @private * @private
*/ */
async function _d20RollDialog({template, title, parts, data, rollMode, dialogOptions, roll} = {}) { async function _d20RollDialog({template, title, parts, data, rollMode, dialogOptions, roll} = {}) {
// Render modal dialog // Render modal dialog
template = template || "systems/sw5e/templates/chat/roll-dialog.html"; template = template || "systems/sw5e/templates/chat/roll-dialog.html";
let dialogData = { let dialogData = {
@ -219,7 +248,8 @@ async function _d20RollDialog({template, title, parts, data, rollMode, dialogOpt
// Create the Dialog window // Create the Dialog window
return new Promise(resolve => { return new Promise(resolve => {
new Dialog({ new Dialog(
{
title: title, title: title,
content: html, content: html,
buttons: { buttons: {
@ -238,7 +268,9 @@ async function _d20RollDialog({template, title, parts, data, rollMode, dialogOpt
}, },
default: "normal", default: "normal",
close: () => resolve(null) close: () => resolve(null)
}, dialogOptions).render(true); },
dialogOptions
).render(true);
}); });
} }
@ -271,10 +303,25 @@ async function _d20RollDialog({template, title, parts, data, rollMode, dialogOpt
* *
* @return {Promise} A Promise which resolves once the roll workflow has completed * @return {Promise} A Promise which resolves once the roll workflow has completed
*/ */
export async function damageRoll({parts, actor, data, event={}, rollMode=null, template, title, speaker, flavor, export async function damageRoll({
allowCritical=true, critical=false, criticalBonusDice=0, criticalMultiplier=2, fastForward=null, parts,
dialogOptions={}, chatMessage=true, messageData={}}={}) { actor,
data,
event = {},
rollMode = null,
template,
title,
speaker,
flavor,
allowCritical = true,
critical = false,
criticalBonusDice = 0,
criticalMultiplier = 2,
fastForward = null,
dialogOptions = {},
chatMessage = true,
messageData = {}
} = {}) {
// Prepare Message Data // Prepare Message Data
messageData.flavor = flavor || title; messageData.flavor = flavor || title;
messageData.speaker = speaker || ChatMessage.getSpeaker(); messageData.speaker = speaker || ChatMessage.getSpeaker();
@ -283,10 +330,9 @@ export async function damageRoll({parts, actor, data, event={}, rollMode=null, t
// Define inner roll function // Define inner roll function
const _roll = function (parts, crit, form) { const _roll = function (parts, crit, form) {
// Optionally include a situational bonus // Optionally include a situational bonus
if (form) { if (form) {
data['bonus'] = form.bonus.value; data["bonus"] = form.bonus.value;
messageOptions.rollMode = form.rollMode.value; messageOptions.rollMode = form.rollMode.value;
} }
if (!data["bonus"]) parts.pop(); if (!data["bonus"]) parts.pop();
@ -297,7 +343,8 @@ export async function damageRoll({parts, actor, data, event={}, rollMode=null, t
// Modify the damage formula for critical hits // Modify the damage formula for critical hits
if (crit === true) { if (crit === true) {
roll.alter(criticalMultiplier, 0); // Multiply all dice roll.alter(criticalMultiplier, 0); // Multiply all dice
if ( roll.terms[0] instanceof Die ) { // Add bonus dice for only the main dice term if (roll.terms[0] instanceof Die) {
// Add bonus dice for only the main dice term
roll.terms[0].alter(1, criticalBonusDice); roll.terms[0].alter(1, criticalBonusDice);
roll._formula = roll.formula; roll._formula = roll.formula;
} }
@ -307,8 +354,8 @@ export async function damageRoll({parts, actor, data, event={}, rollMode=null, t
// Execute the roll // Execute the roll
try { try {
roll.evaluate() roll.evaluate();
if ( crit ) roll.dice.forEach(d => d.options.critical = true); // TODO workaround core bug which wipes Roll#options on roll if (crit) roll.dice.forEach(d => (d.options.critical = true)); // TODO workaround core bug which wipes Roll#options on roll
return roll; return roll;
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@ -318,14 +365,22 @@ export async function damageRoll({parts, actor, data, event={}, rollMode=null, t
}; };
// Create the Roll instance // Create the Roll instance
const roll = fastForward ? _roll(parts, critical) : await _damageRollDialog({ const roll = fastForward
template, title, parts, data, allowCritical, rollMode: messageOptions.rollMode, dialogOptions, roll: _roll ? _roll(parts, critical)
: await _damageRollDialog({
template,
title,
parts,
data,
allowCritical,
rollMode: messageOptions.rollMode,
dialogOptions,
roll: _roll
}); });
// Create a Chat Message // Create a Chat Message
if (roll && chatMessage) roll.toMessage(messageData, messageOptions); if (roll && chatMessage) roll.toMessage(messageData, messageOptions);
return roll; return roll;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -336,7 +391,6 @@ export async function damageRoll({parts, actor, data, event={}, rollMode=null, t
* @private * @private
*/ */
async function _damageRollDialog({template, title, parts, data, allowCritical, rollMode, dialogOptions, roll} = {}) { async function _damageRollDialog({template, title, parts, data, allowCritical, rollMode, dialogOptions, roll} = {}) {
// Render modal dialog // Render modal dialog
template = template || "systems/sw5e/templates/chat/roll-dialog.html"; template = template || "systems/sw5e/templates/chat/roll-dialog.html";
let dialogData = { let dialogData = {
@ -349,7 +403,8 @@ async function _damageRollDialog({template, title, parts, data, allowCritical, r
// Create the Dialog window // Create the Dialog window
return new Promise(resolve => { return new Promise(resolve => {
new Dialog({ new Dialog(
{
title: title, title: title,
content: html, content: html,
buttons: { buttons: {
@ -361,10 +416,12 @@ async function _damageRollDialog({template, title, parts, data, allowCritical, r
normal: { normal: {
label: game.i18n.localize(allowCritical ? "SW5E.Normal" : "SW5E.Roll"), label: game.i18n.localize(allowCritical ? "SW5E.Normal" : "SW5E.Roll"),
callback: html => resolve(roll(parts, false, html[0].querySelector("form"))) callback: html => resolve(roll(parts, false, html[0].querySelector("form")))
}, }
}, },
default: "normal", default: "normal",
close: () => resolve(null) close: () => resolve(null)
}, dialogOptions).render(true); },
dialogOptions
).render(true);
}); });
} }

8
module/effects.js vendored
View file

@ -10,13 +10,16 @@ export function onManageActiveEffect(event, owner) {
const effect = li.dataset.effectId ? owner.effects.get(li.dataset.effectId) : null; const effect = li.dataset.effectId ? owner.effects.get(li.dataset.effectId) : null;
switch (a.dataset.action) { switch (a.dataset.action) {
case "create": case "create":
return ActiveEffect.create({ return ActiveEffect.create(
{
label: "New Effect", label: "New Effect",
icon: "icons/svg/aura.svg", icon: "icons/svg/aura.svg",
origin: owner.uuid, origin: owner.uuid,
"duration.rounds": li.dataset.effectType === "temporary" ? 1 : undefined, "duration.rounds": li.dataset.effectType === "temporary" ? 1 : undefined,
disabled: li.dataset.effectType === "inactive" disabled: li.dataset.effectType === "inactive"
}, owner).create(); },
owner
).create();
case "edit": case "edit":
return effect.sheet.render(true); return effect.sheet.render(true);
case "delete": case "delete":
@ -32,7 +35,6 @@ export function onManageActiveEffect(event, owner) {
* @return {object} Data for rendering * @return {object} Data for rendering
*/ */
export function prepareActiveEffectCategories(effects) { export function prepareActiveEffectCategories(effects) {
// Define effect header categories // Define effect header categories
const categories = { const categories = {
temporary: { temporary: {

View file

@ -5,7 +5,6 @@ import AbilityUseDialog from "../apps/ability-use-dialog.js";
* Override and extend the basic :class:`Item` implementation * Override and extend the basic :class:`Item` implementation
*/ */
export default class Item5e extends Item { export default class Item5e extends Item {
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Item Properties */ /* Item Properties */
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -20,7 +19,6 @@ export default class Item5e extends Item {
// Case 1 - defined directly by the item // Case 1 - defined directly by the item
if (itemData.ability) return itemData.ability; if (itemData.ability) return itemData.ability;
// Case 2 - inferred from a parent actor // Case 2 - inferred from a parent actor
else if (this.actor) { else if (this.actor) {
const actorData = this.actor.data.data; const actorData = this.actor.data.data;
@ -28,26 +26,29 @@ export default class Item5e extends Item {
// Powers - Use Actor powercasting modifier based on power school // Powers - Use Actor powercasting modifier based on power school
if (this.data.type === "power") { if (this.data.type === "power") {
switch (this.data.data.school) { switch (this.data.data.school) {
case "lgt": return "wis"; case "lgt":
case "uni": return (actorData.abilities["wis"].mod >= actorData.abilities["cha"].mod) ? "wis" : "cha"; return "wis";
case "drk": return "cha"; case "uni":
case "tec": return "int"; return actorData.abilities["wis"].mod >= actorData.abilities["cha"].mod ? "wis" : "cha";
case "drk":
return "cha";
case "tec":
return "int";
} }
return "none"; return "none";
} }
// Tools - default to Intelligence // Tools - default to Intelligence
else if (this.data.type === "tool") return "int"; else if (this.data.type === "tool") return "int";
// Weapons // Weapons
else if (this.data.type === "weapon") { else if (this.data.type === "weapon") {
const wt = itemData.weaponType; const wt = itemData.weaponType;
// Melee weapons - Str or Dex if Finesse (PHB pg. 147) // Melee weapons - Str or Dex if Finesse (PHB pg. 147)
if (["simpleVW", "martialVW", "simpleLW", "martialLW"].includes(wt)) { if (["simpleVW", "martialVW", "simpleLW", "martialLW"].includes(wt)) {
if (itemData.properties.fin === true) { // Finesse weapons if (itemData.properties.fin === true) {
return (actorData.abilities["dex"].mod >= actorData.abilities["str"].mod) ? "dex" : "str"; // Finesse weapons
return actorData.abilities["dex"].mod >= actorData.abilities["str"].mod ? "dex" : "str";
} }
return "str"; return "str";
} }
@ -59,7 +60,7 @@ export default class Item5e extends Item {
} }
// Case 3 - unknown // Case 3 - unknown
return null return null;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -99,7 +100,7 @@ export default class Item5e extends Item {
* @return {boolean} * @return {boolean}
*/ */
get isHealing() { get isHealing() {
return (this.data.data.actionType === "heal") && this.data.data.damage.parts.length; return this.data.data.actionType === "heal" && this.data.data.damage.parts.length;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -132,7 +133,7 @@ export default class Item5e extends Item {
*/ */
get hasAreaTarget() { get hasAreaTarget() {
const target = this.data.data.target; const target = this.data.data.target;
return target && (target.type in CONFIG.SW5E.areaTargetTypes); return target && target.type in CONFIG.SW5E.areaTargetTypes;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -144,7 +145,7 @@ export default class Item5e extends Item {
get hasLimitedUses() { get hasLimitedUses() {
let chg = this.data.data.recharge || {}; let chg = this.data.data.recharge || {};
let uses = this.data.data.uses || {}; let uses = this.data.data.uses || {};
return !!chg.value || (!!uses.per && (uses.max > 0)); return !!chg.value || (!!uses.per && uses.max > 0);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -161,7 +162,7 @@ export default class Item5e extends Item {
const itemData = this.data; const itemData = this.data;
const data = itemData.data; const data = itemData.data;
const C = CONFIG.SW5E; const C = CONFIG.SW5E;
const labels = this.labels = {}; const labels = (this.labels = {});
// Classes // Classes
if (itemData.type === "class") { if (itemData.type === "class") {
@ -184,9 +185,12 @@ export default class Item5e extends Item {
// Feat Items // Feat Items
else if (itemData.type === "feat") { else if (itemData.type === "feat") {
const act = data.activation; const act = data.activation;
if ( act && (act.type === C.abilityActivationTypes.legendary) ) labels.featType = game.i18n.localize("SW5E.LegendaryActionLabel"); if (act && act.type === C.abilityActivationTypes.legendary)
else if ( act && (act.type === C.abilityActivationTypes.lair) ) labels.featType = game.i18n.localize("SW5E.LairActionLabel"); labels.featType = game.i18n.localize("SW5E.LegendaryActionLabel");
else if ( act && act.type ) labels.featType = game.i18n.localize(data.damage.length ? "SW5E.Attack" : "SW5E.Action"); else if (act && act.type === C.abilityActivationTypes.lair)
labels.featType = game.i18n.localize("SW5E.LairActionLabel");
else if (act && act.type)
labels.featType = game.i18n.localize(data.damage.length ? "SW5E.Attack" : "SW5E.Action");
else labels.featType = game.i18n.localize("SW5E.Passive"); else labels.featType = game.i18n.localize("SW5E.Passive");
} }
@ -226,7 +230,6 @@ export default class Item5e extends Item {
// Activated Items // Activated Items
if (data.hasOwnProperty("activation")) { if (data.hasOwnProperty("activation")) {
// Ability Activation Label // Ability Activation Label
let act = data.activation || {}; let act = data.activation || {};
if (act) labels.activation = [act.cost, C.abilityActivationTypes[act.type]].filterJoin(" "); if (act) labels.activation = [act.cost, C.abilityActivationTypes[act.type]].filterJoin(" ");
@ -242,7 +245,7 @@ export default class Item5e extends Item {
// Range Label // Range Label
let rng = data.range || {}; let rng = data.range || {};
if (["none", "touch", "self"].includes(rng.units) || (rng.value === 0)) { if (["none", "touch", "self"].includes(rng.units) || rng.value === 0) {
rng.value = null; rng.value = null;
rng.long = null; rng.long = null;
} }
@ -272,7 +275,10 @@ export default class Item5e extends Item {
// Damage // Damage
let dam = data.damage || {}; let dam = data.damage || {};
if (dam.parts) { if (dam.parts) {
labels.damage = dam.parts.map(d => d[0]).join(" + ").replace(/\+ -/g, "- "); labels.damage = dam.parts
.map(d => d[0])
.join(" + ")
.replace(/\+ -/g, "- ");
labels.damageTypes = dam.parts.map(d => C.damageTypes[d[1]]).join(", "); labels.damageTypes = dam.parts.map(d => C.damageTypes[d[1]]).join(", ");
} }
@ -353,7 +359,7 @@ export default class Item5e extends Item {
// Include the item's innate attack bonus as the initial value and label // Include the item's innate attack bonus as the initial value and label
if (itemData.attackBonus) { if (itemData.attackBonus) {
parts.push(itemData.attackBonus) parts.push(itemData.attackBonus);
this.labels.toHit = itemData.attackBonus; this.labels.toHit = itemData.attackBonus;
} }
@ -373,14 +379,14 @@ export default class Item5e extends Item {
if (actorBonus.attack) parts.push(actorBonus.attack); if (actorBonus.attack) parts.push(actorBonus.attack);
// One-time bonus provided by consumed ammunition // One-time bonus provided by consumed ammunition
if ( (itemData.consume?.type === 'ammo') && !!this.actor.items ) { if (itemData.consume?.type === "ammo" && !!this.actor.items) {
const ammoItemData = this.actor.items.get(itemData.consume.target)?.data; const ammoItemData = this.actor.items.get(itemData.consume.target)?.data;
if (ammoItemData) { if (ammoItemData) {
const ammoItemQuantity = ammoItemData.data.quantity; const ammoItemQuantity = ammoItemData.data.quantity;
const ammoCanBeConsumed = ammoItemQuantity && (ammoItemQuantity - (itemData.consume.amount ?? 0) >= 0); const ammoCanBeConsumed = ammoItemQuantity && ammoItemQuantity - (itemData.consume.amount ?? 0) >= 0;
const ammoItemAttackBonus = ammoItemData.data.attackBonus; const ammoItemAttackBonus = ammoItemData.data.attackBonus;
const ammoIsTypeConsumable = (ammoItemData.type === "consumable") && (ammoItemData.data.consumableType === "ammo") const ammoIsTypeConsumable = ammoItemData.type === "consumable" && ammoItemData.data.consumableType === "ammo";
if (ammoCanBeConsumed && ammoItemAttackBonus && ammoIsTypeConsumable) { if (ammoCanBeConsumed && ammoItemAttackBonus && ammoIsTypeConsumable) {
parts.push("@ammo"); parts.push("@ammo");
rollData["ammo"] = ammoItemAttackBonus; rollData["ammo"] = ammoItemAttackBonus;
@ -389,9 +395,9 @@ export default class Item5e extends Item {
} }
// Condense the resulting attack bonus formula into a simplified label // Condense the resulting attack bonus formula into a simplified label
let toHitLabel = simplifyRollFormula(parts.join('+'), rollData).trim(); let toHitLabel = simplifyRollFormula(parts.join("+"), rollData).trim();
if (toHitLabel.charAt(0) !== '-') { if (toHitLabel.charAt(0) !== "-") {
toHitLabel = '+ ' + toHitLabel toHitLabel = "+ " + toHitLabel;
} }
this.labels.toHit = toHitLabel; this.labels.toHit = toHitLabel;
@ -421,23 +427,24 @@ export default class Item5e extends Item {
const uses = id?.uses ?? {}; // Limited uses const uses = id?.uses ?? {}; // Limited uses
const isPower = this.type === "power"; // Does the item require a power slot? const isPower = this.type === "power"; // Does the item require a power slot?
// TODO: Possibly Mod this to not consume slots based on class? // TODO: Possibly Mod this to not consume slots based on class?
const requirePowerSlot = isPower && (id.level > 0) && CONFIG.SW5E.powerUpcastModes.includes(id.preparation.mode); const requirePowerSlot = isPower && id.level > 0 && CONFIG.SW5E.powerUpcastModes.includes(id.preparation.mode);
// Define follow-up actions resulting from the item usage // Define follow-up actions resulting from the item usage
let createMeasuredTemplate = hasArea; // Trigger a template creation let createMeasuredTemplate = hasArea; // Trigger a template creation
let consumeRecharge = !!recharge.value; // Consume recharge let consumeRecharge = !!recharge.value; // Consume recharge
let consumeResource = !!resource.target && resource.type !== "ammo" && !['simpleB', 'martialB'].includes(id.weaponType); // Consume a linked (non-ammo) resource, ignore if use is from a blaster let consumeResource =
!!resource.target && resource.type !== "ammo" && !["simpleB", "martialB"].includes(id.weaponType); // Consume a linked (non-ammo) resource, ignore if use is from a blaster
let consumePowerSlot = requirePowerSlot; // Consume a power slot let consumePowerSlot = requirePowerSlot; // Consume a power slot
let consumeUsage = !!uses.per; // Consume limited uses let consumeUsage = !!uses.per; // Consume limited uses
let consumeQuantity = uses.autoDestroy; // Consume quantity of the item in lieu of uses let consumeQuantity = uses.autoDestroy; // Consume quantity of the item in lieu of uses
// Display a configuration dialog to customize the usage // Display a configuration dialog to customize the usage
const needsConfiguration = createMeasuredTemplate || consumeRecharge || consumeResource || consumePowerSlot || consumeUsage; const needsConfiguration =
createMeasuredTemplate || consumeRecharge || consumeResource || consumePowerSlot || consumeUsage;
if (configureDialog && needsConfiguration) { if (configureDialog && needsConfiguration) {
const configuration = await AbilityUseDialog.create(this); const configuration = await AbilityUseDialog.create(this);
if (!configuration) return; if (!configuration) return;
// Determine consumption preferences // Determine consumption preferences
createMeasuredTemplate = Boolean(configuration.placeTemplate); createMeasuredTemplate = Boolean(configuration.placeTemplate);
consumeUsage = Boolean(configuration.consumeUse); consumeUsage = Boolean(configuration.consumeUse);
@ -459,14 +466,20 @@ export default class Item5e extends Item {
} }
// Determine whether the item can be used by testing for resource consumption // Determine whether the item can be used by testing for resource consumption
const usage = item._getUsageUpdates({consumeRecharge, consumeResource, consumePowerSlot, consumeUsage, consumeQuantity}); const usage = item._getUsageUpdates({
consumeRecharge,
consumeResource,
consumePowerSlot,
consumeUsage,
consumeQuantity
});
if (!usage) return; if (!usage) return;
const {actorUpdates, itemUpdates, resourceUpdates} = usage; const {actorUpdates, itemUpdates, resourceUpdates} = usage;
// Commit pending data updates // Commit pending data updates
if (!isObjectEmpty(itemUpdates)) await item.update(itemUpdates); if (!isObjectEmpty(itemUpdates)) await item.update(itemUpdates);
if ( consumeQuantity && (item.data.data.quantity === 0) ) await item.delete(); if (consumeQuantity && item.data.data.quantity === 0) await item.delete();
if (!isObjectEmpty(actorUpdates)) await actor.update(actorUpdates); if (!isObjectEmpty(actorUpdates)) await actor.update(actorUpdates);
if (!isObjectEmpty(resourceUpdates)) { if (!isObjectEmpty(resourceUpdates)) {
const resource = actor.items.get(id.consume?.target); const resource = actor.items.get(id.consume?.target);
@ -496,8 +509,13 @@ export default class Item5e extends Item {
* @returns {object|boolean} A set of data changes to apply when the item is used, or false * @returns {object|boolean} A set of data changes to apply when the item is used, or false
* @private * @private
*/ */
_getUsageUpdates({consumeQuantity=false, consumeRecharge=false, consumeResource=false, consumePowerSlot=false, consumeUsage=false}) { _getUsageUpdates({
consumeQuantity = false,
consumeRecharge = false,
consumeResource = false,
consumePowerSlot = false,
consumeUsage = false
}) {
// Reference item data // Reference item data
const id = this.data.data; const id = this.data.data;
const actorUpdates = {}; const actorUpdates = {};
@ -526,7 +544,7 @@ export default class Item5e extends Item {
const fp = this.actor.data.data.attributes.force.points; const fp = this.actor.data.data.attributes.force.points;
const tp = this.actor.data.data.attributes.tech.points; const tp = this.actor.data.data.attributes.tech.points;
const powerCost = id.level + 1; const powerCost = id.level + 1;
const innatePower = this.actor.data.data.attributes.powercasting === 'innate'; const innatePower = this.actor.data.data.attributes.powercasting === "innate";
if (!innatePower) { if (!innatePower) {
switch (id.school) { switch (id.school) {
case "lgt": case "lgt":
@ -567,7 +585,6 @@ export default class Item5e extends Item {
} }
} }
// Consume Limited Usage // Consume Limited Usage
if (consumeUsage) { if (consumeUsage) {
const uses = id.uses || {}; const uses = id.uses || {};
@ -582,7 +599,7 @@ export default class Item5e extends Item {
} }
// Reduce quantity if not reducing usages or if usages hit 0 and we are set to consumeQuantity // Reduce quantity if not reducing usages or if usages hit 0 and we are set to consumeQuantity
if ( consumeQuantity && (!used || (remaining === 0)) ) { if (consumeQuantity && (!used || remaining === 0)) {
const q = Number(id.quantity ?? 1); const q = Number(id.quantity ?? 1);
if (q >= 1) { if (q >= 1) {
used = true; used = true;
@ -692,7 +709,6 @@ export default class Item5e extends Item {
* the prepared message data (if false) * the prepared message data (if false)
*/ */
async displayCard({rollMode, createMessage = true} = {}) { async displayCard({rollMode, createMessage = true} = {}) {
// Basic template rendering data // Basic template rendering data
const token = this.actor.token; const token = this.actor.token;
const templateData = { const templateData = {
@ -726,7 +742,7 @@ export default class Item5e extends Item {
}; };
// If the Item was destroyed in the process of displaying its card - embed the item data in the chat message // If the Item was destroyed in the process of displaying its card - embed the item data in the chat message
if ( (this.data.type === "consumable") && !this.actor.items.has(this.id) ) { if (this.data.type === "consumable" && !this.actor.items.has(this.id)) {
chatData.flags["sw5e.itemData"] = this.data; chatData.flags["sw5e.itemData"] = this.data;
} }
@ -760,10 +776,11 @@ export default class Item5e extends Item {
// Equipment properties // Equipment properties
if (data.hasOwnProperty("equipped") && !["loot", "tool"].includes(this.data.type)) { if (data.hasOwnProperty("equipped") && !["loot", "tool"].includes(this.data.type)) {
if ( data.attunement === CONFIG.SW5E.attunementTypes.REQUIRED ) props.push(game.i18n.localize(CONFIG.SW5E.attunements[CONFIG.SW5E.attunementTypes.REQUIRED])); if (data.attunement === CONFIG.SW5E.attunementTypes.REQUIRED)
props.push(game.i18n.localize(CONFIG.SW5E.attunements[CONFIG.SW5E.attunementTypes.REQUIRED]));
props.push( props.push(
game.i18n.localize(data.equipped ? "SW5E.Equipped" : "SW5E.Unequipped"), game.i18n.localize(data.equipped ? "SW5E.Equipped" : "SW5E.Unequipped"),
game.i18n.localize(data.proficient ? "SW5E.Proficient" : "SW5E.NotProficient"), game.i18n.localize(data.proficient ? "SW5E.Proficient" : "SW5E.NotProficient")
); );
} }
@ -803,9 +820,7 @@ export default class Item5e extends Item {
* @private * @private
*/ */
_weaponChatData(data, labels, props) { _weaponChatData(data, labels, props) {
props.push( props.push(CONFIG.SW5E.weaponTypes[data.weaponType]);
CONFIG.SW5E.weaponTypes[data.weaponType],
);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -829,10 +844,7 @@ export default class Item5e extends Item {
* @private * @private
*/ */
_toolChatData(data, labels, props) { _toolChatData(data, labels, props) {
props.push( props.push(CONFIG.SW5E.abilities[data.ability] || null, CONFIG.SW5E.proficiencyLevels[data.proficient || 0]);
CONFIG.SW5E.abilities[data.ability] || null,
CONFIG.SW5E.proficiencyLevels[data.proficient || 0]
);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -856,10 +868,7 @@ export default class Item5e extends Item {
* @private * @private
*/ */
_powerChatData(data, labels, props) { _powerChatData(data, labels, props) {
props.push( props.push(labels.level, labels.components + (labels.materials ? ` (${labels.materials})` : ""));
labels.level,
labels.components + (labels.materials ? ` (${labels.materials})` : "")
);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -904,7 +913,7 @@ export default class Item5e extends Item {
if (ammo?.data) { if (ammo?.data) {
const q = ammo.data.data.quantity; const q = ammo.data.data.quantity;
const consumeAmount = consume.amount ?? 0; const consumeAmount = consume.amount ?? 0;
if ( q && (q - consumeAmount >= 0) ) { if (q && q - consumeAmount >= 0) {
this._ammo = ammo; this._ammo = ammo;
title += ` [${ammo.name}]`; title += ` [${ammo.name}]`;
} }
@ -917,7 +926,8 @@ export default class Item5e extends Item {
} }
// Compose roll options // Compose roll options
const rollConfig = mergeObject({ const rollConfig = mergeObject(
{
parts: parts, parts: parts,
actor: this.actor, actor: this.actor,
data: rollData, data: rollData,
@ -930,13 +940,15 @@ export default class Item5e extends Item {
left: window.innerWidth - 710 left: window.innerWidth - 710
}, },
messageData: {"flags.sw5e.roll": {type: "attack", itemId: this.id}} messageData: {"flags.sw5e.roll": {type: "attack", itemId: this.id}}
}, options); },
options
);
rollConfig.event = options.event; rollConfig.event = options.event;
// Expanded critical hit thresholds // Expanded critical hit thresholds
if (( this.data.type === "weapon" ) && flags.weaponCriticalThreshold) { if (this.data.type === "weapon" && flags.weaponCriticalThreshold) {
rollConfig.critical = parseInt(flags.weaponCriticalThreshold); rollConfig.critical = parseInt(flags.weaponCriticalThreshold);
} else if (( this.data.type === "power" ) && flags.powerCriticalThreshold) { } else if (this.data.type === "power" && flags.powerCriticalThreshold) {
rollConfig.critical = parseInt(flags.powerCriticalThreshold); rollConfig.critical = parseInt(flags.powerCriticalThreshold);
} }
@ -1010,12 +1022,11 @@ export default class Item5e extends Item {
} }
// Scale damage from up-casting powers // Scale damage from up-casting powers
if ( (this.data.type === "power") ) { if (this.data.type === "power") {
if ( (itemData.scaling.mode === "atwill") ) { if (itemData.scaling.mode === "atwill") {
const level = this.actor.data.type === "character" ? actorData.details.level : actorData.details.powerLevel; const level = this.actor.data.type === "character" ? actorData.details.level : actorData.details.powerLevel;
this._scaleAtWillDamage(parts, itemData.scaling.formula, level, rollData); this._scaleAtWillDamage(parts, itemData.scaling.formula, level, rollData);
} } else if (powerLevel && itemData.scaling.mode === "level" && itemData.scaling.formula) {
else if ( powerLevel && (itemData.scaling.mode === "level") && itemData.scaling.formula ) {
const scaling = itemData.scaling.formula; const scaling = itemData.scaling.formula;
this._scalePowerDamage(parts, itemData.level, powerLevel, scaling, rollData); this._scalePowerDamage(parts, itemData.level, powerLevel, scaling, rollData);
} }
@ -1023,7 +1034,7 @@ export default class Item5e extends Item {
// Add damage bonus formula // Add damage bonus formula
const actorBonus = getProperty(actorData, `bonuses.${itemData.actionType}`) || {}; const actorBonus = getProperty(actorData, `bonuses.${itemData.actionType}`) || {};
if ( actorBonus.damage && (parseInt(actorBonus.damage) !== 0) ) { if (actorBonus.damage && parseInt(actorBonus.damage) !== 0) {
parts.push(actorBonus.damage); parts.push(actorBonus.damage);
} }
@ -1031,7 +1042,7 @@ export default class Item5e extends Item {
const ammoData = this._ammo?.data; const ammoData = this._ammo?.data;
// only add the ammunition damage if the ammution is a consumable with type 'ammo' // only add the ammunition damage if the ammution is a consumable with type 'ammo'
if ( this._ammo && (ammoData.type === "consumable") && (ammoData.data.consumableType === "ammo") ) { if (this._ammo && ammoData.type === "consumable" && ammoData.data.consumableType === "ammo") {
parts.push("@ammo"); parts.push("@ammo");
rollData["ammo"] = ammoData.data.damage.parts.map(p => p[0]).join("+"); rollData["ammo"] = ammoData.data.damage.parts.map(p => p[0]).join("+");
rollConfig.flavor += ` [${this._ammo.name}]`; rollConfig.flavor += ` [${this._ammo.name}]`;
@ -1095,10 +1106,10 @@ export default class Item5e extends Item {
// Attempt to simplify by combining like dice terms // Attempt to simplify by combining like dice terms
let simplified = false; let simplified = false;
if ( (s.terms[0] instanceof Die) && (s.terms.length === 1) ) { if (s.terms[0] instanceof Die && s.terms.length === 1) {
const d0 = p0.terms[0]; const d0 = p0.terms[0];
const s0 = s.terms[0]; const s0 = s.terms[0];
if ( (d0 instanceof Die) && (d0.faces === s0.faces) && d0.modifiers.equals(s0.modifiers) ) { if (d0 instanceof Die && d0.faces === s0.faces && d0.modifiers.equals(s0.modifiers)) {
d0.number += s0.number; d0.number += s0.number;
parts[0] = p0.formula; parts[0] = p0.formula;
simplified = true; simplified = true;
@ -1156,10 +1167,14 @@ export default class Item5e extends Item {
const success = roll.total >= parseInt(data.recharge.value); const success = roll.total >= parseInt(data.recharge.value);
// Display a Chat Message // Display a Chat Message
const promises = [roll.toMessage({ const promises = [
flavor: `${game.i18n.format("SW5E.ItemRechargeCheck", {name: this.name})} - ${game.i18n.localize(success ? "SW5E.ItemRechargeSuccess" : "SW5E.ItemRechargeFailure")}`, roll.toMessage({
flavor: `${game.i18n.format("SW5E.ItemRechargeCheck", {name: this.name})} - ${game.i18n.localize(
success ? "SW5E.ItemRechargeSuccess" : "SW5E.ItemRechargeFailure"
)}`,
speaker: ChatMessage.getSpeaker({actor: this.actor, token: this.actor.token}) speaker: ChatMessage.getSpeaker({actor: this.actor, token: this.actor.token})
})]; })
];
// Update the Item data // Update the Item data
if (success) promises.push(this.update({"data.recharge.charged": true})); if (success) promises.push(this.update({"data.recharge.charged": true}));
@ -1182,7 +1197,8 @@ export default class Item5e extends Item {
const title = `${this.name} - ${game.i18n.localize("SW5E.ToolCheck")}`; const title = `${this.name} - ${game.i18n.localize("SW5E.ToolCheck")}`;
// Compose the roll data // Compose the roll data
const rollConfig = mergeObject({ const rollConfig = mergeObject(
{
parts: parts, parts: parts,
data: rollData, data: rollData,
template: "systems/sw5e/templates/chat/tool-roll-dialog.html", template: "systems/sw5e/templates/chat/tool-roll-dialog.html",
@ -1192,12 +1208,14 @@ export default class Item5e extends Item {
dialogOptions: { dialogOptions: {
width: 400, width: 400,
top: options.event ? options.event.clientY - 80 : null, top: options.event ? options.event.clientY - 80 : null,
left: window.innerWidth - 710, left: window.innerWidth - 710
}, },
halflingLucky: this.actor.getFlag("sw5e", "halflingLucky") || false, halflingLucky: this.actor.getFlag("sw5e", "halflingLucky") || false,
reliableTalent: (this.data.data.proficient >= 1) && this.actor.getFlag("sw5e", "reliableTalent"), reliableTalent: this.data.data.proficient >= 1 && this.actor.getFlag("sw5e", "reliableTalent"),
messageData: {"flags.sw5e.roll": {type: "tool", itemId: this.id}} messageData: {"flags.sw5e.roll": {type: "tool", itemId: this.id}}
}, options); },
options
);
rollConfig.event = options.event; rollConfig.event = options.event;
// Call the roll helper utility // Call the roll helper utility
@ -1223,7 +1241,7 @@ export default class Item5e extends Item {
} }
// Include a proficiency score // Include a proficiency score
const prof = ("proficient" in rollData.item) ? (rollData.item.proficient || 0) : 1; const prof = "proficient" in rollData.item ? rollData.item.proficient || 0 : 1;
rollData["prof"] = Math.floor(prof * (rollData.attributes.prof || 0)); rollData["prof"] = Math.floor(prof * (rollData.attributes.prof || 0));
return rollData; return rollData;
} }
@ -1233,8 +1251,8 @@ export default class Item5e extends Item {
/* -------------------------------------------- */ /* -------------------------------------------- */
static chatListeners(html) { static chatListeners(html) {
html.on('click', '.card-buttons button', this._onChatCardAction.bind(this)); html.on("click", ".card-buttons button", this._onChatCardAction.bind(this));
html.on('click', '.item-name', this._onChatCardToggleContent.bind(this)); html.on("click", ".item-name", this._onChatCardToggleContent.bind(this));
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -1268,14 +1286,17 @@ export default class Item5e extends Item {
const storedData = message.getFlag("sw5e", "itemData"); const storedData = message.getFlag("sw5e", "itemData");
const item = storedData ? this.createOwned(storedData, actor) : actor.getOwnedItem(card.dataset.itemId); const item = storedData ? this.createOwned(storedData, actor) : actor.getOwnedItem(card.dataset.itemId);
if (!item) { if (!item) {
return ui.notifications.error(game.i18n.format("SW5E.ActionWarningNoItem", {item: card.dataset.itemId, name: actor.name})) return ui.notifications.error(
game.i18n.format("SW5E.ActionWarningNoItem", {item: card.dataset.itemId, name: actor.name})
);
} }
const powerLevel = parseInt(card.dataset.powerLevel) || null; const powerLevel = parseInt(card.dataset.powerLevel) || null;
// Handle different actions // Handle different actions
switch (action) { switch (action) {
case "attack": case "attack":
await item.rollAttack({event}); break; await item.rollAttack({event});
break;
case "damage": case "damage":
case "versatile": case "versatile":
await item.rollDamage({ await item.rollDamage({
@ -1286,7 +1307,8 @@ export default class Item5e extends Item {
}); });
break; break;
case "formula": case "formula":
await item.rollFormula({event, powerLevel}); break; await item.rollFormula({event, powerLevel});
break;
case "save": case "save":
const targets = this._getChatCardTargets(card); const targets = this._getChatCardTargets(card);
for (let token of targets) { for (let token of targets) {
@ -1295,7 +1317,8 @@ export default class Item5e extends Item {
} }
break; break;
case "toolCheck": case "toolCheck":
await item.rollToolCheck({event}); break; await item.rollToolCheck({event});
break;
case "placeTemplate": case "placeTemplate":
const template = game.sw5e.canvas.AbilityTemplate.fromItem(item); const template = game.sw5e.canvas.AbilityTemplate.fromItem(item);
if (template) template.drawPreview(); if (template) template.drawPreview();
@ -1330,7 +1353,6 @@ export default class Item5e extends Item {
* @private * @private
*/ */
static _getChatCardActor(card) { static _getChatCardActor(card) {
// Case 1 - a synthetic actor from a Token // Case 1 - a synthetic actor from a Token
const tokenKey = card.dataset.tokenId; const tokenKey = card.dataset.tokenId;
if (tokenKey) { if (tokenKey) {
@ -1374,7 +1396,6 @@ export default class Item5e extends Item {
* @private * @private
*/ */
static async createScrollFromPower(power) { static async createScrollFromPower(power) {
// Get power data // Get power data
const itemData = power instanceof Item5e ? power.data : power; const itemData = power instanceof Item5e ? power.data : power;
const {actionType, description, source, activation, duration, target, range, damage, save, level} = itemData.data; const {actionType, description, source, activation, duration, target, range, damage, save, level} = itemData.data;
@ -1387,7 +1408,7 @@ export default class Item5e extends Item {
// Split the scroll description into an intro paragraph and the remaining details // Split the scroll description into an intro paragraph and the remaining details
const scrollDescription = scrollData.data.description.value; const scrollDescription = scrollData.data.description.value;
const pdel = '</p>'; const pdel = "</p>";
const scrollIntroEnd = scrollDescription.indexOf(pdel); const scrollIntroEnd = scrollDescription.indexOf(pdel);
const scrollIntro = scrollDescription.slice(0, scrollIntroEnd + pdel.length); const scrollIntro = scrollDescription.slice(0, scrollIntroEnd + pdel.length);
const scrollDetails = scrollDescription.slice(scrollIntroEnd + pdel.length); const scrollDetails = scrollDescription.slice(scrollIntroEnd + pdel.length);

View file

@ -171,8 +171,8 @@ export default class ItemSheet5e extends ItemSheet {
if (item.type === "weapon") { if (item.type === "weapon") {
props.push( props.push(
...Object.entries(item.data.properties) ...Object.entries(item.data.properties)
.filter((e) => e[1] === true) .filter(e => e[1] === true)
.map((e) => CONFIG.SW5E.weaponProperties[e[0]]) .map(e => CONFIG.SW5E.weaponProperties[e[0]])
); );
} else if (item.type === "power") { } else if (item.type === "power") {
props.push( props.push(
@ -211,7 +211,7 @@ export default class ItemSheet5e extends ItemSheet {
if (item.type !== "weapon" && item.data.activation && !isObjectEmpty(item.data.activation)) { if (item.type !== "weapon" && item.data.activation && !isObjectEmpty(item.data.activation)) {
props.push(labels.activation, labels.range, labels.target, labels.duration); props.push(labels.activation, labels.range, labels.target, labels.duration);
} }
return props.filter((p) => !!p); return props.filter(p => !!p);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -256,7 +256,7 @@ export default class ItemSheet5e extends ItemSheet {
// Handle Damage array // Handle Damage array
const damage = data.data?.damage; const damage = data.data?.damage;
if (damage) damage.parts = Object.values(damage?.parts || {}).map((d) => [d[0] || "", d[1] || ""]); if (damage) damage.parts = Object.values(damage?.parts || {}).map(d => [d[0] || "", d[1] || ""]);
// Return the flattened submission data // Return the flattened submission data
return flattenObject(data); return flattenObject(data);
@ -270,7 +270,7 @@ export default class ItemSheet5e extends ItemSheet {
if (this.isEditable) { if (this.isEditable) {
html.find(".damage-control").click(this._onDamageControl.bind(this)); html.find(".damage-control").click(this._onDamageControl.bind(this));
html.find(".trait-selector.class-skills").click(this._onConfigureClassSkills.bind(this)); html.find(".trait-selector.class-skills").click(this._onConfigureClassSkills.bind(this));
html.find(".effect-control").click((ev) => { html.find(".effect-control").click(ev => {
if (this.item.isOwned) if (this.item.isOwned)
return ui.notifications.warn( return ui.notifications.warn(
"Managing Active Effects within an Owned Item is not currently supported and will be added in a subsequent update." "Managing Active Effects within an Owned Item is not currently supported and will be added in a subsequent update."

View file

@ -1,4 +1,3 @@
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Hotbar Macros */ /* Hotbar Macros */
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -17,7 +16,7 @@ export async function create5eMacro(data, slot) {
// Create the macro command // Create the macro command
const command = `game.sw5e.rollItemMacro("${item.name}");`; const command = `game.sw5e.rollItemMacro("${item.name}");`;
let macro = game.macros.entities.find(m => (m.name === item.name) && (m.command === command)); let macro = game.macros.entities.find(m => m.name === item.name && m.command === command);
if (!macro) { if (!macro) {
macro = await Macro.create({ macro = await Macro.create({
name: item.name, name: item.name,
@ -48,7 +47,9 @@ export function rollItemMacro(itemName) {
// Get matching items // Get matching items
const items = actor ? actor.items.filter(i => i.name === itemName) : []; const items = actor ? actor.items.filter(i => i.name === itemName) : [];
if (items.length > 1) { if (items.length > 1) {
ui.notifications.warn(`Your controlled Actor ${actor.name} has more than one Item with name ${itemName}. The first matched item will be chosen.`); ui.notifications.warn(
`Your controlled Actor ${actor.name} has more than one Item with name ${itemName}. The first matched item will be chosen.`
);
} else if (items.length === 0) { } else if (items.length === 0) {
return ui.notifications.warn(`Your controlled Actor does not have an item named ${itemName}`); return ui.notifications.warn(`Your controlled Actor does not have an item named ${itemName}`);
} }

View file

@ -3,7 +3,10 @@
* @return {Promise} A Promise which resolves once the migration is completed * @return {Promise} A Promise which resolves once the migration is completed
*/ */
export const migrateWorld = async function () { export const migrateWorld = async function () {
ui.notifications.info(`Applying SW5e System Migration for version ${game.system.data.version}. Please be patient and do not close your game or shut down your server.`, {permanent: true}); ui.notifications.info(
`Applying SW5e System Migration for version ${game.system.data.version}. Please be patient and do not close your game or shut down your server.`,
{permanent: true}
);
// Migrate World Actors // Migrate World Actors
for await (let a of game.actors.entities) { for await (let a of game.actors.entities) {
@ -100,10 +103,8 @@ export const migrateCompendium = async function(pack) {
updateData["_id"] = ent._id; updateData["_id"] = ent._id;
await pack.updateEntity(updateData); await pack.updateEntity(updateData);
console.log(`Migrated ${entity} entity ${ent.name} in Compendium ${pack.collection}`); console.log(`Migrated ${entity} entity ${ent.name} in Compendium ${pack.collection}`);
} } catch (err) {
// Handle migration failures // Handle migration failures
catch(err) {
err.message = `Failed sw5e system migration for entity ${ent.name} in pack ${pack.collection}: ${err.message}`; err.message = `Failed sw5e system migration for entity ${ent.name} in pack ${pack.collection}: ${err.message}`;
console.error(err); console.error(err);
} }
@ -171,14 +172,12 @@ export const migrateActorData = async function(actor) {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**
* Scrub an Actor's system data, removing all keys which are not explicitly defined in the system template * Scrub an Actor's system data, removing all keys which are not explicitly defined in the system template
* @param {Object} actorData The data object for an Actor * @param {Object} actorData The data object for an Actor
* @return {Object} The scrubbed Actor data * @return {Object} The scrubbed Actor data
*/ */
function cleanActorData(actorData) { function cleanActorData(actorData) {
// Scrub system data // Scrub system data
const model = game.system.model.Actor[actorData.type]; const model = game.system.model.Actor[actorData.type];
actorData.data = filterObject(actorData.data, model); actorData.data = filterObject(actorData.data, model);
@ -196,7 +195,6 @@ function cleanActorData(actorData) {
return actorData; return actorData;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**
@ -236,7 +234,8 @@ export const migrateActorItemData = async function(item, actor) {
export const migrateSceneData = async function (scene) { export const migrateSceneData = async function (scene) {
const tokens = duplicate(scene.tokens); const tokens = duplicate(scene.tokens);
return { return {
tokens: await Promise.all(tokens.map(async (t) => { tokens: await Promise.all(
tokens.map(async t => {
if (!t.actorId || t.actorLink || !t.actorData.data) { if (!t.actorId || t.actorLink || !t.actorData.data) {
t.actorData = {}; t.actorData = {};
return t; return t;
@ -250,7 +249,8 @@ export const migrateActorItemData = async function(item, actor) {
t.actorData = mergeObject(token.data.actorData, updateData); t.actorData = mergeObject(token.data.actorData, updateData);
} }
return t; return t;
})) })
)
}; };
}; };
@ -266,7 +266,6 @@ export const migrateActorItemData = async function(item, actor) {
* @return {Object} The updated Actor * @return {Object} The updated Actor
*/ */
function _updateNPCData(actor) { function _updateNPCData(actor) {
let actorData = actor.data; let actorData = actor.data;
const updateData = {}; const updateData = {};
// check for flag.core, if not there is no compendium monster so exit // check for flag.core, if not there is no compendium monster so exit
@ -274,13 +273,20 @@ function _updateNPCData(actor) {
if (!hasSource) return actor; if (!hasSource) return actor;
// shortcut out if dataVersion flag is set to 1.2.4 or higher // shortcut out if dataVersion flag is set to 1.2.4 or higher
const hasDataVersion = actor?.flags?.sw5e?.dataVersion !== undefined; const hasDataVersion = actor?.flags?.sw5e?.dataVersion !== undefined;
if (hasDataVersion && (actor.flags.sw5e.dataVersion === "1.2.4" || isNewerVersion("1.2.4", actor.flags.sw5e.dataVersion))) return actor; if (
hasDataVersion &&
(actor.flags.sw5e.dataVersion === "1.2.4" || isNewerVersion("1.2.4", actor.flags.sw5e.dataVersion))
)
return actor;
// Check to see what the source of NPC is // Check to see what the source of NPC is
const sourceId = actor.flags.core.sourceId; const sourceId = actor.flags.core.sourceId;
const coreSource = sourceId.substr(0, sourceId.length - 17); const coreSource = sourceId.substr(0, sourceId.length - 17);
const core_id = sourceId.substr(sourceId.length - 16, 16); const core_id = sourceId.substr(sourceId.length - 16, 16);
if (coreSource === "Compendium.sw5e.monsters") { if (coreSource === "Compendium.sw5e.monsters") {
game.packs.get("sw5e.monsters").getEntity(core_id).then(monster => { game.packs
.get("sw5e.monsters")
.getEntity(core_id)
.then(monster => {
const monsterData = monster.data.data; const monsterData = monster.data.data;
// copy movement[], senses[], powercasting, force[], tech[], powerForceLevel, powerTechLevel // copy movement[], senses[], powercasting, force[], tech[], powerForceLevel, powerTechLevel
updateData["data.attributes.movement"] = monsterData.attributes.movement; updateData["data.attributes.movement"] = monsterData.attributes.movement;
@ -296,7 +302,9 @@ function _updateNPCData(actor) {
const itemData = i.data; const itemData = i.data;
if (itemData.type === "power") { if (itemData.type === "power") {
const itemCompendium_id = itemData.flags?.core?.sourceId.split(".").slice(-1)[0]; const itemCompendium_id = itemData.flags?.core?.sourceId.split(".").slice(-1)[0];
let hasPower = !!actor.items.find(item => item.flags?.core?.sourceId.split(".").slice(-1)[0] === itemCompendium_id); let hasPower = !!actor.items.find(
item => item.flags?.core?.sourceId.split(".").slice(-1)[0] === itemCompendium_id
);
if (!hasPower) { if (!hasPower) {
// Clone power to new object. Don't know if it is technically needed, but seems to prevent some weirdness. // Clone power to new object. Don't know if it is technically needed, but seems to prevent some weirdness.
const newPower = JSON.parse(JSON.stringify(itemData)); const newPower = JSON.parse(JSON.stringify(itemData));
@ -313,17 +321,15 @@ function _updateNPCData(actor) {
// set flag to check to see if migration has been done so we don't do it again. // set flag to check to see if migration has been done so we don't do it again.
liveActor.setFlag("sw5e", "dataVersion", "1.2.4"); liveActor.setFlag("sw5e", "dataVersion", "1.2.4");
}) });
} }
//merge object //merge object
actorData = mergeObject(actorData, updateData); actorData = mergeObject(actorData, updateData);
// Return the scrubbed data // Return the scrubbed data
return actor; return actor;
} }
/** /**
* Migrate the actor speed string to movement object * Migrate the actor speed string to movement object
* @private * @private
@ -332,13 +338,12 @@ function _migrateActorMovement(actorData, updateData) {
const ad = actorData.data; const ad = actorData.data;
// Work is needed if old data is present // Work is needed if old data is present
const old = actorData.type === 'vehicle' ? ad?.attributes?.speed : ad?.attributes?.speed?.value; const old = actorData.type === "vehicle" ? ad?.attributes?.speed : ad?.attributes?.speed?.value;
const hasOld = old !== undefined; const hasOld = old !== undefined;
if (hasOld) { if (hasOld) {
// If new data is not present, migrate the old data // If new data is not present, migrate the old data
const hasNew = ad?.attributes?.movement?.walk !== undefined; const hasNew = ad?.attributes?.movement?.walk !== undefined;
if ( !hasNew && (typeof old === "string") ) { if (!hasNew && typeof old === "string") {
const s = (old || "").split(" "); const s = (old || "").split(" ");
if (s.length > 0) updateData["data.attributes.movement.walk"] = Number.isNumeric(s[0]) ? parseInt(s[0]) : null; if (s.length > 0) updateData["data.attributes.movement.walk"] = Number.isNumeric(s[0]) ? parseInt(s[0]) : null;
} }
@ -346,7 +351,7 @@ function _migrateActorMovement(actorData, updateData) {
// Remove the old attribute // Remove the old attribute
updateData["data.attributes.-=speed"] = null; updateData["data.attributes.-=speed"] = null;
} }
return updateData return updateData;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -407,7 +412,7 @@ function _migrateActorPowers(actorData, updateData) {
// Remove the Power DC Bonus // Remove the Power DC Bonus
updateData["data.bonuses.power.-=dc"] = null; updateData["data.bonuses.power.-=dc"] = null;
return updateData return updateData;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -494,7 +499,11 @@ async function _migrateItemPower(item, actor, updateData) {
// shortcut out if dataVersion flag is set to 1.2.4 or higher // shortcut out if dataVersion flag is set to 1.2.4 or higher
const hasDataVersion = item?.flags?.sw5e?.dataVersion !== undefined; const hasDataVersion = item?.flags?.sw5e?.dataVersion !== undefined;
if (hasDataVersion && (item.flags.sw5e.dataVersion === "1.2.4" || isNewerVersion("1.2.4", item.flags.sw5e.dataVersion))) return updateData; if (
hasDataVersion &&
(item.flags.sw5e.dataVersion === "1.2.4" || isNewerVersion("1.2.4", item.flags.sw5e.dataVersion))
)
return updateData;
// Check to see what the source of Power is // Check to see what the source of Power is
const sourceId = item.flags.core.sourceId; const sourceId = item.flags.core.sourceId;
@ -512,11 +521,10 @@ async function _migrateItemPower(item, actor, updateData) {
const corePowerData = corePower.data; const corePowerData = corePower.data;
// copy Core Power Data over original Power // copy Core Power Data over original Power
updateData["data"] = corePowerData; updateData["data"] = corePowerData;
updateData["flags"] = {"sw5e": {"dataVersion": "1.2.4"}}; updateData["flags"] = {sw5e: {dataVersion: "1.2.4"}};
return updateData; return updateData;
//game.packs.get(powerType).getEntity(core_id).then(corePower => { //game.packs.get(powerType).getEntity(core_id).then(corePower => {
//}) //})
@ -543,7 +551,7 @@ function _migrateItemAttunement(item, updateData) {
* @private * @private
*/ */
export async function purgeFlags(pack) { export async function purgeFlags(pack) {
const cleanFlags = (flags) => { const cleanFlags = flags => {
const flags5e = flags.sw5e || null; const flags5e = flags.sw5e || null;
return flags5e ? {sw5e: flags5e} : {}; return flags5e ? {sw5e: flags5e} : {};
}; };
@ -555,7 +563,7 @@ export async function purgeFlags(pack) {
update.items = entity.data.items.map(i => { update.items = entity.data.items.map(i => {
i.flags = cleanFlags(i.flags); i.flags = cleanFlags(i.flags);
return i; return i;
}) });
} }
await pack.updateEntity(update, {recursive: false}); await pack.updateEntity(update, {recursive: false});
console.log(`Purged flags from ${entity.name}`); console.log(`Purged flags from ${entity.name}`);
@ -565,7 +573,6 @@ export async function purgeFlags(pack) {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**
* Purge the data model of any inner objects which have been flagged as _deprecated. * Purge the data model of any inner objects which have been flagged as _deprecated.
* @param {object} data The data to clean * @param {object} data The data to clean
@ -577,8 +584,7 @@ export function removeDeprecatedObjects(data) {
if (v._deprecated === true) { if (v._deprecated === true) {
console.log(`Deleting deprecated object key ${k}`); console.log(`Deleting deprecated object key ${k}`);
delete data[k]; delete data[k];
} } else removeDeprecatedObjects(v);
else removeDeprecatedObjects(v);
} }
} }
return data; return data;

View file

@ -5,7 +5,6 @@ import { SW5E } from "../config.js";
* @extends {MeasuredTemplate} * @extends {MeasuredTemplate}
*/ */
export default class AbilityTemplate extends MeasuredTemplate { export default class AbilityTemplate extends MeasuredTemplate {
/** /**
* A factory method to create an AbilityTemplate instance using provided data from an Item5e instance * A factory method to create an AbilityTemplate instance using provided data from an Item5e instance
* @param {Item5e} item The Item object for which to construct the template * @param {Item5e} item The Item object for which to construct the template
@ -124,7 +123,7 @@ export default class AbilityTemplate extends MeasuredTemplate {
event.stopPropagation(); event.stopPropagation();
let delta = canvas.grid.type > CONST.GRID_TYPES.SQUARE ? 30 : 15; let delta = canvas.grid.type > CONST.GRID_TYPES.SQUARE ? 30 : 15;
let snap = event.shiftKey ? delta : 5; let snap = event.shiftKey ? delta : 5;
this.data.direction += (snap * Math.sign(event.deltaY)); this.data.direction += snap * Math.sign(event.deltaY);
this.refresh(); this.refresh();
}; };

View file

@ -1,5 +1,4 @@
export const registerSystemSettings = function () { export const registerSystemSettings = function () {
/** /**
* Track the system version upon which point a migration was last applied * Track the system version upon which point a migration was last applied
*/ */
@ -22,9 +21,9 @@ export const registerSystemSettings = function() {
default: "normal", default: "normal",
type: String, type: String,
choices: { choices: {
"normal": "SETTINGS.5eRestPHB", normal: "SETTINGS.5eRestPHB",
"gritty": "SETTINGS.5eRestGritty", gritty: "SETTINGS.5eRestGritty",
"epic": "SETTINGS.5eRestEpic", epic: "SETTINGS.5eRestEpic"
} }
}); });
@ -39,11 +38,11 @@ export const registerSystemSettings = function() {
default: "555", default: "555",
type: String, type: String,
choices: { choices: {
"555": "SETTINGS.5eDiagPHB", 555: "SETTINGS.5eDiagPHB",
"5105": "SETTINGS.5eDiagDMG", 5105: "SETTINGS.5eDiagDMG",
"EUCL": "SETTINGS.5eDiagEuclidean", EUCL: "SETTINGS.5eDiagEuclidean"
}, },
onChange: rule => canvas.grid.diagonalRule = rule onChange: rule => (canvas.grid.diagonalRule = rule)
}); });
/** /**
@ -79,7 +78,7 @@ export const registerSystemSettings = function() {
scope: "world", scope: "world",
config: true, config: true,
default: false, default: false,
type: Boolean, type: Boolean
}); });
/** /**
@ -100,10 +99,10 @@ export const registerSystemSettings = function() {
/** /**
* Option to allow GMs to restrict polymorphing to GMs only. * Option to allow GMs to restrict polymorphing to GMs only.
*/ */
game.settings.register('sw5e', 'allowPolymorphing', { game.settings.register("sw5e", "allowPolymorphing", {
name: 'SETTINGS.5eAllowPolymorphingN', name: "SETTINGS.5eAllowPolymorphingN",
hint: 'SETTINGS.5eAllowPolymorphingL', hint: "SETTINGS.5eAllowPolymorphingL",
scope: 'world', scope: "world",
config: true, config: true,
default: false, default: false,
type: Boolean type: Boolean
@ -112,8 +111,8 @@ export const registerSystemSettings = function() {
/** /**
* Remember last-used polymorph settings. * Remember last-used polymorph settings.
*/ */
game.settings.register('sw5e', 'polymorphSettings', { game.settings.register("sw5e", "polymorphSettings", {
scope: 'client', scope: "client",
default: { default: {
keepPhysical: false, keepPhysical: false,
keepMental: false, keepMental: false,
@ -138,8 +137,8 @@ export const registerSystemSettings = function() {
default: "light", default: "light",
type: String, type: String,
choices: { choices: {
"light": "SETTINGS.SWColorLight", light: "SETTINGS.SWColorLight",
"dark": "SETTINGS.SWColorDark" dark: "SETTINGS.SWColorDark"
} }
}); });
}; };

View file

@ -5,7 +5,6 @@
*/ */
export const preloadHandlebarsTemplates = async function () { export const preloadHandlebarsTemplates = async function () {
return loadTemplates([ return loadTemplates([
// Shared Partials // Shared Partials
"systems/sw5e/templates/actors/parts/active-effects.html", "systems/sw5e/templates/actors/parts/active-effects.html",

115
sw5e.js
View file

@ -69,7 +69,7 @@ Hooks.once("init", function() {
dice: dice, dice: dice,
entities: { entities: {
Actor5e, Actor5e,
Item5e, Item5e
}, },
macros: macros, macros: macros,
migrations: migrations, migrations: migrations,
@ -81,11 +81,7 @@ Hooks.once("init", function() {
CONFIG.Actor.entityClass = Actor5e; CONFIG.Actor.entityClass = Actor5e;
CONFIG.Item.entityClass = Item5e; CONFIG.Item.entityClass = Item5e;
CONFIG.time.roundTime = 6; CONFIG.time.roundTime = 6;
CONFIG.fontFamilies = [ CONFIG.fontFamilies = ["Engli-Besh", "Open Sans", "Russo One"];
"Engli-Besh",
"Open Sans",
"Russo One"
];
// 5e cone RAW should be 53.13 degrees // 5e cone RAW should be 53.13 degrees
CONFIG.MeasuredTemplate.defaults.angle = 53.13; CONFIG.MeasuredTemplate.defaults.angle = 53.13;
@ -123,14 +119,31 @@ Hooks.once("init", function() {
makeDefault: false, makeDefault: false,
label: "SW5E.SheetClassNPCOld" label: "SW5E.SheetClassNPCOld"
}); });
Actors.registerSheet('sw5e', ActorSheet5eVehicle, { Actors.registerSheet("sw5e", ActorSheet5eVehicle, {
types: ['vehicle'], types: ["vehicle"],
makeDefault: true, makeDefault: true,
label: "SW5E.SheetClassVehicle" label: "SW5E.SheetClassVehicle"
}); });
Items.unregisterSheet("core", ItemSheet); Items.unregisterSheet("core", ItemSheet);
Items.registerSheet("sw5e", ItemSheet5e, { Items.registerSheet("sw5e", ItemSheet5e, {
types: ['weapon', 'equipment', 'consumable', 'tool', 'loot', 'class', 'power', 'feat', 'species', 'backpack', 'archetype', 'classfeature', 'background', 'fightingmastery', 'fightingstyle', 'lightsaberform'], types: [
"weapon",
"equipment",
"consumable",
"tool",
"loot",
"class",
"power",
"feat",
"species",
"backpack",
"archetype",
"classfeature",
"background",
"fightingmastery",
"fightingstyle",
"lightsaberform"
],
makeDefault: true, makeDefault: true,
label: "SW5E.SheetClassItem" label: "SW5E.SheetClassItem"
}); });
@ -139,7 +152,6 @@ Hooks.once("init", function() {
preloadHandlebarsTemplates(); preloadHandlebarsTemplates();
}); });
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Foundry VTT Setup */ /* Foundry VTT Setup */
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -148,21 +160,61 @@ Hooks.once("init", function() {
* This function runs after game data has been requested and loaded from the servers, so entities exist * This function runs after game data has been requested and loaded from the servers, so entities exist
*/ */
Hooks.once("setup", function () { Hooks.once("setup", function () {
// Localize CONFIG objects once up-front // Localize CONFIG objects once up-front
const toLocalize = [ const toLocalize = [
"abilities", "abilityAbbreviations", "abilityActivationTypes", "abilityConsumptionTypes", "actorSizes", "alignments", "abilities",
"armorProficiencies", "armorPropertiesTypes", "conditionTypes", "consumableTypes", "cover", "currencies", "damageResistanceTypes", "abilityAbbreviations",
"damageTypes", "distanceUnits", "equipmentTypes", "healingTypes", "itemActionTypes", "languages", "abilityActivationTypes",
"limitedUsePeriods", "movementTypes", "movementUnits", "polymorphSettings", "proficiencyLevels", "senses", "skills", "abilityConsumptionTypes",
"powerComponents", "powerLevels", "powerPreparationModes", "powerScalingModes", "powerSchools", "targetTypes", "actorSizes",
"timePeriods", "toolProficiencies", "weaponProficiencies", "weaponProperties", "weaponTypes" "alignments",
"armorProficiencies",
"armorPropertiesTypes",
"conditionTypes",
"consumableTypes",
"cover",
"currencies",
"damageResistanceTypes",
"damageTypes",
"distanceUnits",
"equipmentTypes",
"healingTypes",
"itemActionTypes",
"languages",
"limitedUsePeriods",
"movementTypes",
"movementUnits",
"polymorphSettings",
"proficiencyLevels",
"senses",
"skills",
"powerComponents",
"powerLevels",
"powerPreparationModes",
"powerScalingModes",
"powerSchools",
"targetTypes",
"timePeriods",
"toolProficiencies",
"weaponProficiencies",
"weaponProperties",
"weaponTypes"
]; ];
// Exclude some from sorting where the default order matters // Exclude some from sorting where the default order matters
const noSort = [ const noSort = [
"abilities", "alignments", "currencies", "distanceUnits", "movementUnits", "itemActionTypes", "proficiencyLevels", "abilities",
"limitedUsePeriods", "powerComponents", "powerLevels", "powerPreparationModes", "weaponTypes" "alignments",
"currencies",
"distanceUnits",
"movementUnits",
"itemActionTypes",
"proficiencyLevels",
"limitedUsePeriods",
"powerComponents",
"powerLevels",
"powerPreparationModes",
"weaponTypes"
]; ];
// Localize and sort CONFIG objects // Localize and sort CONFIG objects
@ -179,7 +231,7 @@ Hooks.once("setup", function() {
// add DND5E translation for module compatability // add DND5E translation for module compatability
game.i18n.translations.DND5E = game.i18n.translations.SW5E; game.i18n.translations.DND5E = game.i18n.translations.SW5E;
// console.log(game.settings.get("sw5e", "colorTheme")); // console.log(game.settings.get("sw5e", "colorTheme"));
let theme = game.settings.get("sw5e", "colorTheme") + '-theme'; let theme = game.settings.get("sw5e", "colorTheme") + "-theme";
document.body.classList.add(theme); document.body.classList.add(theme);
}); });
@ -189,7 +241,6 @@ Hooks.once("setup", function() {
* Once the entire VTT framework is initialized, check to see if we should perform a data migration * Once the entire VTT framework is initialized, check to see if we should perform a data migration
*/ */
Hooks.once("ready", function () { Hooks.once("ready", function () {
// Wait to register hotbar drop hook on ready so that modules could register earlier if they want to // Wait to register hotbar drop hook on ready so that modules could register earlier if they want to
Hooks.on("hotbarDrop", (bar, data, slot) => macros.create5eMacro(data, slot)); Hooks.on("hotbarDrop", (bar, data, slot) => macros.create5eMacro(data, slot));
@ -199,8 +250,11 @@ Hooks.once("ready", function() {
const NEEDS_MIGRATION_VERSION = "1.2.4.R1-A5"; const NEEDS_MIGRATION_VERSION = "1.2.4.R1-A5";
// Check for R1 SW5E versions // Check for R1 SW5E versions
const SW5E_NEEDS_MIGRATION_VERSION = "R1-A5"; const SW5E_NEEDS_MIGRATION_VERSION = "R1-A5";
const COMPATIBLE_MIGRATION_VERSION = 0.80; const COMPATIBLE_MIGRATION_VERSION = 0.8;
const needsMigration = currentVersion && (isNewerVersion(SW5E_NEEDS_MIGRATION_VERSION, currentVersion) || isNewerVersion(NEEDS_MIGRATION_VERSION, currentVersion)); const needsMigration =
currentVersion &&
(isNewerVersion(SW5E_NEEDS_MIGRATION_VERSION, currentVersion) ||
isNewerVersion(NEEDS_MIGRATION_VERSION, currentVersion));
if (!needsMigration) return; if (!needsMigration) return;
// Perform the migration // Perform the migration
@ -216,7 +270,6 @@ Hooks.once("ready", function() {
/* -------------------------------------------- */ /* -------------------------------------------- */
Hooks.on("canvasInit", function () { Hooks.on("canvasInit", function () {
// Extend Diagonal Measurement // Extend Diagonal Measurement
canvas.grid.diagonalRule = game.settings.get("sw5e", "diagonalMovement"); canvas.grid.diagonalRule = game.settings.get("sw5e", "diagonalMovement");
SquareGrid.prototype.measureDistances = measureDistances; SquareGrid.prototype.measureDistances = measureDistances;
@ -225,13 +278,11 @@ Hooks.on("canvasInit", function() {
Token.prototype.getBarAttribute = getBarAttribute; Token.prototype.getBarAttribute = getBarAttribute;
}); });
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Other Hooks */ /* Other Hooks */
/* -------------------------------------------- */ /* -------------------------------------------- */
Hooks.on("renderChatMessage", (app, html, data) => { Hooks.on("renderChatMessage", (app, html, data) => {
// Display action buttons // Display action buttons
chat.displayChatActionButtons(app, html, data); chat.displayChatActionButtons(app, html, data);
@ -244,7 +295,7 @@ Hooks.on("renderChatMessage", (app, html, data) => {
Hooks.on("getChatLogEntryContext", chat.addChatMessageContextOptions); Hooks.on("getChatLogEntryContext", chat.addChatMessageContextOptions);
Hooks.on("renderChatLog", (app, html, data) => Item5e.chatListeners(html)); Hooks.on("renderChatLog", (app, html, data) => Item5e.chatListeners(html));
Hooks.on("renderChatPopout", (app, html, data) => Item5e.chatListeners(html)); Hooks.on("renderChatPopout", (app, html, data) => Item5e.chatListeners(html));
Hooks.on('getActorDirectoryEntryContext', Actor5e.addDirectoryContextOptions); Hooks.on("getActorDirectoryEntryContext", Actor5e.addDirectoryContextOptions);
Hooks.on("renderSceneDirectory", (app, html, data) => { Hooks.on("renderSceneDirectory", (app, html, data) => {
//console.log(html.find("header.folder-header")); //console.log(html.find("header.folder-header"));
setFolderBackground(html); setFolderBackground(html);
@ -266,16 +317,14 @@ Hooks.on("ActorSheet5eCharacterNew", (app, html, data) => {
console.log("renderSwaltSheet"); console.log("renderSwaltSheet");
}); });
// TODO I should remove this // TODO I should remove this
Handlebars.registerHelper('getProperty', function (data, property) { Handlebars.registerHelper("getProperty", function (data, property) {
return getProperty(data, property); return getProperty(data, property);
}); });
function setFolderBackground(html) { function setFolderBackground(html) {
html.find("header.folder-header").each(function () { html.find("header.folder-header").each(function () {
let bgColor = $(this).css("background-color"); let bgColor = $(this).css("background-color");
if(bgColor == undefined) if (bgColor == undefined) bgColor = "rgb(255,255,255)";
bgColor = "rgb(255,255,255)"; $(this).closest("li").css("background-color", bgColor);
$(this).closest('li').css("background-color", bgColor); });
})
} }