Merge pull request #239 from unrealkakeman89/Develop

Develop
This commit is contained in:
CK 2021-07-07 09:24:23 -04:00 committed by GitHub
commit 2212e3c7d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 13465 additions and 12707 deletions

14
.prettierrc Normal file
View file

@ -0,0 +1,14 @@
{
"printWidth": 120,
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": false,
"quoteProps": "consistent",
"jsxSingleQuote": false,
"trailingComma": "none",
"bracketSpacing": false,
"jsxBracketSameLine": false,
"arrowParens": "always",
"endOfLine": "lf"
}

View file

@ -1,2 +1,3 @@
{ {
"editor.formatOnSave": true
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@ import ActorHitDiceConfig from "../../../apps/hit-dice-config.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 ActorTypeConfig from "../../../apps/actor-type.js"; import ActorTypeConfig from "../../../apps/actor-type.js";
import {SW5E} from '../../../config.js'; import {SW5E} from "../../../config.js";
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js"; import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js";
/** /**
@ -58,7 +58,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`;
} }
@ -66,7 +67,6 @@ export default class ActorSheet5e extends ActorSheet {
/** @override */ /** @override */
getData(options) { getData(options) {
// Basic data // Basic data
let isOwner = this.actor.isOwner; let isOwner = this.actor.isOwner;
const data = { const data = {
@ -78,7 +78,7 @@ export default class ActorSheet5e extends ActorSheet {
isCharacter: this.actor.type === "character", isCharacter: this.actor.type === "character",
isNPC: this.actor.type === "npc", isNPC: this.actor.type === "npc",
isStarship: this.actor.type === "starship", isStarship: this.actor.type === "starship",
isVehicle: this.actor.type === 'vehicle', isVehicle: this.actor.type === "vehicle",
config: CONFIG.SW5E, config: CONFIG.SW5E,
rollData: this.actor.getRollData.bind(this.actor) rollData: this.actor.getRollData.bind(this.actor)
}; };
@ -90,7 +90,7 @@ export default class ActorSheet5e extends ActorSheet {
// Owned Items // Owned Items
data.items = actorData.items; data.items = actorData.items;
for ( let i of data.items ) { for (let i of data.items) {
const item = this.actor.items.get(i._id); const item = this.actor.items.get(i._id);
i.labels = item.labels; i.labels = item.labels;
} }
@ -101,7 +101,7 @@ export default class ActorSheet5e extends ActorSheet {
data.filters = this._filters; data.filters = this._filters;
// Ability Scores // Ability Scores
for ( let [a, abl] of Object.entries(actorData.data.abilities)) { for (let [a, abl] of Object.entries(actorData.data.abilities)) {
abl.icon = this._getProficiencyIcon(abl.proficient); abl.icon = this._getProficiencyIcon(abl.proficient);
abl.hover = CONFIG.SW5E.proficiencyLevels[abl.proficient]; abl.hover = CONFIG.SW5E.proficiencyLevels[abl.proficient];
abl.label = CONFIG.SW5E.abilities[a]; abl.label = CONFIG.SW5E.abilities[a];
@ -115,7 +115,7 @@ export default class ActorSheet5e extends ActorSheet {
skl.hover = CONFIG.SW5E.proficiencyLevels[skl.value]; skl.hover = CONFIG.SW5E.proficiencyLevels[skl.value];
if (data.actor.type === "starship") { if (data.actor.type === "starship") {
skl.label = CONFIG.SW5E.starshipSkills[s]; skl.label = CONFIG.SW5E.starshipSkills[s];
}else{ } else {
skl.label = CONFIG.SW5E.skills[s]; skl.label = CONFIG.SW5E.skills[s];
} }
} }
@ -137,7 +137,7 @@ export default class ActorSheet5e extends ActorSheet {
data.effects = prepareActiveEffectCategories(this.actor.effects); data.effects = prepareActiveEffectCategories(this.actor.effects);
// Return data to the sheet // Return data to the sheet
return data return data;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -149,38 +149,42 @@ export default class ActorSheet5e extends ActorSheet {
* @returns {{primary: string, special: string}} * @returns {{primary: string, special: string}}
* @private * @private
*/ */
_getMovementSpeed(actorData, largestPrimary=false) { _getMovementSpeed(actorData, largestPrimary = false) {
const movement = actorData.data.attributes.movement || {}; const movement = actorData.data.attributes.movement || {};
// Prepare an array of available movement speeds // Prepare an array of available movement speeds
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}`]);
} }
// Filter and sort speeds on their values // Filter and sort speeds on their values
speeds = speeds.filter(s => !!s[0]).sort((a, b) => b[0] - a[0]); speeds = speeds.filter((s) => !!s[0]).sort((a, b) => b[0] - a[0]);
// Case 1: Largest as primary // Case 1: Largest as primary
if ( largestPrimary ) { if (largestPrimary) {
let primary = speeds.shift(); let primary = speeds.shift();
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
else { else {
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(", ") : ""
} };
} }
} }
@ -189,12 +193,12 @@ export default class ActorSheet5e extends ActorSheet {
_getSenses(actorData) { _getSenses(actorData) {
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}`;
} }
if ( !!senses.special ) tags["special"] = senses.special; if (!!senses.special) tags["special"] = senses.special;
return tags; return tags;
} }
@ -207,20 +211,20 @@ 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];
if ( !trait ) continue; if (!trait) continue;
let values = []; let values = [];
if ( trait.value ) { if (trait.value) {
values = trait.value instanceof Array ? trait.value : [trait.value]; values = trait.value instanceof Array ? trait.value : [trait.value];
} }
trait.selected = values.reduce((obj, t) => { trait.selected = values.reduce((obj, t) => {
@ -229,8 +233,8 @@ 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";
} }
@ -252,8 +256,8 @@ 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
@ -264,32 +268,37 @@ export default class ActorSheet5e extends ActorSheet {
}; };
// Format a powerbook entry for a certain indexed level // Format a powerbook entry for a certain indexed level
const registerSection = (sl, i, label, {prepMode="prepared", value, max, override}={}) => { const registerSection = (sl, i, label, {prepMode = "prepared", value, max, override} = {}) => {
powerbook[i] = { powerbook[i] = {
order: i, order: i,
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
}; };
}; };
// Determine the maximum power level which has a slot // Determine the maximum power level which has a slot
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);
// Level-based powercasters have cantrips and leveled slots // Level-based powercasters have cantrips and leveled slots
if ( maxLevel > 0 ) { if (maxLevel > 0) {
registerSection("power0", 0, CONFIG.SW5E.powerLevels[0]); registerSection("power0", 0, CONFIG.SW5E.powerLevels[0]);
for (let lvl = 1; lvl <= maxLevel; lvl++) { for (let lvl = 1; lvl <= maxLevel; lvl++) {
const sl = `power${lvl}`; const sl = `power${lvl}`;
@ -298,15 +307,15 @@ export default class ActorSheet5e extends ActorSheet {
} }
// Iterate over every power item, adding powers to the powerbook by section // Iterate over every power item, adding powers to the powerbook by section
powers.forEach(power => { powers.forEach((power) => {
const mode = power.data.preparation.mode || "prepared"; const mode = power.data.preparation.mode || "prepared";
let s = power.data.level || 0; let s = power.data.level || 0;
const sl = `power${s}`; const sl = `power${s}`;
// Specialized powercasting modes (if they exist) // Specialized powercasting modes (if they exist)
if ( mode in sections ) { if (mode in sections) {
s = sections[mode]; s = sections[mode];
if ( !powerbook[s] ){ if (!powerbook[s]) {
const l = levels[mode] || {}; const l = levels[mode] || {};
const config = CONFIG.SW5E.powerPreparationModes[mode]; const config = CONFIG.SW5E.powerPreparationModes[mode];
registerSection(mode, s, config, { registerSection(mode, s, config, {
@ -319,7 +328,7 @@ export default class ActorSheet5e extends ActorSheet {
} }
// Sections for higher-level powers which the caster "should not" have, but power items exist for // Sections for higher-level powers which the caster "should not" have, but power items exist for
else if ( !powerbook[s] ) { else if (!powerbook[s]) {
registerSection(sl, s, CONFIG.SW5E.powerLevels[s], {levels: levels[sl]}); registerSection(sl, s, CONFIG.SW5E.powerLevels[s], {levels: levels[sl]});
} }
@ -341,33 +350,33 @@ export default class ActorSheet5e extends ActorSheet {
* @private * @private
*/ */
_filterItems(items, filters) { _filterItems(items, filters) {
return items.filter(item => { return items.filter((item) => {
const data = item.data; const data = item.data;
// 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;
} }
} }
// Power-specific filters // Power-specific filters
if ( filters.has("ritual") ) { if (filters.has("ritual")) {
if (data.components.ritual !== true) return false; if (data.components.ritual !== true) return false;
} }
if ( filters.has("concentration") ) { if (filters.has("concentration")) {
if (data.components.concentration !== true) return false; if (data.components.concentration !== true) return false;
} }
if ( filters.has("prepared") ) { if (filters.has("prepared")) {
if ( data.level === 0 || ["innate", "always"].includes(data.preparation.mode) ) return true; if (data.level === 0 || ["innate", "always"].includes(data.preparation.mode)) return true;
if ( this.actor.data.type === "npc" ) return true; if (this.actor.data.type === "npc") return true;
if ( this.actor.data.type === "starship" ) return true; if (this.actor.data.type === "starship") return true;
return data.preparation.prepared; return data.preparation.prepared;
} }
// Equipment-specific filters // Equipment-specific filters
if ( filters.has("equipped") ) { if (filters.has("equipped")) {
if ( data.equipped !== true ) return false; if (data.equipped !== true) return false;
} }
return true; return true;
}); });
@ -395,64 +404,62 @@ export default class ActorSheet5e extends ActorSheet {
/** @inheritdoc */ /** @inheritdoc */
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));
// View Item Sheets // View Item Sheets
html.find('.item-edit').click(this._onItemEdit.bind(this)); html.find(".item-edit").click(this._onItemEdit.bind(this));
// 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-delete').click(this._onItemDelete.bind(this)); html.find(".item-delete").click(this._onItemDelete.bind(this));
html.find('.item-collapse').click(this._onItemCollapse.bind(this)); html.find(".item-collapse").click(this._onItemCollapse.bind(this));
html.find('.item-uses input').click(ev => ev.target.select()).change(this._onUsesChange.bind(this)); html.find(".item-uses input")
html.find('.slot-max-override').click(this._onPowerSlotOverride.bind(this)); .click((ev) => ev.target.select())
html.find('.increment-class-level').click(this._onIncrementClassLevel.bind(this)); .change(this._onUsesChange.bind(this));
html.find('.decrement-class-level').click(this._onDecrementClassLevel.bind(this)); html.find(".slot-max-override").click(this._onPowerSlotOverride.bind(this));
html.find(".increment-class-level").click(this._onIncrementClassLevel.bind(this));
html.find(".decrement-class-level").click(this._onDecrementClassLevel.bind(this));
// Active Effect management // Active Effect management
html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.actor)); html.find(".effect-control").click((ev) => onManageActiveEffect(ev, this.actor));
} }
// Owner Only Listeners // Owner Only Listeners
if ( this.actor.isOwner ) { if (this.actor.isOwner) {
// 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
@ -473,8 +480,8 @@ export default class ActorSheet5e extends ActorSheet {
_initializeFilterItemList(i, ul) { _initializeFilterItemList(i, ul) {
const set = this._filters[ul.dataset.filter]; const set = this._filters[ul.dataset.filter];
const filters = ul.querySelectorAll(".filter-item"); const filters = ul.querySelectorAll(".filter-item");
for ( let li of filters ) { for (let li of filters) {
if ( set.has(li.dataset.filter) ) li.classList.add("active"); if (set.has(li.dataset.filter)) li.classList.add("active");
} }
} }
@ -490,10 +497,10 @@ export default class ActorSheet5e extends ActorSheet {
_onChangeInputDelta(event) { _onChangeInputDelta(event) {
const input = event.target; const input = event.target;
const value = input.value; const value = input.value;
if ( ["+", "-"].includes(value[0]) ) { if (["+", "-"].includes(value[0])) {
let delta = parseFloat(value); let delta = parseFloat(value);
input.value = getProperty(this.actor.data, input.name) + delta; input.value = getProperty(this.actor.data, input.name) + delta;
} else if ( value[0] === "=" ) { } else if (value[0] === "=") {
input.value = value.slice(1); input.value = value.slice(1);
} }
} }
@ -509,7 +516,7 @@ export default class ActorSheet5e extends ActorSheet {
event.preventDefault(); event.preventDefault();
const button = event.currentTarget; const button = event.currentTarget;
let app; let app;
switch ( button.dataset.action ) { switch (button.dataset.action) {
case "hit-dice": case "hit-dice":
app = new ActorHitDiceConfig(this.object); app = new ActorHitDiceConfig(this.object);
break; break;
@ -546,10 +553,10 @@ export default class ActorSheet5e extends ActorSheet {
let idx = levels.indexOf(level); let idx = levels.indexOf(level);
// 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
@ -560,49 +567,51 @@ export default class ActorSheet5e extends ActorSheet {
/** @override */ /** @override */
async _onDropActor(event, data) { async _onDropActor(event, data) {
const canPolymorph = game.user.isGM || (this.actor.isOwner && game.settings.get('sw5e', 'allowPolymorphing')); const canPolymorph = game.user.isGM || (this.actor.isOwner && game.settings.get("sw5e", "allowPolymorphing"));
if ( !canPolymorph ) return false; if (!canPolymorph) return false;
// Get the target actor // Get the target actor
let sourceActor = null; let sourceActor = null;
if (data.pack) { if (data.pack) {
const pack = game.packs.find(p => p.collection === data.pack); const pack = game.packs.find((p) => p.collection === data.pack);
sourceActor = await pack.getEntity(data.id); sourceActor = await pack.getEntity(data.id);
} else { } else {
sourceActor = game.actors.get(data.id); sourceActor = game.actors.get(data.id);
} }
if ( !sourceActor ) return; if (!sourceActor) return;
// 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,
@ -613,60 +622,64 @@ 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) {
// Check to make sure items of this type are allowed on this actor // Check to make sure items of this type are allowed on this actor
if ( this.constructor.unsupportedItemTypes.has(itemData.type) ) { if (this.constructor.unsupportedItemTypes.has(itemData.type)) {
return ui.notifications.warn(game.i18n.format("SW5E.ActorWarningInvalidItem", { return ui.notifications.warn(
game.i18n.format("SW5E.ActorWarningInvalidItem", {
itemType: game.i18n.localize(CONFIG.Item.typeLabels[itemData.type]), itemType: game.i18n.localize(CONFIG.Item.typeLabels[itemData.type]),
actorType: game.i18n.localize(CONFIG.Actor.typeLabels[this.actor.type]) actorType: game.i18n.localize(CONFIG.Actor.typeLabels[this.actor.type])
})); })
);
} }
// 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;
} }
if ( itemData.data ) { if (itemData.data) {
// Ignore certain statuses // Ignore certain statuses
["equipped", "proficient", "prepared"].forEach(k => delete itemData.data[k]); ["equipped", "proficient", "prepared"].forEach((k) => delete itemData.data[k]);
// Downgrade ATTUNED to REQUIRED // Downgrade ATTUNED to REQUIRED
itemData.data.attunement = Math.min(itemData.data.attunement, CONFIG.SW5E.attunementTypes.REQUIRED); itemData.data.attunement = Math.min(itemData.data.attunement, CONFIG.SW5E.attunementTypes.REQUIRED);
} }
// Stack identical consumables // Stack identical consumables
if ( itemData.type === "consumable" && itemData.flags.core?.sourceId ) { if (itemData.type === "consumable" && itemData.flags.core?.sourceId) {
const similarItem = this.actor.items.find(i => { const similarItem = this.actor.items.find((i) => {
const sourceId = i.getFlag("core", "sourceId"); const sourceId = i.getFlag("core", "sourceId");
return sourceId && (sourceId === itemData.flags.core?.sourceId) && return sourceId && sourceId === itemData.flags.core?.sourceId && i.type === "consumable";
(i.type === "consumable");
}); });
if ( similarItem && itemData.name !== "Power Cell" ) { // Always create a new powercell instead of increasing quantity if (similarItem && itemData.name !== "Power Cell") {
// Always create a new powercell instead of increasing quantity
return similarItem.update({ return similarItem.update({
'data.quantity': similarItem.data.data.quantity + Math.max(itemData.data.quantity, 1) "data.quantity": similarItem.data.data.quantity + Math.max(itemData.data.quantity, 1)
}); });
} }
} }
@ -682,7 +695,7 @@ export default class ActorSheet5e extends ActorSheet {
* @param {MouseEvent} event The originating click event * @param {MouseEvent} event The originating click event
* @private * @private
*/ */
async _onPowerSlotOverride (event) { async _onPowerSlotOverride(event) {
const span = event.currentTarget.parentElement; const span = event.currentTarget.parentElement;
const level = span.dataset.level; const level = span.dataset.level;
const override = this.actor.data.data.powers[level].override || span.dataset.slots; const override = this.actor.data.data.powers[level].override || span.dataset.slots;
@ -712,7 +725,7 @@ export default class ActorSheet5e extends ActorSheet {
const item = this.actor.items.get(itemId); const item = this.actor.items.get(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});
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -740,7 +753,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.items.get(itemId); const item = this.actor.items.get(itemId);
return item.rollRecharge(); return item.rollRecharge();
}; }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -755,13 +768,13 @@ export default class ActorSheet5e extends ActorSheet {
chatData = item.getChatData({secrets: this.actor.isOwner}); chatData = item.getChatData({secrets: this.actor.isOwner});
// Toggle summary // Toggle summary
if ( li.hasClass("expanded") ) { if (li.hasClass("expanded")) {
let summary = li.children(".item-summary"); let summary = li.children(".item-summary");
summary.slideUp(200, () => summary.remove()); summary.slideUp(200, () => summary.remove());
} else { } else {
let div = $(`<div class="item-summary">${chatData.description.value}</div>`); let div = $(`<div class="item-summary">${chatData.description.value}</div>`);
let props = $(`<div class="item-properties"></div>`); let props = $(`<div class="item-properties"></div>`);
chatData.properties.forEach(p => props.append(`<span class="tag">${p}</span>`)); chatData.properties.forEach((p) => props.append(`<span class="tag">${p}</span>`));
div.append(props); div.append(props);
li.append(div.hide()); li.append(div.hide());
div.slideDown(200); div.slideDown(200);
@ -814,7 +827,7 @@ export default class ActorSheet5e extends ActorSheet {
event.preventDefault(); event.preventDefault();
const li = event.currentTarget.closest(".item"); const li = event.currentTarget.closest(".item");
const item = this.actor.items.get(li.dataset.itemId); const item = this.actor.items.get(li.dataset.itemId);
if ( item ) return item.delete(); if (item) return item.delete();
} }
/** /**
@ -823,7 +836,7 @@ export default class ActorSheet5e extends ActorSheet {
* @private * @private
*/ */
_onItemCollapse(event) { _onItemCollapse(event) {
event.preventDefault(); event.preventDefault();
event.currentTarget.classList.toggle("active"); event.currentTarget.classList.toggle("active");
@ -838,7 +851,7 @@ _onItemCollapse(event) {
} }
} }
/** /**
* Handle incrementing class level on the actor sheet * Handle incrementing class level on the actor sheet
* @param {Event} event The originating click event * @param {Event} event The originating click event
* @private * @private
@ -847,7 +860,7 @@ _onItemCollapse(event) {
_onIncrementClassLevel(event) { _onIncrementClassLevel(event) {
event.preventDefault(); event.preventDefault();
const div = event.currentTarget.closest(".character") const div = event.currentTarget.closest(".character");
const li = event.currentTarget.closest("li"); const li = event.currentTarget.closest("li");
const actorId = div.id.split("-")[1]; const actorId = div.id.split("-")[1];
@ -857,12 +870,12 @@ _onItemCollapse(event) {
const item = actor.items.get(itemId); const item = actor.items.get(itemId);
let levels = item.data.data.levels; let levels = item.data.data.levels;
const update = {_id: item.data._id, data: {levels: (levels + 1) }}; const update = {_id: item.data._id, data: {levels: levels + 1}};
actor.updateEmbeddedDocuments("Item", [update]); actor.updateEmbeddedDocuments("Item", [update]);
} }
/** /**
* Handle decrementing class level on the actor sheet * Handle decrementing class level on the actor sheet
* @param {Event} event The originating click event * @param {Event} event The originating click event
* @private * @private
@ -871,7 +884,7 @@ _onItemCollapse(event) {
_onDecrementClassLevel(event) { _onDecrementClassLevel(event) {
event.preventDefault(); event.preventDefault();
const div = event.currentTarget.closest(".character") const div = event.currentTarget.closest(".character");
const li = event.currentTarget.closest("li"); const li = event.currentTarget.closest("li");
const actorId = div.id.split("-")[1]; const actorId = div.id.split("-")[1];
@ -881,10 +894,10 @@ _onItemCollapse(event) {
const item = actor.items.get(itemId); const item = actor.items.get(itemId);
let levels = item.data.data.levels; let levels = item.data.data.levels;
const update = {_id: item.data._id, data: {levels: (levels - 1) }}; const update = {_id: item.data._id, data: {levels: levels - 1}};
actor.updateEmbeddedDocuments("Item", [update]); actor.updateEmbeddedDocuments("Item", [update]);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -937,7 +950,7 @@ _onItemCollapse(event) {
const li = event.currentTarget; const li = event.currentTarget;
const set = this._filters[li.parentElement.dataset.filter]; const set = this._filters[li.parentElement.dataset.filter];
const filter = li.dataset.filter; const filter = li.dataset.filter;
if ( set.has(filter) ) set.delete(filter); if (set.has(filter)) set.delete(filter);
else set.add(filter); else set.add(filter);
return this.render(); return this.render();
} }
@ -954,8 +967,8 @@ _onItemCollapse(event) {
const a = event.currentTarget; const a = event.currentTarget;
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};
return new TraitSelector(this.actor, options).render(true) return new TraitSelector(this.actor, options).render(true);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -965,7 +978,7 @@ _onItemCollapse(event) {
let buttons = super._getHeaderButtons(); let buttons = super._getHeaderButtons();
if (this.actor.isPolymorphed) { if (this.actor.isPolymorphed) {
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: () => this.actor.revertOriginalForm() onclick: () => 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"
}], }
]
}); });
} }
@ -48,7 +48,7 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
sheetData["resources"] = ["primary", "secondary", "tertiary"].reduce((arr, r) => { sheetData["resources"] = ["primary", "secondary", "tertiary"].reduce((arr, r) => {
const res = sheetData.data.resources[r] || {}; const res = sheetData.data.resources[r] || {};
res.name = r; res.name = r;
res.placeholder = game.i18n.localize("SW5E.Resource"+r.titleCase()); res.placeholder = game.i18n.localize("SW5E.Resource" + r.titleCase());
if (res && res.value === 0) delete res.value; if (res && res.value === 0) delete res.value;
if (res && res.max === 0) delete res.max; if (res && res.max === 0) delete res.max;
return arr.concat([res]); return arr.concat([res]);
@ -56,10 +56,12 @@ 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,23 +74,38 @@ 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"}},
equipment: { label: "SW5E.ItemTypeEquipmentPl", items: [], dataset: {type: "equipment"} }, equipment: {label: "SW5E.ItemTypeEquipmentPl", items: [], dataset: {type: "equipment"}},
consumable: { label: "SW5E.ItemTypeConsumablePl", items: [], dataset: {type: "consumable"} }, consumable: {label: "SW5E.ItemTypeConsumablePl", items: [], dataset: {type: "consumable"}},
tool: { label: "SW5E.ItemTypeToolPl", items: [], dataset: {type: "tool"} }, tool: {label: "SW5E.ItemTypeToolPl", items: [], dataset: {type: "tool"}},
backpack: { label: "SW5E.ItemTypeContainerPl", items: [], dataset: {type: "backpack"} }, backpack: {label: "SW5E.ItemTypeContainerPl", items: [], dataset: {type: "backpack"}},
loot: { label: "SW5E.ItemTypeLootPl", items: [], dataset: {type: "loot"} } loot: {label: "SW5E.ItemTypeLootPl", items: [], dataset: {type: "loot"}}
}; };
// Partition items by category // Partition items by category
let [items, forcepowers, techpowers, feats, classes, deployments, deploymentfeatures, ventures, species, archetypes, classfeatures, backgrounds, fightingstyles, fightingmasteries, lightsaberforms] = data.items.reduce((arr, item) => { let [
items,
forcepowers,
techpowers,
feats,
classes,
deployments,
deploymentfeatures,
ventures,
species,
archetypes,
classfeatures,
backgrounds,
fightingstyles,
fightingmasteries,
lightsaberforms
] = data.items.reduce(
(arr, item) => {
// Item details // Item details
item.img = item.img || CONST.DEFAULT_TOKEN; item.img = item.img || CONST.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,35 +120,39 @@ 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.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0)); item.data.recharge && !!item.data.recharge.value && item.data.recharge.charged === false;
item.hasTarget = !!item.data.target && !(["none",""].includes(item.data.target.type)); 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 toggle state // Item toggle state
this._prepareItemToggleState(item); this._prepareItemToggleState(item);
// Primary Class // Primary Class
if ( item.type === "class" ) item.isOriginalClass = ( item._id === this.actor.data.data.details.originalClass ); if (item.type === "class")
item.isOriginalClass = item._id === this.actor.data.data.details.originalClass;
// Classify items into types // Classify items into types
if ( item.type === "power" && ["lgt", "drk", "uni"].includes(item.data.school) ) arr[1].push(item); if (item.type === "power" && ["lgt", "drk", "uni"].includes(item.data.school)) arr[1].push(item);
else if ( item.type === "power" && ["tec"].includes(item.data.school) ) arr[2].push(item); else if (item.type === "power" && ["tec"].includes(item.data.school)) arr[2].push(item);
else if ( item.type === "feat" ) arr[3].push(item); else if (item.type === "feat") arr[3].push(item);
else if ( item.type === "class" ) arr[4].push(item); else if (item.type === "class") arr[4].push(item);
else if ( item.type === "deployment" ) arr[5].push(item); else if (item.type === "deployment") arr[5].push(item);
else if ( item.type === "deploymentfeature" ) arr[6].push(item); else if (item.type === "deploymentfeature") arr[6].push(item);
else if ( item.type === "venture" ) arr[7].push(item); else if (item.type === "venture") arr[7].push(item);
else if ( item.type === "species" ) arr[8].push(item); else if (item.type === "species") arr[8].push(item);
else if ( item.type === "archetype" ) arr[9].push(item); else if (item.type === "archetype") arr[9].push(item);
else if ( item.type === "classfeature" ) arr[10].push(item); else if (item.type === "classfeature") arr[10].push(item);
else if ( item.type === "background" ) arr[11].push(item); else if (item.type === "background") arr[11].push(item);
else if ( item.type === "fightingstyle" ) arr[12].push(item); else if (item.type === "fightingstyle") arr[12].push(item);
else if ( item.type === "fightingmastery" ) arr[13].push(item); else if (item.type === "fightingmastery") arr[13].push(item);
else if ( item.type === "lightsaberform" ) arr[14].push(item); else if (item.type === "lightsaberform") arr[14].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);
@ -140,7 +161,7 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
feats = this._filterItems(feats, this._filters.features); feats = this._filterItems(feats, this._filters.features);
// Organize items // Organize items
for ( let i of items ) { for (let i of items) {
i.data.quantity = i.data.quantity || 0; i.data.quantity = i.data.quantity || 0;
i.data.weight = i.data.weight || 0; i.data.weight = i.data.weight || 0;
i.totalWeight = (i.data.quantity * i.data.weight).toNearest(0.1); i.totalWeight = (i.data.quantity * i.data.weight).toNearest(0.1);
@ -153,22 +174,93 @@ 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: {
classfeatures: { label: "SW5E.ItemTypeClassFeats", items: [], hasActions: true, dataset: {type: "classfeature"}, isClassfeature: true }, label: "SW5E.ItemTypeClassPl",
archetype: { label: "SW5E.ItemTypeArchetype", items: [], hasActions: false, dataset: {type: "archetype"}, isArchetype: true }, items: [],
deployments: { label: "SW5E.ItemTypeDeploymentPl", items: [], hasActions: false, dataset: {type: "deployment"}, isDeployment: true }, hasActions: false,
deploymentfeatures: { label: "SW5E.ItemTypeDeploymentFeaturePl", items: [], hasActions: true, dataset: {type: "deploymentfeature"}, isDeploymentfeature: true }, dataset: {type: "class"},
ventures: { label: "SW5E.ItemTypeVenturePl", items: [], hasActions: false, dataset: {type: "venture"}, isVenture: true }, isClass: 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 }, classfeatures: {
fightingstyles: { label: "SW5E.ItemTypeFightingStylePl", items: [], hasActions: false, dataset: {type: "fightingstyle"}, isFightingstyle: true }, label: "SW5E.ItemTypeClassFeats",
fightingmasteries: { label: "SW5E.ItemTypeFightingMasteryPl", items: [], hasActions: false, dataset: {type: "fightingmastery"}, isFightingmastery: true }, items: [],
lightsaberforms: { label: "SW5E.ItemTypeLightsaberFormPl", items: [], hasActions: false, dataset: {type: "lightsaberform"}, isLightsaberform: true }, hasActions: true,
active: { label: "SW5E.FeatureActive", items: [], hasActions: true, dataset: {type: "feat", "activation.type": "action"} }, dataset: {type: "classfeature"},
passive: { label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"} } isClassfeature: true
},
archetype: {
label: "SW5E.ItemTypeArchetype",
items: [],
hasActions: false,
dataset: {type: "archetype"},
isArchetype: true
},
deployments: {
label: "SW5E.ItemTypeDeploymentPl",
items: [],
hasActions: false,
dataset: {type: "deployment"},
isDeployment: true
},
deploymentfeatures: {
label: "SW5E.ItemTypeDeploymentFeaturePl",
items: [],
hasActions: true,
dataset: {type: "deploymentfeature"},
isDeploymentfeature: true
},
ventures: {
label: "SW5E.ItemTypeVenturePl",
items: [],
hasActions: false,
dataset: {type: "venture"},
isVenture: 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"}}
}; };
for ( let f of feats ) { for (let f of feats) {
if ( f.data.activation.type ) features.active.items.push(f); if (f.data.activation.type) features.active.items.push(f);
else features.passive.items.push(f); else features.passive.items.push(f);
} }
classes.sort((a, b) => b.data.levels - a.data.levels); classes.sort((a, b) => b.data.levels - a.data.levels);
@ -203,12 +295,11 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
const isAlways = getProperty(item.data, "preparation.mode") === "always"; const isAlways = getProperty(item.data, "preparation.mode") === "always";
const isPrepared = getProperty(item.data, "preparation.prepared"); const isPrepared = getProperty(item.data, "preparation.prepared");
item.toggleClass = isPrepared ? "active" : ""; item.toggleClass = isPrepared ? "active" : "";
if ( isAlways ) item.toggleClass = "fixed"; if (isAlways) item.toggleClass = "fixed";
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");
@ -225,25 +316,27 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
*/ */
activateListeners(html) { activateListeners(html) {
super.activateListeners(html); super.activateListeners(html);
if ( !this.isEditable ) return; if (!this.isEditable) return;
// Inventory Functions // Inventory Functions
// 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));
// Send Languages to Chat onClick // Send Languages to Chat onClick
html.find('[data-options="share-languages"]').click(event => { html.find('[data-options="share-languages"]').click((event) => {
event.preventDefault(); event.preventDefault();
let langs = this.actor.data.data.traits.languages.value.map(l => CONFIG.SW5E.languages[l] || l).join(", "); let langs = this.actor.data.data.traits.languages.value
.map((l) => CONFIG.SW5E.languages[l] || l)
.join(", ");
let custom = this.actor.data.data.traits.languages.custom; let custom = this.actor.data.data.traits.languages.custom;
if (custom) langs += ", " + custom.replace(/;/g, ","); if (custom) langs += ", " + custom.replace(/;/g, ",");
let content = ` let content = `
@ -279,9 +372,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.items.get(itemId); let item = this.actor.items.get(itemId);
new Dialog({ new Dialog({
@ -290,17 +383,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);
}); });
} }
@ -315,7 +408,7 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
_onSheetAction(event) { _onSheetAction(event) {
event.preventDefault(); event.preventDefault();
const button = event.currentTarget; const button = event.currentTarget;
switch( button.dataset.action ) { switch (button.dataset.action) {
case "rollDeathSave": case "rollDeathSave":
return this.actor.rollDeathSave({event: event}); return this.actor.rollDeathSave({event: event});
case "rollInitiative": case "rollInitiative":
@ -325,7 +418,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
@ -369,14 +461,13 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
/** @override */ /** @override */
async _onDropItemCreate(itemData) { async _onDropItemCreate(itemData) {
// Increment the number of class levels of a character instead of creating a new item // Increment the number of class levels of 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);
let priorLevel = cls?.data.data.levels ?? 0; let priorLevel = cls?.data.data.levels ?? 0;
if ( !!cls ) { if (!!cls) {
const next = Math.min(priorLevel + 1, 20 + priorLevel - this.actor.data.data.details.level); const next = Math.min(priorLevel + 1, 20 + priorLevel - this.actor.data.data.details.level);
if ( next > priorLevel ) { if (next > priorLevel) {
itemData.levels = next; itemData.levels = next;
return cls.update({"data.levels": next}); return cls.update({"data.levels": next});
} }
@ -457,9 +548,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;
@ -470,24 +561,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 = $(
favBtn.click(ev => { `<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) => {
app.actor.items.get(item.data._id).update({ app.actor.items.get(item.data._id).update({
"flags.favtab.isFavourite": !item.flags.favtab.isFavourite "flags.favtab.isFavourite": !item.flags.favtab.isFavourite
}); });
}); });
html.find(`.item[data-item-id="${item.data._id}"]`).find('.item-controls').prepend(favBtn); html.find(`.item[data-item-id="${item.data._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); let c = !!comps.concentration;
let r = !!(comps.ritual); let r = !!comps.ritual;
item.powerComps = `${v}${s}${m}`; item.powerComps = `${v}${s}${m}`;
item.powerCon = c; item.powerCon = c;
item.powerRit = r; item.powerRit = r;
@ -495,15 +590,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);
@ -529,60 +624,60 @@ 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.items.get(itemId).sheet.render(true); app.actor.items.get(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.items.get(itemId).data.flags.favtab.isFavourite let val = !app.actor.items.get(itemId).data.flags.favtab.isFavourite;
app.actor.items.get(itemId).update({ app.actor.items.get(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.data._id === dropData.data._id); let dragSource = list.find((i) => i.data._id === dropData.data._id);
let siblings = list.filter(i => i.data._id !== dropData.data._id); let siblings = list.filter((i) => i.data._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.data._id === targetId); let dragTarget = siblings.find((s) => s.data._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;
update._id = u.target.data._id; update._id = u.target.data._id;
return update; return update;
@ -608,49 +703,47 @@ async function addFavorites(app, html, data) {
Hooks.callAll("renderedSwaltSheet", app, html, data); Hooks.callAll("renderedSwaltSheet", app, html, data);
} }
async function addSubTabs(app, html, data) { 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) {
data.options.subTabs[group].forEach(tab => {
if(tab.active) {
html.find(`[data-subgroup=${group}][data-target=${tab.target}]`).addClass('active');
} else {
html.find(`[data-subgroup=${group}][data-target=${tab.target}]`).removeClass('active');
}
})
}
html.find('[data-subgroup-selection]').children().on('click', event => {
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 => {
return tab.target == target
}); });
data.options.subTabs[subgroup].map(el => { }
for (const group in data.options.subTabs) {
data.options.subTabs[group].forEach((tab) => {
if (tab.active) {
html.find(`[data-subgroup=${group}][data-target=${tab.target}]`).addClass("active");
} else {
html.find(`[data-subgroup=${group}][data-target=${tab.target}]`).removeClass("active");
}
});
}
html.find("[data-subgroup-selection]")
.children()
.on("click", (event) => {
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) => {
return tab.target == target;
});
data.options.subTabs[subgroup].map((el) => {
el.active = el.target == target; el.active = el.target == target;
return el; return el;
}) });
});
})
} }
Hooks.on("renderActorSheet5eCharacterNew", (app, html, data) => { Hooks.on("renderActorSheet5eCharacterNew", (app, html, data) => {

View file

@ -6,10 +6,9 @@ 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";
return `systems/sw5e/templates/actors/newActor/npc-sheet.html`; return `systems/sw5e/templates/actors/newActor/npc-sheet.html`;
} }
/** @override */ /** @override */
@ -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"
}], }
]
}); });
} }
@ -37,28 +38,41 @@ 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"),
passive: { label: game.i18n.localize("SW5E.Features"), items: [], dataset: {type: "feat"} }, items: [],
equipment: { label: game.i18n.localize("SW5E.Inventory"), items: [], dataset: {type: "loot"}} 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"}},
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 || CONST.DEFAULT_TOKEN; item.img = item.img || CONST.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.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0)); item.data.recharge && !!item.data.recharge.value && item.data.recharge.charged === false;
item.hasTarget = !!item.data.target && !(["none",""].includes(item.data.target.type)); item.isDepleted = item.isOnCooldown && item.data.uses.per && item.data.uses.value > 0;
if ( item.type === "power" && ["lgt", "drk", "uni"].includes(item.data.school) ) arr[0].push(item); item.hasTarget = !!item.data.target && !["none", ""].includes(item.data.target.type);
else if ( item.type === "power" && ["tec"].includes(item.data.school) ) arr[1].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 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,13 +84,12 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
const techPowerbook = this._preparePowerbook(data, techpowers, "tec"); const techPowerbook = this._preparePowerbook(data, techpowers, "tec");
// Organize Features // Organize Features
for ( let item of other ) { for (let item of other) {
if ( item.type === "weapon" ) features.weapons.items.push(item); if (item.type === "weapon") features.weapons.items.push(item);
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
@ -107,13 +120,12 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
/** @override */ /** @override */
async _updateObject(event, formData) { async _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";
let cr = formData[crv]; let cr = formData[crv];
cr = crs[cr] || parseFloat(cr); cr = crs[cr] || parseFloat(cr);
if ( cr ) formData[crv] = cr < 1 ? cr : parseInt(cr); if (cr) formData[crv] = cr < 1 ? cr : parseInt(cr);
// Parent ActorSheet update steps // Parent ActorSheet update steps
return super._updateObject(event, formData); return super._updateObject(event, formData);
@ -139,10 +151,9 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
_onRollHPFormula(event) { _onRollHPFormula(event) {
event.preventDefault(); event.preventDefault();
const formula = this.actor.data.data.attributes.hp.formula; const formula = this.actor.data.data.attributes.hp.formula;
if ( !formula ) return; if (!formula) return;
const hp = new Roll(formula).roll().total; const hp = new Roll(formula).roll().total;
AudioHelper.play({src: CONFIG.sounds.dice}); AudioHelper.play({src: CONFIG.sounds.dice});
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

@ -6,10 +6,9 @@ import ActorSheet5e from "./base.js";
* @extends {ActorSheet5e} * @extends {ActorSheet5e}
*/ */
export default class ActorSheet5eStarship extends ActorSheet5e { export default class ActorSheet5eStarship 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";
return `systems/sw5e/templates/actors/newActor/starship.html`; return `systems/sw5e/templates/actors/newActor/starship.html`;
} }
/** @override */ /** @override */
@ -17,11 +16,13 @@ export default class ActorSheet5eStarship extends ActorSheet5e {
return mergeObject(super.defaultOptions, { return mergeObject(super.defaultOptions, {
classes: ["sw5e", "sheet", "actor", "starship"], classes: ["sw5e", "sheet", "actor", "starship"],
width: 800, width: 800,
tabs: [{ tabs: [
{
navSelector: ".root-tabs", navSelector: ".root-tabs",
contentSelector: ".sheet-body", contentSelector: ".sheet-body",
initial: "attributes" initial: "attributes"
}], }
]
}); });
} }
@ -32,29 +33,47 @@ export default class ActorSheet5eStarship 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.ItemTypeWeaponPl"), items: [], hasActions: true, dataset: {type: "weapon", "weapon-type": "natural"} }, weapons: {
passive: { label: game.i18n.localize("SW5E.Features"), items: [], dataset: {type: "feat"} }, label: game.i18n.localize("SW5E.ItemTypeWeaponPl"),
equipment: { label: game.i18n.localize("SW5E.StarshipEquipment"), items: [], dataset: {type: "equipment"}}, items: [],
starshipfeatures: { label: game.i18n.localize("SW5E.StarshipfeaturePl"), items: [], hasActions: true, dataset: {type: "starshipfeature"} }, hasActions: true,
starshipmods: { label: game.i18n.localize("SW5E.StarshipmodPl"), items: [], hasActions: false, dataset: {type: "starshipmod"} } dataset: {"type": "weapon", "weapon-type": "natural"}
},
passive: {label: game.i18n.localize("SW5E.Features"), items: [], dataset: {type: "feat"}},
equipment: {label: game.i18n.localize("SW5E.StarshipEquipment"), items: [], dataset: {type: "equipment"}},
starshipfeatures: {
label: game.i18n.localize("SW5E.StarshipfeaturePl"),
items: [],
hasActions: true,
dataset: {type: "starshipfeature"}
},
starshipmods: {
label: game.i18n.localize("SW5E.StarshipmodPl"),
items: [],
hasActions: false,
dataset: {type: "starshipmod"}
}
}; };
// 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 || CONST.DEFAULT_TOKEN; item.img = item.img || CONST.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.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0)); item.data.recharge && !!item.data.recharge.value && item.data.recharge.charged === false;
item.hasTarget = !!item.data.target && !(["none",""].includes(item.data.target.type)); item.isDepleted = item.isOnCooldown && item.data.uses.per && item.data.uses.value > 0;
if ( item.type === "power" && ["lgt", "drk", "uni"].includes(item.data.school) ) arr[0].push(item); item.hasTarget = !!item.data.target && !["none", ""].includes(item.data.target.type);
else if ( item.type === "power" && ["tec"].includes(item.data.school) ) arr[1].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 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);
@ -62,32 +81,28 @@ export default class ActorSheet5eStarship extends ActorSheet5e {
other = this._filterItems(other, this._filters.features); other = this._filterItems(other, this._filters.features);
// Organize Powerbook // Organize Powerbook
// const forcePowerbook = this._preparePowerbook(data, forcepowers, "uni"); // const forcePowerbook = this._preparePowerbook(data, forcepowers, "uni");
// const techPowerbook = this._preparePowerbook(data, techpowers, "tec"); // const techPowerbook = this._preparePowerbook(data, techpowers, "tec");
// Organize Features // Organize Features
for ( let item of other ) { for (let item of other) {
if ( item.type === "weapon" ) features.weapons.items.push(item); if (item.type === "weapon") features.weapons.items.push(item);
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 if (item.type === "starshipfeature") {
else if ( item.type === "starshipfeature" ) {
features.starshipfeatures.items.push(item); features.starshipfeatures.items.push(item);
} } else if (item.type === "starshipmod") {
else if ( item.type === "starshipmod" ) {
features.starshipmods.items.push(item); features.starshipmods.items.push(item);
} } else features.equipment.items.push(item);
else features.equipment.items.push(item);
} }
// Assign and return // Assign and return
data.features = Object.values(features); data.features = Object.values(features);
// data.forcePowerbook = forcePowerbook; // data.forcePowerbook = forcePowerbook;
// data.techPowerbook = techPowerbook; // data.techPowerbook = techPowerbook;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
@ -115,13 +130,12 @@ export default class ActorSheet5eStarship extends ActorSheet5e {
/** @override */ /** @override */
async _updateObject(event, formData) { async _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";
let cr = formData[crv]; let cr = formData[crv];
cr = crs[cr] || parseFloat(cr); cr = crs[cr] || parseFloat(cr);
if ( cr ) formData[crv] = cr < 1 ? cr : parseInt(cr); if (cr) formData[crv] = cr < 1 ? cr : parseInt(cr);
// Parent ActorSheet update steps // Parent ActorSheet update steps
return super._updateObject(event, formData); return super._updateObject(event, formData);
@ -147,7 +161,7 @@ export default class ActorSheet5eStarship extends ActorSheet5e {
_onRollHPFormula(event) { _onRollHPFormula(event) {
event.preventDefault(); event.preventDefault();
const formula = this.actor.data.data.attributes.hp.formula; const formula = this.actor.data.data.attributes.hp.formula;
if ( !formula ) return; if (!formula) return;
const hp = new Roll(formula).roll().total; const hp = new Roll(formula).roll().total;
AudioHelper.play({src: CONFIG.sounds.dice}); AudioHelper.play({src: CONFIG.sounds.dice});
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,13 +25,12 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**
* Creates a new cargo entry for a vehicle Actor. * Creates a new cargo entry for a vehicle Actor.
*/ */
static get newCargo() { static get newCargo() {
return { return {
name: '', name: "",
quantity: 1 quantity: 1
}; };
} }
@ -46,7 +45,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;
@ -63,7 +61,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
_getMovementSpeed(actorData, largestPrimary=true) { _getMovementSpeed(actorData, largestPrimary = true) {
return super._getMovementSpeed(actorData, largestPrimary); return super._getMovementSpeed(actorData, largestPrimary);
} }
@ -75,25 +73,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 : "—";
} }
} }
@ -104,111 +101,125 @@ 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"
}
]
} }
}; };
@ -219,14 +230,14 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
// Handle cargo explicitly // Handle cargo explicitly
const isCargo = item.flags.sw5e?.vehicleCargo === true; const isCargo = item.flags.sw5e?.vehicleCargo === true;
if ( isCargo ) { if (isCargo) {
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);
continue; continue;
} }
// Handle non-cargo item types // Handle non-cargo item types
switch ( item.type ) { switch (item.type) {
case "weapon": case "weapon":
features.weapons.items.push(item); features.weapons.items.push(item);
break; break;
@ -234,8 +245,9 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
features.equipment.items.push(item); features.equipment.items.push(item);
break; break;
case "feat": case "feat":
if ( !item.data.activation.type || (item.data.activation.type === "none") ) features.passive.items.push(item); if (!item.data.activation.type || item.data.activation.type === "none")
else if (item.data.activation.type === 'reaction') features.reactions.items.push(item); features.passive.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);
break; break;
default: default:
@ -259,21 +271,21 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
super.activateListeners(html); super.activateListeners(html);
if (!this.isEditable) return; if (!this.isEditable) 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();
} }
} }
@ -288,9 +300,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 = foundry.utils.deepClone(this.actor.data.data.cargo[property]); const cargo = foundry.utils.deepClone(this.actor.data.data.cargo[property]);
@ -298,10 +310,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
@ -318,14 +330,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});
} }
@ -342,7 +358,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 = foundry.utils.deepClone(this.actor.data.data.cargo[type]); const cargo = foundry.utils.deepClone(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});
@ -360,10 +376,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 = foundry.utils.deepClone(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx); const cargo = foundry.utils.deepClone(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});
} }
@ -376,7 +392,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
/** @override */ /** @override */
async _onDropItemCreate(itemData) { async _onDropItemCreate(itemData) {
const cargoTypes = ["weapon", "equipment", "consumable", "tool", "loot", "backpack"]; const cargoTypes = ["weapon", "equipment", "consumable", "tool", "loot", "backpack"];
const isCargo = cargoTypes.includes(itemData.type) && (this._tabs[0].active === "cargo"); const isCargo = cargoTypes.includes(itemData.type) && this._tabs[0].active === "cargo";
foundry.utils.setProperty(itemData, "flags.sw5e.vehicleCargo", isCargo); foundry.utils.setProperty(itemData, "flags.sw5e.vehicleCargo", isCargo);
return super._onDropItemCreate(itemData); return super._onDropItemCreate(itemData);
} }
@ -391,11 +407,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});
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -408,9 +424,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

@ -5,7 +5,7 @@ import ActorHitDiceConfig from "../../../apps/hit-dice-config.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 ActorTypeConfig from "../../../apps/actor-type.js"; import ActorTypeConfig from "../../../apps/actor-type.js";
import {SW5E} from '../../../config.js'; import {SW5E} from "../../../config.js";
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js"; import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js";
/** /**
@ -54,10 +54,9 @@ export default class ActorSheet5e extends ActorSheet {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
get template() { get template() {
if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/oldActor/limited-sheet.html"; if (!game.user.isGM && this.actor.limited) return "systems/sw5e/templates/actors/oldActor/limited-sheet.html";
return `systems/sw5e/templates/actors/oldActor/${this.actor.data.type}-sheet.html`; return `systems/sw5e/templates/actors/oldActor/${this.actor.data.type}-sheet.html`;
} }
@ -65,7 +64,6 @@ export default class ActorSheet5e extends ActorSheet {
/** @override */ /** @override */
getData(options) { getData(options) {
// Basic data // Basic data
let isOwner = this.actor.isOwner; let isOwner = this.actor.isOwner;
const data = { const data = {
@ -77,7 +75,7 @@ export default class ActorSheet5e extends ActorSheet {
isCharacter: this.actor.type === "character", isCharacter: this.actor.type === "character",
isNPC: this.actor.type === "npc", isNPC: this.actor.type === "npc",
isStarship: this.actor.type === "starship", isStarship: this.actor.type === "starship",
isVehicle: this.actor.type === 'vehicle', isVehicle: this.actor.type === "vehicle",
config: CONFIG.SW5E, config: CONFIG.SW5E,
rollData: this.actor.getRollData.bind(this.actor) rollData: this.actor.getRollData.bind(this.actor)
}; };
@ -89,7 +87,7 @@ export default class ActorSheet5e extends ActorSheet {
// Owned Items // Owned Items
data.items = actorData.items; data.items = actorData.items;
for ( let i of data.items ) { for (let i of data.items) {
const item = this.actor.items.get(i._id); const item = this.actor.items.get(i._id);
i.labels = item.labels; i.labels = item.labels;
} }
@ -100,7 +98,7 @@ export default class ActorSheet5e extends ActorSheet {
data.filters = this._filters; data.filters = this._filters;
// Ability Scores // Ability Scores
for ( let [a, abl] of Object.entries(actorData.data.abilities)) { for (let [a, abl] of Object.entries(actorData.data.abilities)) {
abl.icon = this._getProficiencyIcon(abl.proficient); abl.icon = this._getProficiencyIcon(abl.proficient);
abl.hover = CONFIG.SW5E.proficiencyLevels[abl.proficient]; abl.hover = CONFIG.SW5E.proficiencyLevels[abl.proficient];
abl.label = CONFIG.SW5E.abilities[a]; abl.label = CONFIG.SW5E.abilities[a];
@ -108,7 +106,7 @@ export default class ActorSheet5e extends ActorSheet {
// Skills // Skills
if (actorData.data.skills) { if (actorData.data.skills) {
for ( let [s, skl] of Object.entries(actorData.data.skills)) { for (let [s, skl] of Object.entries(actorData.data.skills)) {
skl.ability = CONFIG.SW5E.abilityAbbreviations[skl.ability]; skl.ability = CONFIG.SW5E.abilityAbbreviations[skl.ability];
skl.icon = this._getProficiencyIcon(skl.value); skl.icon = this._getProficiencyIcon(skl.value);
skl.hover = CONFIG.SW5E.proficiencyLevels[skl.value]; skl.hover = CONFIG.SW5E.proficiencyLevels[skl.value];
@ -132,7 +130,7 @@ export default class ActorSheet5e extends ActorSheet {
data.effects = prepareActiveEffectCategories(this.actor.effects); data.effects = prepareActiveEffectCategories(this.actor.effects);
// Return data to the sheet // Return data to the sheet
return data return data;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -144,38 +142,42 @@ export default class ActorSheet5e extends ActorSheet {
* @returns {{primary: string, special: string}} * @returns {{primary: string, special: string}}
* @private * @private
*/ */
_getMovementSpeed(actorData, largestPrimary=false) { _getMovementSpeed(actorData, largestPrimary = false) {
const movement = actorData.data.attributes.movement || {}; const movement = actorData.data.attributes.movement || {};
// Prepare an array of available movement speeds // Prepare an array of available movement speeds
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}`]);
} }
// Filter and sort speeds on their values // Filter and sort speeds on their values
speeds = speeds.filter(s => !!s[0]).sort((a, b) => b[0] - a[0]); speeds = speeds.filter((s) => !!s[0]).sort((a, b) => b[0] - a[0]);
// Case 1: Largest as primary // Case 1: Largest as primary
if ( largestPrimary ) { if (largestPrimary) {
let primary = speeds.shift(); let primary = speeds.shift();
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
else { else {
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(", ") : ""
} };
} }
} }
@ -184,12 +186,12 @@ export default class ActorSheet5e extends ActorSheet {
_getSenses(actorData) { _getSenses(actorData) {
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}`;
} }
if ( !!senses.special ) tags["special"] = senses.special; if (!!senses.special) tags["special"] = senses.special;
return tags; return tags;
} }
@ -202,20 +204,20 @@ 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];
if ( !trait ) continue; if (!trait) continue;
let values = []; let values = [];
if ( trait.value ) { if (trait.value) {
values = trait.value instanceof Array ? trait.value : [trait.value]; values = trait.value instanceof Array ? trait.value : [trait.value];
} }
trait.selected = values.reduce((obj, t) => { trait.selected = values.reduce((obj, t) => {
@ -224,8 +226,8 @@ 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";
} }
@ -246,9 +248,9 @@ 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
@ -259,13 +261,13 @@ export default class ActorSheet5e extends ActorSheet {
}; };
// Format a powerbook entry for a certain indexed level // Format a powerbook entry for a certain indexed level
const registerSection = (sl, i, label, {prepMode="prepared", value, max, override}={}) => { const registerSection = (sl, i, label, {prepMode = "prepared", value, max, override} = {}) => {
powerbook[i] = { powerbook[i] = {
order: i, order: i,
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,
@ -277,14 +279,14 @@ export default class ActorSheet5e extends ActorSheet {
// Determine the maximum power level which has a slot // Determine the maximum power level which has a slot
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);
// Level-based powercasters have cantrips and leveled slots // Level-based powercasters have cantrips and leveled slots
if ( maxLevel > 0 ) { if (maxLevel > 0) {
registerSection("power0", 0, CONFIG.SW5E.powerLevels[0]); registerSection("power0", 0, CONFIG.SW5E.powerLevels[0]);
for (let lvl = 1; lvl <= maxLevel; lvl++) { for (let lvl = 1; lvl <= maxLevel; lvl++) {
const sl = `power${lvl}`; const sl = `power${lvl}`;
@ -294,8 +296,8 @@ export default class ActorSheet5e extends ActorSheet {
// Pact magic users have cantrips and a pact magic section // Pact magic users have cantrips and a pact magic section
// TODO: Check if this is needed, we've removed pacts everywhere else // TODO: Check if this is needed, we've removed pacts everywhere else
if ( levels.pact && levels.pact.max ) { if (levels.pact && levels.pact.max) {
if ( !powerbook["0"] ) registerSection("power0", 0, CONFIG.SW5E.powerLevels[0]); if (!powerbook["0"]) registerSection("power0", 0, CONFIG.SW5E.powerLevels[0]);
const l = levels.pact; const l = levels.pact;
const config = CONFIG.SW5E.powerPreparationModes.pact; const config = CONFIG.SW5E.powerPreparationModes.pact;
const level = game.i18n.localize(`SW5E.PowerLevel${levels.pact.level}`); const level = game.i18n.localize(`SW5E.PowerLevel${levels.pact.level}`);
@ -309,15 +311,15 @@ export default class ActorSheet5e extends ActorSheet {
} }
// Iterate over every power item, adding powers to the powerbook by section // Iterate over every power item, adding powers to the powerbook by section
powers.forEach(power => { powers.forEach((power) => {
const mode = power.data.preparation.mode || "prepared"; const mode = power.data.preparation.mode || "prepared";
let s = power.data.level || 0; let s = power.data.level || 0;
const sl = `power${s}`; const sl = `power${s}`;
// Specialized powercasting modes (if they exist) // Specialized powercasting modes (if they exist)
if ( mode in sections ) { if (mode in sections) {
s = sections[mode]; s = sections[mode];
if ( !powerbook[s] ){ if (!powerbook[s]) {
const l = levels[mode] || {}; const l = levels[mode] || {};
const config = CONFIG.SW5E.powerPreparationModes[mode]; const config = CONFIG.SW5E.powerPreparationModes[mode];
registerSection(mode, s, config, { registerSection(mode, s, config, {
@ -330,7 +332,7 @@ export default class ActorSheet5e extends ActorSheet {
} }
// Sections for higher-level powers which the caster "should not" have, but power items exist for // Sections for higher-level powers which the caster "should not" have, but power items exist for
else if ( !powerbook[s] ) { else if (!powerbook[s]) {
registerSection(sl, s, CONFIG.SW5E.powerLevels[s], {levels: levels[sl]}); registerSection(sl, s, CONFIG.SW5E.powerLevels[s], {levels: levels[sl]});
} }
@ -352,32 +354,32 @@ export default class ActorSheet5e extends ActorSheet {
* @private * @private
*/ */
_filterItems(items, filters) { _filterItems(items, filters) {
return items.filter(item => { return items.filter((item) => {
const data = item.data; const data = item.data;
// 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;
} }
} }
// Power-specific filters // Power-specific filters
if ( filters.has("ritual") ) { if (filters.has("ritual")) {
if (data.components.ritual !== true) return false; if (data.components.ritual !== true) return false;
} }
if ( filters.has("concentration") ) { if (filters.has("concentration")) {
if (data.components.concentration !== true) return false; if (data.components.concentration !== true) return false;
} }
if ( filters.has("prepared") ) { if (filters.has("prepared")) {
if ( data.level === 0 || ["innate", "always"].includes(data.preparation.mode) ) return true; if (data.level === 0 || ["innate", "always"].includes(data.preparation.mode)) return true;
if ( this.actor.data.type === "npc" ) return true; if (this.actor.data.type === "npc") return true;
return data.preparation.prepared; return data.preparation.prepared;
} }
// Equipment-specific filters // Equipment-specific filters
if ( filters.has("equipped") ) { if (filters.has("equipped")) {
if ( data.equipped !== true ) return false; if (data.equipped !== true) return false;
} }
return true; return true;
}); });
@ -405,61 +407,59 @@ export default class ActorSheet5e extends ActorSheet {
/** @inheritdoc */ /** @inheritdoc */
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));
// View Item Sheets // View Item Sheets
html.find('.item-edit').click(this._onItemEdit.bind(this)); html.find(".item-edit").click(this._onItemEdit.bind(this));
// 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-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.find(".item-uses input")
html.find('.slot-max-override').click(this._onPowerSlotOverride.bind(this)); .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.actor)); html.find(".effect-control").click((ev) => onManageActiveEffect(ev, this.actor));
} }
// Owner Only Listeners // Owner Only Listeners
if ( this.actor.isOwner ) { if (this.actor.isOwner) {
// 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
@ -480,8 +480,8 @@ export default class ActorSheet5e extends ActorSheet {
_initializeFilterItemList(i, ul) { _initializeFilterItemList(i, ul) {
const set = this._filters[ul.dataset.filter]; const set = this._filters[ul.dataset.filter];
const filters = ul.querySelectorAll(".filter-item"); const filters = ul.querySelectorAll(".filter-item");
for ( let li of filters ) { for (let li of filters) {
if ( set.has(li.dataset.filter) ) li.classList.add("active"); if (set.has(li.dataset.filter)) li.classList.add("active");
} }
} }
@ -497,10 +497,10 @@ export default class ActorSheet5e extends ActorSheet {
_onChangeInputDelta(event) { _onChangeInputDelta(event) {
const input = event.target; const input = event.target;
const value = input.value; const value = input.value;
if ( ["+", "-"].includes(value[0]) ) { if (["+", "-"].includes(value[0])) {
let delta = parseFloat(value); let delta = parseFloat(value);
input.value = getProperty(this.actor.data, input.name) + delta; input.value = getProperty(this.actor.data, input.name) + delta;
} else if ( value[0] === "=" ) { } else if (value[0] === "=") {
input.value = value.slice(1); input.value = value.slice(1);
} }
} }
@ -516,7 +516,7 @@ export default class ActorSheet5e extends ActorSheet {
event.preventDefault(); event.preventDefault();
const button = event.currentTarget; const button = event.currentTarget;
let app; let app;
switch ( button.dataset.action ) { switch (button.dataset.action) {
case "hit-dice": case "hit-dice":
app = new ActorHitDiceConfig(this.object); app = new ActorHitDiceConfig(this.object);
break; break;
@ -553,10 +553,10 @@ export default class ActorSheet5e extends ActorSheet {
let idx = levels.indexOf(level); let idx = levels.indexOf(level);
// 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
@ -567,49 +567,51 @@ export default class ActorSheet5e extends ActorSheet {
/** @override */ /** @override */
async _onDropActor(event, data) { async _onDropActor(event, data) {
const canPolymorph = game.user.isGM || (this.actor.isOwner && game.settings.get('sw5e', 'allowPolymorphing')); const canPolymorph = game.user.isGM || (this.actor.isOwner && game.settings.get("sw5e", "allowPolymorphing"));
if ( !canPolymorph ) return false; if (!canPolymorph) return false;
// Get the target actor // Get the target actor
let sourceActor = null; let sourceActor = null;
if (data.pack) { if (data.pack) {
const pack = game.packs.find(p => p.collection === data.pack); const pack = game.packs.find((p) => p.collection === data.pack);
sourceActor = await pack.getEntity(data.id); sourceActor = await pack.getEntity(data.id);
} else { } else {
sourceActor = game.actors.get(data.id); sourceActor = game.actors.get(data.id);
} }
if ( !sourceActor ) return; if (!sourceActor) return;
// 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,
@ -620,61 +622,64 @@ 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) {
// Check to make sure items of this type are allowed on this actor // Check to make sure items of this type are allowed on this actor
if ( this.constructor.unsupportedItemTypes.has(itemData.type) ) { if (this.constructor.unsupportedItemTypes.has(itemData.type)) {
return ui.notifications.warn(game.i18n.format("SW5E.ActorWarningInvalidItem", { return ui.notifications.warn(
game.i18n.format("SW5E.ActorWarningInvalidItem", {
itemType: game.i18n.localize(CONFIG.Item.typeLabels[itemData.type]), itemType: game.i18n.localize(CONFIG.Item.typeLabels[itemData.type]),
actorType: game.i18n.localize(CONFIG.Actor.typeLabels[this.actor.type]) actorType: game.i18n.localize(CONFIG.Actor.typeLabels[this.actor.type])
})); })
);
} }
// Create a Consumable power scroll on the Inventory tab // Create a Consumable power scroll on the Inventory tab
// TODO: This is pretty non functional as the base items for the scrolls, and the powers, are not defined, maybe consider using holocrons // TODO: This is pretty non functional as the base items for the scrolls, and the powers, are not defined, maybe consider using holocrons
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;
} }
if ( itemData.data ) { if (itemData.data) {
// Ignore certain statuses // Ignore certain statuses
["equipped", "proficient", "prepared"].forEach(k => delete itemData.data[k]); ["equipped", "proficient", "prepared"].forEach((k) => delete itemData.data[k]);
// Downgrade ATTUNED to REQUIRED // Downgrade ATTUNED to REQUIRED
itemData.data.attunement = Math.min(itemData.data.attunement, CONFIG.SW5E.attunementTypes.REQUIRED); itemData.data.attunement = Math.min(itemData.data.attunement, CONFIG.SW5E.attunementTypes.REQUIRED);
} }
// Stack identical consumables // Stack identical consumables
if ( itemData.type === "consumable" && itemData.flags.core?.sourceId ) { if (itemData.type === "consumable" && itemData.flags.core?.sourceId) {
const similarItem = this.actor.items.find(i => { const similarItem = this.actor.items.find((i) => {
const sourceId = i.getFlag("core", "sourceId"); const sourceId = i.getFlag("core", "sourceId");
return sourceId && (sourceId === itemData.flags.core?.sourceId) && return sourceId && sourceId === itemData.flags.core?.sourceId && i.type === "consumable";
(i.type === "consumable");
}); });
if ( similarItem ) { if (similarItem) {
return similarItem.update({ return similarItem.update({
'data.quantity': similarItem.data.data.quantity + Math.max(itemData.data.quantity, 1) "data.quantity": similarItem.data.data.quantity + Math.max(itemData.data.quantity, 1)
}); });
} }
} }
@ -690,7 +695,7 @@ export default class ActorSheet5e extends ActorSheet {
* @param {MouseEvent} event The originating click event * @param {MouseEvent} event The originating click event
* @private * @private
*/ */
async _onPowerSlotOverride (event) { async _onPowerSlotOverride(event) {
const span = event.currentTarget.parentElement; const span = event.currentTarget.parentElement;
const level = span.dataset.level; const level = span.dataset.level;
const override = this.actor.data.data.powers[level].override || span.dataset.slots; const override = this.actor.data.data.powers[level].override || span.dataset.slots;
@ -720,7 +725,7 @@ export default class ActorSheet5e extends ActorSheet {
const item = this.actor.items.get(itemId); const item = this.actor.items.get(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});
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -748,7 +753,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.items.get(itemId); const item = this.actor.items.get(itemId);
return item.rollRecharge(); return item.rollRecharge();
}; }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -763,13 +768,13 @@ export default class ActorSheet5e extends ActorSheet {
chatData = item.getChatData({secrets: this.actor.isOwner}); chatData = item.getChatData({secrets: this.actor.isOwner});
// Toggle summary // Toggle summary
if ( li.hasClass("expanded") ) { if (li.hasClass("expanded")) {
let summary = li.children(".item-summary"); let summary = li.children(".item-summary");
summary.slideUp(200, () => summary.remove()); summary.slideUp(200, () => summary.remove());
} else { } else {
let div = $(`<div class="item-summary">${chatData.description.value}</div>`); let div = $(`<div class="item-summary">${chatData.description.value}</div>`);
let props = $(`<div class="item-properties"></div>`); let props = $(`<div class="item-properties"></div>`);
chatData.properties.forEach(p => props.append(`<span class="tag">${p}</span>`)); chatData.properties.forEach((p) => props.append(`<span class="tag">${p}</span>`));
div.append(props); div.append(props);
li.append(div.hide()); li.append(div.hide());
div.slideDown(200); div.slideDown(200);
@ -822,7 +827,7 @@ export default class ActorSheet5e extends ActorSheet {
event.preventDefault(); event.preventDefault();
const li = event.currentTarget.closest(".item"); const li = event.currentTarget.closest(".item");
const item = this.actor.items.get(li.dataset.itemId); const item = this.actor.items.get(li.dataset.itemId);
if ( item ) return item.delete(); if (item) return item.delete();
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -876,7 +881,7 @@ export default class ActorSheet5e extends ActorSheet {
const li = event.currentTarget; const li = event.currentTarget;
const set = this._filters[li.parentElement.dataset.filter]; const set = this._filters[li.parentElement.dataset.filter];
const filter = li.dataset.filter; const filter = li.dataset.filter;
if ( set.has(filter) ) set.delete(filter); if (set.has(filter)) set.delete(filter);
else set.add(filter); else set.add(filter);
return this.render(); return this.render();
} }
@ -893,8 +898,8 @@ export default class ActorSheet5e extends ActorSheet {
const a = event.currentTarget; const a = event.currentTarget;
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};
return new TraitSelector(this.actor, options).render(true) return new TraitSelector(this.actor, options).render(true);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -902,9 +907,9 @@ export default class ActorSheet5e extends ActorSheet {
/** @override */ /** @override */
_getHeaderButtons() { _getHeaderButtons() {
let buttons = super._getHeaderButtons(); let buttons = super._getHeaderButtons();
if ( this.actor.isPolymorphed ) { if (this.actor.isPolymorphed) {
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: () => this.actor.revertOriginalForm() onclick: () => 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}
@ -37,7 +36,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
sheetData["resources"] = ["primary", "secondary", "tertiary"].reduce((arr, r) => { sheetData["resources"] = ["primary", "secondary", "tertiary"].reduce((arr, r) => {
const res = sheetData.data.resources[r] || {}; const res = sheetData.data.resources[r] || {};
res.name = r; res.name = r;
res.placeholder = game.i18n.localize("SW5E.Resource"+r.titleCase()); res.placeholder = game.i18n.localize("SW5E.Resource" + r.titleCase());
if (res && res.value === 0) delete res.value; if (res && res.value === 0) delete res.value;
if (res && res.max === 0) delete res.max; if (res && res.max === 0) delete res.max;
return arr.concat([res]); return arr.concat([res]);
@ -45,10 +44,12 @@ 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,23 +62,34 @@ 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"}},
equipment: { label: "SW5E.ItemTypeEquipmentPl", items: [], dataset: {type: "equipment"} }, equipment: {label: "SW5E.ItemTypeEquipmentPl", items: [], dataset: {type: "equipment"}},
consumable: { label: "SW5E.ItemTypeConsumablePl", items: [], dataset: {type: "consumable"} }, consumable: {label: "SW5E.ItemTypeConsumablePl", items: [], dataset: {type: "consumable"}},
tool: { label: "SW5E.ItemTypeToolPl", items: [], dataset: {type: "tool"} }, tool: {label: "SW5E.ItemTypeToolPl", items: [], dataset: {type: "tool"}},
backpack: { label: "SW5E.ItemTypeContainerPl", items: [], dataset: {type: "backpack"} }, backpack: {label: "SW5E.ItemTypeContainerPl", items: [], dataset: {type: "backpack"}},
loot: { label: "SW5E.ItemTypeLootPl", items: [], dataset: {type: "loot"} } loot: {label: "SW5E.ItemTypeLootPl", items: [], dataset: {type: "loot"}}
}; };
// 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 || CONST.DEFAULT_TOKEN; item.img = item.img || CONST.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,31 +104,35 @@ 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.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0)); item.data.recharge && !!item.data.recharge.value && item.data.recharge.charged === false;
item.hasTarget = !!item.data.target && !(["none",""].includes(item.data.target.type)); 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 toggle state // Item toggle state
this._prepareItemToggleState(item); this._prepareItemToggleState(item);
// Primary Class // Primary Class
if ( item.type === "class" ) item.isOriginalClass = ( item._id === this.actor.data.data.details.originalClass ); if (item.type === "class")
item.isOriginalClass = item._id === this.actor.data.data.details.originalClass;
// Classify items into types // Classify items into types
if ( item.type === "power" ) arr[1].push(item); if (item.type === "power") arr[1].push(item);
else if ( item.type === "feat" ) arr[2].push(item); else if (item.type === "feat") arr[2].push(item);
else if ( item.type === "class" ) arr[3].push(item); else if (item.type === "class") arr[3].push(item);
else if ( item.type === "species" ) arr[4].push(item); else if (item.type === "species") arr[4].push(item);
else if ( item.type === "archetype" ) arr[5].push(item); else if (item.type === "archetype") arr[5].push(item);
else if ( item.type === "classfeature" ) arr[6].push(item); else if (item.type === "classfeature") arr[6].push(item);
else if ( item.type === "background" ) arr[7].push(item); else if (item.type === "background") arr[7].push(item);
else if ( item.type === "fightingstyle" ) arr[8].push(item); else if (item.type === "fightingstyle") arr[8].push(item);
else if ( item.type === "fightingmastery" ) arr[9].push(item); else if (item.type === "fightingmastery") arr[9].push(item);
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);
@ -124,7 +140,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
feats = this._filterItems(feats, this._filters.features); feats = this._filterItems(feats, this._filters.features);
// Organize items // Organize items
for ( let i of items ) { for (let i of items) {
i.data.quantity = i.data.quantity || 0; i.data.quantity = i.data.quantity || 0;
i.data.weight = i.data.weight || 0; i.data.weight = i.data.weight || 0;
i.totalWeight = (i.data.quantity * i.data.weight).toNearest(0.1); i.totalWeight = (i.data.quantity * i.data.weight).toNearest(0.1);
@ -133,25 +149,78 @@ 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: {
classfeatures: { label: "SW5E.ItemTypeClassFeats", items: [], hasActions: true, dataset: {type: "classfeature"}, isClassfeature: true }, label: "SW5E.ItemTypeClassPl",
archetype: { label: "SW5E.ItemTypeArchetype", items: [], hasActions: false, dataset: {type: "archetype"}, isArchetype: true }, items: [],
species: { label: "SW5E.ItemTypeSpecies", items: [], hasActions: false, dataset: {type: "species"}, isSpecies: true }, hasActions: false,
background: { label: "SW5E.ItemTypeBackground", items: [], hasActions: false, dataset: {type: "background"}, isBackground: true }, dataset: {type: "class"},
fightingstyles: { label: "SW5E.ItemTypeFightingStylePl", items: [], hasActions: false, dataset: {type: "fightingstyle"}, isFightingstyle: true }, isClass: 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 }, classfeatures: {
active: { label: "SW5E.FeatureActive", items: [], hasActions: true, dataset: {type: "feat", "activation.type": "action"} }, label: "SW5E.ItemTypeClassFeats",
passive: { label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"} } items: [],
hasActions: true,
dataset: {type: "classfeature"},
isClassfeature: true
},
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"}}
}; };
for ( let f of feats ) { for (let f of feats) {
if ( f.data.activation.type ) features.active.items.push(f); if (f.data.activation.type) features.active.items.push(f);
else features.passive.items.push(f); else features.passive.items.push(f);
} }
classes.sort((a, b) => b.data.levels - a.data.levels); classes.sort((a, b) => b.data.levels - a.data.levels);
@ -183,12 +252,11 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
const isAlways = getProperty(item.data, "preparation.mode") === "always"; const isAlways = getProperty(item.data, "preparation.mode") === "always";
const isPrepared = getProperty(item.data, "preparation.prepared"); const isPrepared = getProperty(item.data, "preparation.prepared");
item.toggleClass = isPrepared ? "active" : ""; item.toggleClass = isPrepared ? "active" : "";
if ( isAlways ) item.toggleClass = "fixed"; if (isAlways) item.toggleClass = "fixed";
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,14 +273,14 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
*/ */
activateListeners(html) { activateListeners(html) {
super.activateListeners(html); super.activateListeners(html);
if ( !this.isEditable ) return; if (!this.isEditable) 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));
@ -228,7 +296,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
_onSheetAction(event) { _onSheetAction(event) {
event.preventDefault(); event.preventDefault();
const button = event.currentTarget; const button = event.currentTarget;
switch( button.dataset.action ) { switch (button.dataset.action) {
case "rollDeathSave": case "rollDeathSave":
return this.actor.rollDeathSave({event: event}); return this.actor.rollDeathSave({event: event});
case "rollInitiative": case "rollInitiative":
@ -281,14 +349,13 @@ 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);
let priorLevel = cls?.data.data.levels ?? 0; let priorLevel = cls?.data.data.levels ?? 0;
if ( !!cls ) { if (!!cls) {
const next = Math.min(priorLevel + 1, 20 + priorLevel - this.actor.data.data.details.level); const next = Math.min(priorLevel + 1, 20 + priorLevel - this.actor.data.data.details.level);
if ( next > priorLevel ) { if (next > priorLevel) {
itemData.levels = next; itemData.levels = next;
return cls.update({"data.levels": next}); return cls.update({"data.levels": next});
} }

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, {
@ -28,27 +27,40 @@ 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"),
passive: { label: game.i18n.localize("SW5E.Features"), items: [], dataset: {type: "feat"} }, items: [],
equipment: { label: game.i18n.localize("SW5E.Inventory"), items: [], dataset: {type: "loot"}} 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"}},
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 || CONST.DEFAULT_TOKEN; item.img = item.img || CONST.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.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0)); item.data.recharge && !!item.data.recharge.value && item.data.recharge.charged === false;
item.hasTarget = !!item.data.target && !(["none",""].includes(item.data.target.type)); item.isDepleted = item.isOnCooldown && item.data.uses.per && item.data.uses.value > 0;
if ( item.type === "power" ) arr[0].push(item); item.hasTarget = !!item.data.target && !["none", ""].includes(item.data.target.type);
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,13 +70,12 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
const powerbook = this._preparePowerbook(data, powers); const powerbook = this._preparePowerbook(data, powers);
// Organize Features // Organize Features
for ( let item of other ) { for (let item of other) {
if ( item.type === "weapon" ) features.weapons.items.push(item); if (item.type === "weapon") features.weapons.items.push(item);
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
@ -72,7 +83,6 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
data.powerbook = powerbook; data.powerbook = powerbook;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @inheritdoc */ /** @inheritdoc */
@ -95,13 +105,12 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
/** @override */ /** @override */
async _updateObject(event, formData) { async _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";
let cr = formData[crv]; let cr = formData[crv];
cr = crs[cr] || parseFloat(cr); cr = crs[cr] || parseFloat(cr);
if ( cr ) formData[crv] = cr < 1 ? cr : parseInt(cr); if (cr) formData[crv] = cr < 1 ? cr : parseInt(cr);
// Parent ActorSheet update steps // Parent ActorSheet update steps
return super._updateObject(event, formData); return super._updateObject(event, formData);
@ -127,7 +136,7 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
_onRollHPFormula(event) { _onRollHPFormula(event) {
event.preventDefault(); event.preventDefault();
const formula = this.actor.data.data.attributes.hp.formula; const formula = this.actor.data.data.attributes.hp.formula;
if ( !formula ) return; if (!formula) return;
const hp = new Roll(formula).roll().total; const hp = new Roll(formula).roll().total;
AudioHelper.play({src: CONFIG.sounds.dice}); AudioHelper.play({src: CONFIG.sounds.dice});
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,13 +25,12 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**
* Creates a new cargo entry for a vehicle Actor. * Creates a new cargo entry for a vehicle Actor.
*/ */
static get newCargo() { static get newCargo() {
return { return {
name: '', name: "",
quantity: 1 quantity: 1
}; };
} }
@ -46,7 +45,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;
@ -63,7 +61,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
_getMovementSpeed(actorData, largestPrimary=true) { _getMovementSpeed(actorData, largestPrimary = true) {
return super._getMovementSpeed(actorData, largestPrimary); return super._getMovementSpeed(actorData, largestPrimary);
} }
@ -75,25 +73,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 : "—";
} }
} }
@ -104,111 +101,125 @@ 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"
}
]
} }
}; };
@ -219,14 +230,14 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
// Handle cargo explicitly // Handle cargo explicitly
const isCargo = item.flags.sw5e?.vehicleCargo === true; const isCargo = item.flags.sw5e?.vehicleCargo === true;
if ( isCargo ) { if (isCargo) {
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);
continue; continue;
} }
// Handle non-cargo item types // Handle non-cargo item types
switch ( item.type ) { switch (item.type) {
case "weapon": case "weapon":
features.weapons.items.push(item); features.weapons.items.push(item);
break; break;
@ -234,8 +245,9 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
features.equipment.items.push(item); features.equipment.items.push(item);
break; break;
case "feat": case "feat":
if (!item.data.activation.type || (item.data.activation.type === "none")) features.passive.items.push(item); if (!item.data.activation.type || item.data.activation.type === "none")
else if (item.data.activation.type === 'reaction') features.reactions.items.push(item); features.passive.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);
break; break;
default: default:
@ -259,21 +271,21 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
super.activateListeners(html); super.activateListeners(html);
if (!this.isEditable) return; if (!this.isEditable) 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();
} }
} }
@ -288,9 +300,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 = foundry.utils.deepClone(this.actor.data.data.cargo[property]); const cargo = foundry.utils.deepClone(this.actor.data.data.cargo[property]);
@ -298,10 +310,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
@ -318,14 +330,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});
} }
@ -342,7 +358,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 = foundry.utils.deepClone(this.actor.data.data.cargo[type]); const cargo = foundry.utils.deepClone(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});
@ -360,10 +376,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 = foundry.utils.deepClone(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx); const cargo = foundry.utils.deepClone(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});
} }
@ -376,7 +392,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
/** @override */ /** @override */
async _onDropItemCreate(itemData) { async _onDropItemCreate(itemData) {
const cargoTypes = ["weapon", "equipment", "consumable", "tool", "loot", "backpack"]; const cargoTypes = ["weapon", "equipment", "consumable", "tool", "loot", "backpack"];
const isCargo = cargoTypes.includes(itemData.type) && (this._tabs[0].active === "cargo"); const isCargo = cargoTypes.includes(itemData.type) && this._tabs[0].active === "cargo";
foundry.utils.setProperty(itemData, "flags.sw5e.vehicleCargo", isCargo); foundry.utils.setProperty(itemData, "flags.sw5e.vehicleCargo", isCargo);
return super._onDropItemCreate(itemData); return super._onDropItemCreate(itemData);
} }
@ -391,11 +407,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});
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -408,9 +424,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 @@
* @type {Dialog} * @type {Dialog}
*/ */
export default class AbilityUseDialog extends Dialog { export default class AbilityUseDialog extends Dialog {
constructor(item, dialogData={}, options={}) { constructor(item, dialogData = {}, options = {}) {
super(dialogData, options); super(dialogData, options);
this.options.classes = ["sw5e", "dialog"]; this.options.classes = ["sw5e", "dialog"];
@ -25,7 +25,7 @@ export default class AbilityUseDialog extends Dialog {
* @return {Promise} * @return {Promise}
*/ */
static async create(item) { static async create(item) {
if ( !item.isOwned ) throw new Error("You cannot display an ability usage dialog for an unowned item"); if (!item.isOwned) throw new Error("You cannot display an ability usage dialog for an unowned item");
// Prepare data // Prepare data
const actorData = item.actor.data.data; const actorData = item.actor.data.data;
@ -39,17 +39,20 @@ export default class AbilityUseDialog extends Dialog {
// Prepare dialog form data // Prepare dialog form data
const data = { const data = {
item: item.data, item: item.data,
title: game.i18n.format("SW5E.AbilityUseHint", {type: game.i18n.localize(`SW5E.ItemType${item.type.capitalize()}`), name: item.name}), title: game.i18n.format("SW5E.AbilityUseHint", {
type: game.i18n.localize(`SW5E.ItemType${item.type.capitalize()}`),
name: item.name
}),
note: this._getAbilityUseNote(item.data, uses, recharge), note: this._getAbilityUseNote(item.data, uses, recharge),
consumePowerSlot: false, consumePowerSlot: false,
consumeRecharge: recharges, consumeRecharge: recharges,
consumeResource: !!itemData.consume.target, consumeResource: !!itemData.consume.target,
consumeUses: uses.per && (uses.max > 0), consumeUses: uses.per && uses.max > 0,
canUse: recharges ? recharge.charged : sufficientUses, canUse: recharges ? recharge.charged : sufficientUses,
createTemplate: game.user.can("TEMPLATE_CREATE") && item.hasAreaTarget, createTemplate: game.user.can("TEMPLATE_CREATE") && item.hasAreaTarget,
errors: [] errors: []
}; };
if ( item.data.type === "power" ) this._getPowerData(actorData, itemData, data); if (item.data.type === "power") this._getPowerData(actorData, itemData, data);
// Render the ability usage template // Render the ability usage template
const html = await renderTemplate("systems/sw5e/templates/apps/ability-use.html", data); const html = await renderTemplate("systems/sw5e/templates/apps/ability-use.html", data);
@ -65,7 +68,7 @@ export default class AbilityUseDialog extends Dialog {
use: { use: {
icon: `<i class="fas ${icon}"></i>`, icon: `<i class="fas ${icon}"></i>`,
label: label, label: label,
callback: html => { callback: (html) => {
const fd = new FormDataExtended(html[0].querySelector("form")); const fd = new FormDataExtended(html[0].querySelector("form"));
resolve(fd.toObject()); resolve(fd.toObject());
} }
@ -87,14 +90,13 @@ 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) {
mergeObject(data, { isPower: true, consumePowerSlot }); mergeObject(data, {isPower: true, consumePowerSlot});
return; return;
} }
@ -102,74 +104,78 @@ export default class AbilityUseDialog extends Dialog {
let lmax = 0; let lmax = 0;
let points; let points;
let powerType; let powerType;
switch (itemData.school){ switch (itemData.school) {
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)
powerLevels = Array.fromRange(10).reduce((arr, i) => { .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); }, [])
}else if (powerType === "tech"){ .filter((sl) => sl.level <= lmax);
powerLevels = Array.fromRange(10).reduce((arr, i) => { } else if (powerType === "tech") {
if ( i < lvl ) return arr; powerLevels = Array.fromRange(10)
.reduce((arr, i) => {
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);
if (!canCast)
const canCast = powerLevels.some(l => l.hasSlots); data.errors.push(
if ( !canCast ) data.errors.push(game.i18n.format("SW5E.PowerCastNoSlots", { 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 foundry.utils.mergeObject(data, { isPower: true, consumePowerSlot, powerLevels }); return foundry.utils.mergeObject(data, {isPower: true, consumePowerSlot, powerLevels});
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -179,27 +185,26 @@ 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");
// 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: game.i18n.localize(`SW5E.ItemType${item.type.capitalize()}`), type: game.i18n.localize(`SW5E.ItemType${item.type.capitalize()}`)
}) });
} }
// Does not use any resource // Does not use any resource
if ( !uses.per || !uses.max ) return ""; if (!uses.per || !uses.max) return "";
// Consumables // Consumables
if ( item.type === "consumable" ) { if (item.type === "consumable") {
let str = "SW5E.AbilityUseNormalHint"; let str = "SW5E.AbilityUseNormalHint";
if ( uses.value > 1 ) str = "SW5E.AbilityUseConsumableChargeHint"; if (uses.value > 1) str = "SW5E.AbilityUseConsumableChargeHint";
else if ( item.data.quantity === 1 && uses.autoDestroy ) str = "SW5E.AbilityUseConsumableDestroyHint"; else if (item.data.quantity === 1 && uses.autoDestroy) str = "SW5E.AbilityUseConsumableDestroyHint";
else if ( item.data.quantity > 1 ) str = "SW5E.AbilityUseConsumableQuantityHint"; else if (item.data.quantity > 1) str = "SW5E.AbilityUseConsumableQuantityHint";
return game.i18n.format(str, { return game.i18n.format(str, {
type: game.i18n.localize(`SW5E.Consumable${item.data.consumableType.capitalize()}`), type: game.i18n.localize(`SW5E.Consumable${item.data.consumableType.capitalize()}`),
value: uses.value, value: uses.value,

View file

@ -17,7 +17,7 @@ export default class ActorSheetFlags extends DocumentSheet {
/** @override */ /** @override */
get title() { get title() {
return `${game.i18n.localize('SW5E.FlagsTitle')}: ${this.object.name}`; return `${game.i18n.localize("SW5E.FlagsTitle")}: ${this.object.name}`;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -40,8 +40,10 @@ export default class ActorSheetFlags extends DocumentSheet {
* @private * @private
*/ */
_getClasses() { _getClasses() {
const classes = this.object.items.filter(i => i.type === "class"); const classes = this.object.items.filter((i) => i.type === "class");
return classes.sort((a, b) => a.name.localeCompare(b.name)).reduce((obj, i) => { return classes
.sort((a, b) => a.name.localeCompare(b.name))
.reduce((obj, i) => {
obj[i.id] = i.name; obj[i.id] = i.name;
return obj; return obj;
}, {}); }, {});
@ -58,12 +60,12 @@ export default class ActorSheetFlags extends DocumentSheet {
_getFlags() { _getFlags() {
const flags = {}; const flags = {};
const baseData = this.document.toJSON(); const baseData = this.document.toJSON();
for ( let [k, v] of Object.entries(CONFIG.SW5E.characterFlags) ) { for (let [k, v] of Object.entries(CONFIG.SW5E.characterFlags)) {
if ( !flags.hasOwnProperty(v.section) ) flags[v.section] = {}; if (!flags.hasOwnProperty(v.section)) flags[v.section] = {};
let flag = foundry.utils.deepClone(v); let flag = foundry.utils.deepClone(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;
} }
@ -96,7 +98,7 @@ export default class ActorSheetFlags extends DocumentSheet {
{name: "data.bonuses.power.forceUnivDC", label: "SW5E.BonusForceUnivPowerDC"}, {name: "data.bonuses.power.forceUnivDC", label: "SW5E.BonusForceUnivPowerDC"},
{name: "data.bonuses.power.techDC", label: "SW5E.BonusTechPowerDC"} {name: "data.bonuses.power.techDC", label: "SW5E.BonusTechPowerDC"}
]; ];
for ( let b of bonuses ) { for (let b of bonuses) {
b.value = getProperty(this.object._data, b.name) || ""; b.value = getProperty(this.object._data, b.name) || "";
} }
return bonuses; return bonuses;
@ -113,11 +115,11 @@ export default class ActorSheetFlags extends DocumentSheet {
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];
if ( hasProperty(actor._data.flags, `sw5e.${k}`) ) { if (hasProperty(actor._data.flags, `sw5e.${k}`)) {
unset = true; unset = true;
flags[`-=${k}`] = null; flags[`-=${k}`] = null;
} }
@ -125,8 +127,8 @@ export default class ActorSheetFlags extends DocumentSheet {
} }
// Clear any bonuses which are whitespace only // Clear any bonuses which are whitespace only
for ( let b of Object.values(updateData.data.bonuses ) ) { for (let b of Object.values(updateData.data.bonuses)) {
for ( let [k, v] of Object.entries(b) ) { for (let [k, v] of Object.entries(b)) {
b[k] = v.trim(); b[k] = v.trim();
} }
} }

View file

@ -5,7 +5,6 @@ import Actor5e from "../actor/entity.js";
* @extends {FormApplication} * @extends {FormApplication}
*/ */
export default class ActorTypeConfig extends FormApplication { export default class ActorTypeConfig extends FormApplication {
/** @inheritdoc */ /** @inheritdoc */
static get defaultOptions() { static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, { return foundry.utils.mergeObject(super.defaultOptions, {
@ -32,11 +31,11 @@ export default class ActorTypeConfig extends FormApplication {
/** @override */ /** @override */
getData(options) { getData(options) {
// Get current value or new default // Get current value or new default
let attr = foundry.utils.getProperty(this.object.data.data, 'details.type'); let attr = foundry.utils.getProperty(this.object.data.data, "details.type");
if ( foundry.utils.getType(attr) !== "Object" ) attr = { if (foundry.utils.getType(attr) !== "Object")
value: (attr in CONFIG.SW5E.creatureTypes) ? attr : "humanoid", attr = {
value: attr in CONFIG.SW5E.creatureTypes ? attr : "humanoid",
subtype: "", subtype: "",
swarm: "", swarm: "",
custom: "" custom: ""
@ -44,11 +43,11 @@ export default class ActorTypeConfig extends FormApplication {
// Populate choices // Populate choices
const types = {}; const types = {};
for ( let [k, v] of Object.entries(CONFIG.SW5E.creatureTypes) ) { for (let [k, v] of Object.entries(CONFIG.SW5E.creatureTypes)) {
types[k] = { types[k] = {
label: game.i18n.localize(v), label: game.i18n.localize(v),
chosen: attr.value === k chosen: attr.value === k
} };
} }
// Return data for rendering // Return data for rendering
@ -61,12 +60,14 @@ export default class ActorTypeConfig extends FormApplication {
}, },
subtype: attr.subtype, subtype: attr.subtype,
swarm: attr.swarm, swarm: attr.swarm,
sizes: Array.from(Object.entries(CONFIG.SW5E.actorSizes)).reverse().reduce((obj, e) => { sizes: Array.from(Object.entries(CONFIG.SW5E.actorSizes))
.reverse()
.reduce((obj, e) => {
obj[e[0]] = e[1]; obj[e[0]] = e[1];
return obj; return obj;
}, {}), }, {}),
preview: Actor5e.formatCreatureType(attr) || "" preview: Actor5e.formatCreatureType(attr) || ""
} };
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -74,7 +75,7 @@ export default class ActorTypeConfig extends FormApplication {
/** @override */ /** @override */
async _updateObject(event, formData) { async _updateObject(event, formData) {
const typeObject = foundry.utils.expandObject(formData); const typeObject = foundry.utils.expandObject(formData);
return this.object.update({ 'data.details.type': typeObject }); return this.object.update({"data.details.type": typeObject});
} }
/* -------------------------------------------- */ /* -------------------------------------------- */

View file

@ -3,7 +3,6 @@
* @implements {DocumentSheet} * @implements {DocumentSheet}
*/ */
export default class ActorHitDiceConfig extends DocumentSheet { export default class ActorHitDiceConfig extends DocumentSheet {
/** @override */ /** @override */
static get defaultOptions() { static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, { return foundry.utils.mergeObject(super.defaultOptions, {
@ -26,7 +25,8 @@ export default class ActorHitDiceConfig extends DocumentSheet {
/** @override */ /** @override */
getData(options) { getData(options) {
return { return {
classes: this.object.items.reduce((classes, item) => { classes: this.object.items
.reduce((classes, item) => {
if (item.data.type === "class") { if (item.data.type === "class") {
// Add the appropriate data only if this item is a "class" // Add the appropriate data only if this item is a "class"
classes.push({ classes.push({
@ -35,11 +35,12 @@ export default class ActorHitDiceConfig extends DocumentSheet {
diceDenom: item.data.data.hitDice, diceDenom: item.data.data.hitDice,
currentHitDice: item.data.data.levels - item.data.data.hitDiceUsed, currentHitDice: item.data.data.levels - item.data.data.hitDiceUsed,
maxHitDice: item.data.data.levels, maxHitDice: item.data.data.levels,
canRoll: (item.data.data.levels - item.data.data.hitDiceUsed) > 0 canRoll: item.data.data.levels - item.data.data.hitDiceUsed > 0
}); });
} }
return classes; return classes;
}, []).sort((a, b) => parseInt(b.diceDenom.slice(1)) - parseInt(a.diceDenom.slice(1))) }, [])
.sort((a, b) => parseInt(b.diceDenom.slice(1)) - parseInt(a.diceDenom.slice(1)))
}; };
} }
@ -50,7 +51,7 @@ export default class ActorHitDiceConfig extends DocumentSheet {
super.activateListeners(html); super.activateListeners(html);
// Hook up -/+ buttons to adjust the current value in the form // Hook up -/+ buttons to adjust the current value in the form
html.find("button.increment,button.decrement").click(event => { html.find("button.increment,button.decrement").click((event) => {
const button = event.currentTarget; const button = event.currentTarget;
const current = button.parentElement.querySelector(".current"); const current = button.parentElement.querySelector(".current");
const max = button.parentElement.querySelector(".max"); const max = button.parentElement.querySelector(".max");
@ -67,8 +68,8 @@ export default class ActorHitDiceConfig extends DocumentSheet {
async _updateObject(event, formData) { async _updateObject(event, formData) {
const actorItems = this.object.items; const actorItems = this.object.items;
const classUpdates = Object.entries(formData).map(([id, hd]) => ({ const classUpdates = Object.entries(formData).map(([id, hd]) => ({
_id: id, "_id": id,
"data.hitDiceUsed": actorItems.get(id).data.data.levels - hd, "data.hitDiceUsed": actorItems.get(id).data.data.levels - hd
})); }));
return this.object.updateEmbeddedDocuments("Item", classUpdates); return this.object.updateEmbeddedDocuments("Item", classUpdates);
} }

View file

@ -37,7 +37,7 @@ export default class LongRestDialog extends Dialog {
* @param {Actor5e} actor * @param {Actor5e} actor
* @return {Promise} * @return {Promise}
*/ */
static async longRestDialog({ actor } = {}) { static async longRestDialog({actor} = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const dlg = new this(actor, { const dlg = new this(actor, {
title: game.i18n.localize("SW5E.LongRest"), title: game.i18n.localize("SW5E.LongRest"),
@ -45,7 +45,7 @@ export default class LongRestDialog extends Dialog {
rest: { rest: {
icon: '<i class="fas fa-bed"></i>', icon: '<i class="fas fa-bed"></i>',
label: game.i18n.localize("SW5E.Rest"), label: game.i18n.localize("SW5E.Rest"),
callback: html => { callback: (html) => {
let newDay = true; let newDay = true;
if (game.settings.get("sw5e", "restVariant") !== "gritty") if (game.settings.get("sw5e", "restVariant") !== "gritty")
newDay = html.find('input[name="newDay"]')[0].checked; newDay = html.find('input[name="newDay"]')[0].checked;
@ -58,7 +58,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 @@
* @extends {DocumentSheet} * @extends {DocumentSheet}
*/ */
export default class ActorMovementConfig extends DocumentSheet { export default class ActorMovementConfig extends DocumentSheet {
/** @override */ /** @override */
static get defaultOptions() { static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, { return foundry.utils.mergeObject(super.defaultOptions, {
@ -30,8 +29,8 @@ export default class ActorMovementConfig extends DocumentSheet {
movement: foundry.utils.deepClone(sourceMovement), movement: foundry.utils.deepClone(sourceMovement),
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;
} }
return data; return data;

View file

@ -3,7 +3,7 @@
* @type {Dialog} * @type {Dialog}
*/ */
export default class SelectItemsPrompt extends Dialog { export default class SelectItemsPrompt extends Dialog {
constructor(items, dialogData={}, options={}) { constructor(items, dialogData = {}, options = {}) {
super(dialogData, options); super(dialogData, options);
this.options.classes = ["sw5e", "dialog", "select-items-prompt", "sheet"]; this.options.classes = ["sw5e", "dialog", "select-items-prompt", "sheet"];
@ -18,11 +18,11 @@ export default class SelectItemsPrompt extends Dialog {
super.activateListeners(html); super.activateListeners(html);
// render the item's sheet if its image is clicked // render the item's sheet if its image is clicked
html.on('click', '.item-image', (event) => { html.on("click", ".item-image", (event) => {
const item = this.items.find((feature) => feature.id === event.currentTarget.dataset?.itemId); const item = this.items.find((feature) => feature.id === event.currentTarget.dataset?.itemId);
item?.sheet.render(true); item?.sheet.render(true);
}) });
} }
/** /**
@ -33,29 +33,27 @@ export default class SelectItemsPrompt extends Dialog {
* @param {string} options.hint - Localized hint to display at the top of the prompt * @param {string} options.hint - Localized hint to display at the top of the prompt
* @return {Promise<string[]>} - list of item ids which the user has selected * @return {Promise<string[]>} - list of item ids which the user has selected
*/ */
static async create(items, { static async create(items, {hint}) {
hint
}) {
// Render the ability usage template // Render the ability usage template
const html = await renderTemplate("systems/sw5e/templates/apps/select-items-prompt.html", {items, hint}); const html = await renderTemplate("systems/sw5e/templates/apps/select-items-prompt.html", {items, hint});
return new Promise((resolve) => { return new Promise((resolve) => {
const dlg = new this(items, { const dlg = new this(items, {
title: game.i18n.localize('SW5E.SelectItemsPromptTitle'), title: game.i18n.localize("SW5E.SelectItemsPromptTitle"),
content: html, content: html,
buttons: { buttons: {
apply: { apply: {
icon: `<i class="fas fa-user-plus"></i>`, icon: `<i class="fas fa-user-plus"></i>`,
label: game.i18n.localize('SW5E.Apply'), label: game.i18n.localize("SW5E.Apply"),
callback: html => { callback: (html) => {
const fd = new FormDataExtended(html[0].querySelector("form")).toObject(); const fd = new FormDataExtended(html[0].querySelector("form")).toObject();
const selectedIds = Object.keys(fd).filter(itemId => fd[itemId]); const selectedIds = Object.keys(fd).filter((itemId) => fd[itemId]);
resolve(selectedIds); resolve(selectedIds);
} }
}, },
cancel: { cancel: {
icon: '<i class="fas fa-forward"></i>', icon: '<i class="fas fa-forward"></i>',
label: game.i18n.localize('SW5E.Skip'), label: game.i18n.localize("SW5E.Skip"),
callback: () => resolve([]) callback: () => resolve([])
} }
}, },

View file

@ -3,7 +3,6 @@
* @extends {DocumentSheet} * @extends {DocumentSheet}
*/ */
export default class ActorSensesConfig extends DocumentSheet { export default class ActorSensesConfig extends DocumentSheet {
/** @inheritdoc */ /** @inheritdoc */
static get defaultOptions() { static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, { return foundry.utils.mergeObject(super.defaultOptions, {
@ -29,14 +28,15 @@ export default class ActorSensesConfig extends DocumentSheet {
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

@ -5,7 +5,7 @@ import LongRestDialog from "./long-rest.js";
* @extends {Dialog} * @extends {Dialog}
*/ */
export default class ShortRestDialog extends Dialog { export default class ShortRestDialog extends Dialog {
constructor(actor, dialogData={}, options={}) { constructor(actor, dialogData = {}, options = {}) {
super(dialogData, options); super(dialogData, options);
/** /**
@ -39,7 +39,7 @@ export default class ShortRestDialog extends Dialog {
// Determine Hit Dice // Determine Hit Dice
data.availableHD = this.actor.data.items.reduce((hd, item) => { data.availableHD = this.actor.data.items.reduce((hd, item) => {
if ( item.type === "class" ) { if (item.type === "class") {
const d = item.data.data; const d = item.data.data;
const denom = d.hitDice || "d6"; const denom = d.hitDice || "d6";
const available = parseInt(d.levels || 1) - parseInt(d.hitDiceUsed || 0); const available = parseInt(d.levels || 1) - parseInt(d.hitDiceUsed || 0);
@ -59,7 +59,6 @@ export default class ShortRestDialog extends Dialog {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
activateListeners(html) { activateListeners(html) {
super.activateListeners(html); super.activateListeners(html);
@ -90,7 +89,7 @@ export default class ShortRestDialog extends Dialog {
* @param {Actor5e} actor * @param {Actor5e} actor
* @return {Promise} * @return {Promise}
*/ */
static async shortRestDialog({actor}={}) { static async shortRestDialog({actor} = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const dlg = new this(actor, { const dlg = new this(actor, {
title: game.i18n.localize("SW5E.ShortRest"), title: game.i18n.localize("SW5E.ShortRest"),
@ -98,7 +97,7 @@ export default class ShortRestDialog extends Dialog {
rest: { rest: {
icon: '<i class="fas fa-bed"></i>', icon: '<i class="fas fa-bed"></i>',
label: game.i18n.localize("SW5E.Rest"), label: game.i18n.localize("SW5E.Rest"),
callback: html => { callback: (html) => {
let newDay = false; let newDay = false;
if (game.settings.get("sw5e", "restVariant") === "gritty") if (game.settings.get("sw5e", "restVariant") === "gritty")
newDay = html.find('input[name="newDay"]')[0].checked; newDay = html.find('input[name="newDay"]')[0].checked;
@ -126,8 +125,10 @@ export default class ShortRestDialog extends Dialog {
* @param {Actor5e} actor * @param {Actor5e} actor
* @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 @@
* @extends {DocumentSheet} * @extends {DocumentSheet}
*/ */
export default class TraitSelector extends DocumentSheet { export default class TraitSelector extends DocumentSheet {
/** @inheritdoc */ /** @inheritdoc */
static get defaultOptions() { static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, { return foundry.utils.mergeObject(super.defaultOptions, {
@ -38,22 +37,22 @@ export default class TraitSelector extends DocumentSheet {
getData() { getData() {
const attr = foundry.utils.getProperty(this.object.data, this.attribute); const attr = foundry.utils.getProperty(this.object.data, this.attribute);
const o = this.options; const o = this.options;
const value = (o.valueKey) ? attr[o.valueKey] ?? [] : attr; const value = o.valueKey ? attr[o.valueKey] ?? [] : attr;
const custom = (o.customKey) ? attr[o.customKey] ?? "" : ""; const custom = o.customKey ? attr[o.customKey] ?? "" : "";
// Populate choices // Populate choices
const choices = Object.entries(o.choices).reduce((obj, e) => { const choices = Object.entries(o.choices).reduce((obj, e) => {
let [k, v] = e; let [k, v] = e;
obj[k] = { label: v, chosen: attr ? value.includes(k) : false }; obj[k] = {label: v, chosen: attr ? value.includes(k) : false};
return obj; return obj;
}, {}) }, {});
// Return data // Return data
return { return {
allowCustom: o.allowCustom, allowCustom: o.allowCustom,
choices: choices, choices: choices,
custom: custom custom: custom
} };
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -64,21 +63,21 @@ export default class TraitSelector extends DocumentSheet {
// 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);
} }
// Object including custom data // Object including custom data
const updateData = {}; const updateData = {};
if ( o.valueKey ) updateData[`${this.attribute}.${o.valueKey}`] = chosen; if (o.valueKey) updateData[`${this.attribute}.${o.valueKey}`] = chosen;
else updateData[this.attribute] = chosen; else updateData[this.attribute] = chosen;
if ( o.allowCustom ) updateData[`${this.attribute}.${o.customKey}`] = formData.custom; if (o.allowCustom) updateData[`${this.attribute}.${o.customKey}`] = formData.custom;
// Validate the number chosen // Validate the number chosen
if ( o.minimum && (chosen.length < o.minimum) ) { if (o.minimum && chosen.length < o.minimum) {
return ui.notifications.error(`You must choose at least ${o.minimum} options`); return ui.notifications.error(`You must choose at least ${o.minimum} options`);
} }
if ( o.maximum && (chosen.length > o.maximum) ) { if (o.maximum && chosen.length > o.maximum) {
return ui.notifications.error(`You may choose no more than ${o.maximum} options`); return ui.notifications.error(`You may choose no more than ${o.maximum} options`);
} }

View file

@ -1,6 +1,6 @@
/** @override */ /** @override */
export const measureDistances = function(segments, options={}) { export const measureDistances = function (segments, options = {}) {
if ( !options.gridSpaces ) return BaseGrid.prototype.measureDistances.call(this, segments, options); if (!options.gridSpaces) return BaseGrid.prototype.measureDistances.call(this, segments, options);
// Track the total number of diagonals // Track the total number of diagonals
let nDiagonal = 0; let nDiagonal = 0;
@ -8,7 +8,7 @@ export const measureDistances = function(segments, options={}) {
const d = canvas.dimensions; const d = canvas.dimensions;
// Iterate over measured segments // Iterate over measured segments
return segments.map(s => { return segments.map((s) => {
let r = s.ray; let r = s.ray;
// Determine the total distance traveled // Determine the total distance traveled
@ -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;
} }

View file

@ -151,13 +151,15 @@ export default class CharacterImporter {
result.forEach((prof) => { result.forEach((prof) => {
let assignedProfession = professionsPack.find((o) => o.name === prof.profession); let assignedProfession = professionsPack.find((o) => o.name === prof.profession);
assignedProfession.data.data.levels = prof.level; assignedProfession.data.data.levels = prof.level;
actor.createEmbeddedDocuments("Item", [assignedProfession.data], { displaySheet: false }); actor.createEmbeddedDocuments("Item", [assignedProfession.data], {displaySheet: false});
}); });
this.addSpecies(sourceCharacter.attribs.find((e) => e.name == "race").current, actor); this.addSpecies(sourceCharacter.attribs.find((e) => e.name == "race").current, actor);
this.addPowers( this.addPowers(
sourceCharacter.attribs.filter((e) => e.name.search(/repeating_power.+_powername/g) != -1).map((e) => e.current), sourceCharacter.attribs
.filter((e) => e.name.search(/repeating_power.+_powername/g) != -1)
.map((e) => e.current),
actor actor
); );
@ -180,7 +182,7 @@ export default class CharacterImporter {
let classes = await game.packs.get("sw5e.classes").getDocuments(); let classes = await game.packs.get("sw5e.classes").getDocuments();
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.createEmbeddedDocuments("Item", [assignedClass.data], { displaySheet: false }); await actor.createEmbeddedDocuments("Item", [assignedClass.data], {displaySheet: false});
} }
static classOrMulticlass(name) { static classOrMulticlass(name) {
@ -213,7 +215,7 @@ export default class CharacterImporter {
const species = await game.packs.get("sw5e.species").getDocuments(); const species = await game.packs.get("sw5e.species").getDocuments();
const assignedSpecies = species.find((c) => c.name === race); const assignedSpecies = species.find((c) => c.name === race);
const activeEffects = [...assignedSpecies.data.effects][0].data.changes; const activeEffects = [...assignedSpecies.data.effects][0].data.changes;
const actorData = { data: { abilities: { ...actor.data.data.abilities } } }; const actorData = {data: {abilities: {...actor.data.data.abilities}}};
activeEffects.map((effect) => { activeEffects.map((effect) => {
switch (effect.key) { switch (effect.key) {
@ -248,7 +250,7 @@ export default class CharacterImporter {
actor.update(actorData); actor.update(actorData);
await actor.createEmbeddedDocuments("Item", [assignedSpecies.data], { displaySheet: false }); await actor.createEmbeddedDocuments("Item", [assignedSpecies.data], {displaySheet: false});
} }
static async addPowers(powers, actor) { static async addPowers(powers, actor) {
@ -259,7 +261,7 @@ export default class CharacterImporter {
const createdPower = forcePowers.find((c) => c.name === power) || techPowers.find((c) => c.name === power); const createdPower = forcePowers.find((c) => c.name === power) || techPowers.find((c) => c.name === power);
if (createdPower) { if (createdPower) {
await actor.createEmbeddedDocuments("Item", [createdPower.data], { displaySheet: false }); await actor.createEmbeddedDocuments("Item", [createdPower.data], {displaySheet: false});
} }
} }
} }
@ -280,7 +282,7 @@ export default class CharacterImporter {
createdItem.data.data.quantity = item.quantity; createdItem.data.data.quantity = item.quantity;
} }
await actor.createEmbeddedDocuments("Item", [createdItem.data], { displaySheet: false }); await actor.createEmbeddedDocuments("Item", [createdItem.data], {displaySheet: false});
} }
} }
} }

View file

@ -1,28 +1,27 @@
/** /**
* Highlight critical success or failure on d20 rolls * Highlight critical success or failure on d20 rolls
*/ */
export const highlightCriticalSuccessFailure = function(message, html, data) { export const highlightCriticalSuccessFailure = function (message, html, data) {
if ( !message.isRoll || !message.isContentVisible ) return; if (!message.isRoll || !message.isContentVisible) return;
// Highlight rolls where the first part is a d20 roll // Highlight rolls where the first part is a d20 roll
const roll = message.roll; const roll = message.roll;
if ( !roll.dice.length ) return; if (!roll.dice.length) return;
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
const critical = d.options.critical || 20; const critical = d.options.critical || 20;
const fumble = d.options.fumble || 1; const fumble = d.options.fumble || 1;
if ( d.total >= critical ) html.find(".dice-total").addClass("critical"); if (d.total >= critical) html.find(".dice-total").addClass("critical");
else if ( d.total <= fumble ) html.find(".dice-total").addClass("fumble"); else if (d.total <= fumble) html.find(".dice-total").addClass("fumble");
else if ( d.options.target ) { else if (d.options.target) {
if ( roll.total >= d.options.target ) html.find(".dice-total").addClass("success"); if (roll.total >= d.options.target) html.find(".dice-total").addClass("success");
else html.find(".dice-total").addClass("failure"); else html.find(".dice-total").addClass("failure");
} }
}; };
@ -32,22 +31,22 @@ export const highlightCriticalSuccessFailure = function(message, html, data) {
/** /**
* Optionally hide the display of chat card action buttons which cannot be performed by the user * Optionally hide the display of chat card action buttons which cannot be performed by the user
*/ */
export const displayChatActionButtons = function(message, html, data) { export const displayChatActionButtons = function (message, html, data) {
const chatCard = html.find(".sw5e.chat-card"); const chatCard = html.find(".sw5e.chat-card");
if ( chatCard.length > 0 ) { if (chatCard.length > 0) {
const flavor = html.find(".flavor-text"); const flavor = html.find(".flavor-text");
if ( flavor.text() === html.find(".item-name").text() ) flavor.remove(); if (flavor.text() === html.find(".item-name").text()) flavor.remove();
// 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.isOwner ) return; if (actor && actor.isOwner) 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";
}); });
} }
}; };
@ -63,8 +62,8 @@ export const displayChatActionButtons = function(message, html, data) {
* *
* @return {Array} The extended options Array including new context choices * @return {Array} The extended options Array including new context choices
*/ */
export const addChatMessageContextOptions = function(html, options) { export const addChatMessageContextOptions = function (html, options) {
let canApply = li => { let canApply = (li) => {
const message = game.messages.get(li.data("messageId")); const message = game.messages.get(li.data("messageId"));
return message?.isRoll && message?.isContentVisible && canvas.tokens?.controlled.length; return message?.isRoll && message?.isContentVisible && canvas.tokens?.controlled.length;
}; };
@ -73,25 +72,25 @@ export const addChatMessageContextOptions = function(html, options) {
name: game.i18n.localize("SW5E.ChatContextDamage"), name: game.i18n.localize("SW5E.ChatContextDamage"),
icon: '<i class="fas fa-user-minus"></i>', icon: '<i class="fas fa-user-minus"></i>',
condition: canApply, condition: canApply,
callback: li => applyChatCardDamage(li, 1) callback: (li) => applyChatCardDamage(li, 1)
}, },
{ {
name: game.i18n.localize("SW5E.ChatContextHealing"), name: game.i18n.localize("SW5E.ChatContextHealing"),
icon: '<i class="fas fa-user-plus"></i>', icon: '<i class="fas fa-user-plus"></i>',
condition: canApply, condition: canApply,
callback: li => applyChatCardDamage(li, -1) callback: (li) => applyChatCardDamage(li, -1)
}, },
{ {
name: game.i18n.localize("SW5E.ChatContextDoubleDamage"), name: game.i18n.localize("SW5E.ChatContextDoubleDamage"),
icon: '<i class="fas fa-user-injured"></i>', icon: '<i class="fas fa-user-injured"></i>',
condition: canApply, condition: canApply,
callback: li => applyChatCardDamage(li, 2) callback: (li) => applyChatCardDamage(li, 2)
}, },
{ {
name: game.i18n.localize("SW5E.ChatContextHalfDamage"), name: game.i18n.localize("SW5E.ChatContextHalfDamage"),
icon: '<i class="fas fa-user-shield"></i>', icon: '<i class="fas fa-user-shield"></i>',
condition: canApply, condition: canApply,
callback: li => applyChatCardDamage(li, 0.5) callback: (li) => applyChatCardDamage(li, 0.5)
} }
); );
return options; return options;
@ -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,13 +1,12 @@
/** /**
* 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
* Apply the dexterity score as a decimal tiebreaker if requested * Apply the dexterity score as a decimal tiebreaker if requested
* See Combat._getInitiativeFormula for more detail. * See Combat._getInitiativeFormula for more detail.
*/ */
export const _getInitiativeFormula = function() { export const _getInitiativeFormula = function () {
const actor = this.actor; const actor = this.actor;
if ( !actor ) return "1d20"; if (!actor) return "1d20";
const init = actor.data.data.attributes.init; const init = actor.data.data.attributes.init;
// Construct initiative formula parts // Construct initiative formula parts
@ -18,10 +17,15 @@ export const _getInitiativeFormula = function() {
nd = 2; nd = 2;
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");
if ( tiebreaker ) parts.push(actor.data.data.abilities.dex.value / 100); if (tiebreaker) parts.push(actor.data.data.abilities.dex.value / 100);
return parts.filter(p => p !== null).join(" + "); return parts.filter((p) => p !== null).join(" + ");
}; };

File diff suppressed because it is too large Load diff

View file

@ -23,14 +23,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 (term instanceof OperatorTerm) 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 (term instanceof OperatorTerm) 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.
} // } //
@ -43,9 +48,9 @@ export function simplifyRollFormula(formula, data, {constantFirst = false} = {})
// Mathematically evaluate the constant formula to produce a single constant term // Mathematically evaluate the constant formula to produce a single constant term
let constantPart = undefined; let constantPart = undefined;
if ( constantFormula ) { if (constantFormula) {
try { try {
constantPart = Roll.safeEval(constantFormula) constantPart = Roll.safeEval(constantFormula);
} catch (err) { } catch (err) {
console.warn(`Unable to evaluate constant term ${constantFormula} in simplifyRollFormula`); console.warn(`Unable to evaluate constant term ${constantFormula} in simplifyRollFormula`);
} }
@ -111,17 +116,33 @@ function _isUnsupportedTerm(term) {
* @return {Promise<D20Roll|null>} The evaluated D20Roll, or null if the workflow was cancelled * @return {Promise<D20Roll|null>} The evaluated D20Roll, or null if the workflow was cancelled
*/ */
export async function d20Roll({ export async function d20Roll({
parts=[], data={}, // Roll creation parts = [],
advantage, disadvantage, fumble=1, critical=20, targetValue, elvenAccuracy, halflingLucky, reliableTalent, // Roll customization data = {}, // Roll creation
chooseModifier=false, fastForward=false, event, template, title, dialogOptions, // Dialog configuration advantage,
chatMessage=true, messageData={}, rollMode, speaker, flavor // Chat Message customization disadvantage,
}={}) { fumble = 1,
critical = 20,
targetValue,
elvenAccuracy,
halflingLucky,
reliableTalent, // Roll customization
chooseModifier = false,
fastForward = false,
event,
template,
title,
dialogOptions, // Dialog configuration
chatMessage = true,
messageData = {},
rollMode,
speaker,
flavor // Chat Message customization
} = {}) {
// Handle input arguments // Handle input arguments
const formula = ["1d20"].concat(parts).join(" + "); const formula = ["1d20"].concat(parts).join(" + ");
const {advantageMode, isFF} = _determineAdvantageMode({advantage, disadvantage, fastForward, event}); const {advantageMode, isFF} = _determineAdvantageMode({advantage, disadvantage, fastForward, event});
const defaultRollMode = rollMode || game.settings.get("core", "rollMode"); const defaultRollMode = rollMode || game.settings.get("core", "rollMode");
if ( chooseModifier && !isFF ) data["mod"] = "@mod"; if (chooseModifier && !isFF) data["mod"] = "@mod";
// Construct the D20Roll instance // Construct the D20Roll instance
const roll = new CONFIG.Dice.D20Roll(formula, data, { const roll = new CONFIG.Dice.D20Roll(formula, data, {
@ -137,27 +158,32 @@ export async function d20Roll({
}); });
// Prompt a Dialog to further configure the D20Roll // Prompt a Dialog to further configure the D20Roll
if ( !isFF ) { if (!isFF) {
const configured = await roll.configureDialog({ const configured = await roll.configureDialog(
{
title, title,
chooseModifier, chooseModifier,
defaultRollMode: defaultRollMode, defaultRollMode: defaultRollMode,
defaultAction: advantageMode, defaultAction: advantageMode,
defaultAbility: data?.item?.ability, defaultAbility: data?.item?.ability,
template template
}, dialogOptions); },
if ( configured === null ) return null; dialogOptions
);
if (configured === null) return null;
} }
// Evaluate the configured roll // Evaluate the configured roll
await roll.evaluate({async: true}); await roll.evaluate({async: true});
// Create a Chat Message // Create a Chat Message
if ( speaker ) { if (speaker) {
console.warn(`You are passing the speaker argument to the d20Roll function directly which should instead be passed as an internal key of messageData`); console.warn(
`You are passing the speaker argument to the d20Roll function directly which should instead be passed as an internal key of messageData`
);
messageData.speaker = speaker; messageData.speaker = speaker;
} }
if ( roll && chatMessage ) await roll.toMessage(messageData); if (roll && chatMessage) await roll.toMessage(messageData);
return roll; return roll;
} }
@ -167,11 +193,12 @@ export async function d20Roll({
* Determines whether this d20 roll should be fast-forwarded, and whether advantage or disadvantage should be applied * Determines whether this d20 roll should be fast-forwarded, and whether advantage or disadvantage should be applied
* @returns {{isFF: boolean, advantageMode: number}} Whether the roll is fast-forward, and its advantage mode * @returns {{isFF: boolean, advantageMode: number}} Whether the roll is fast-forward, and its advantage mode
*/ */
function _determineAdvantageMode({event, advantage=false, disadvantage=false, fastForward=false}={}) { function _determineAdvantageMode({event, advantage = false, disadvantage = false, fastForward = false} = {}) {
const isFF = fastForward || (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)); const isFF = fastForward || (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey));
let advantageMode = CONFIG.Dice.D20Roll.ADV_MODE.NORMAL; let advantageMode = CONFIG.Dice.D20Roll.ADV_MODE.NORMAL;
if ( advantage || event?.altKey ) advantageMode = CONFIG.Dice.D20Roll.ADV_MODE.ADVANTAGE; if (advantage || event?.altKey) advantageMode = CONFIG.Dice.D20Roll.ADV_MODE.ADVANTAGE;
else if ( disadvantage || event?.ctrlKey || event?.metaKey ) advantageMode = CONFIG.Dice.D20Roll.ADV_MODE.DISADVANTAGE; else if (disadvantage || event?.ctrlKey || event?.metaKey)
advantageMode = CONFIG.Dice.D20Roll.ADV_MODE.DISADVANTAGE;
return {isFF, advantageMode}; return {isFF, advantageMode};
} }
@ -210,12 +237,25 @@ function _determineAdvantageMode({event, advantage=false, disadvantage=false, fa
* @return {Promise<DamageRoll|null>} The evaluated DamageRoll, or null if the workflow was canceled * @return {Promise<DamageRoll|null>} The evaluated DamageRoll, or null if the workflow was canceled
*/ */
export async function damageRoll({ export async function damageRoll({
parts=[], data, // Roll creation parts = [],
critical=false, criticalBonusDice, criticalMultiplier, multiplyNumeric, powerfulCritical, // Damage customization data, // Roll creation
fastForward=false, event, allowCritical=true, template, title, dialogOptions, // Dialog configuration critical = false,
chatMessage=true, messageData={}, rollMode, speaker, flavor, // Chat Message customization criticalBonusDice,
}={}) { criticalMultiplier,
multiplyNumeric,
powerfulCritical, // Damage customization
fastForward = false,
event,
allowCritical = true,
template,
title,
dialogOptions, // Dialog configuration
chatMessage = true,
messageData = {},
rollMode,
speaker,
flavor // Chat Message customization
} = {}) {
// Handle input arguments // Handle input arguments
const defaultRollMode = rollMode || game.settings.get("core", "rollMode"); const defaultRollMode = rollMode || game.settings.get("core", "rollMode");
@ -232,26 +272,31 @@ export async function damageRoll({
}); });
// Prompt a Dialog to further configure the DamageRoll // Prompt a Dialog to further configure the DamageRoll
if ( !isFF ) { if (!isFF) {
const configured = await roll.configureDialog({ const configured = await roll.configureDialog(
{
title, title,
defaultRollMode: defaultRollMode, defaultRollMode: defaultRollMode,
defaultCritical: isCritical, defaultCritical: isCritical,
template, template,
allowCritical allowCritical
}, dialogOptions); },
if ( configured === null ) return null; dialogOptions
);
if (configured === null) return null;
} }
// Evaluate the configured roll // Evaluate the configured roll
await roll.evaluate({async: true}); await roll.evaluate({async: true});
// Create a Chat Message // Create a Chat Message
if ( speaker ) { if (speaker) {
console.warn(`You are passing the speaker argument to the damageRoll function directly which should instead be passed as an internal key of messageData`); console.warn(
`You are passing the speaker argument to the damageRoll function directly which should instead be passed as an internal key of messageData`
);
messageData.speaker = speaker; messageData.speaker = speaker;
} }
if ( roll && chatMessage ) await roll.toMessage(messageData); if (roll && chatMessage) await roll.toMessage(messageData);
return roll; return roll;
} }
@ -261,8 +306,8 @@ export async function damageRoll({
* Determines whether this d20 roll should be fast-forwarded, and whether advantage or disadvantage should be applied * Determines whether this d20 roll should be fast-forwarded, and whether advantage or disadvantage should be applied
* @returns {{isFF: boolean, isCritical: boolean}} Whether the roll is fast-forward, and whether it is a critical hit * @returns {{isFF: boolean, isCritical: boolean}} Whether the roll is fast-forward, and whether it is a critical hit
*/ */
function _determineCriticalMode({event, critical=false, fastForward=false}={}) { function _determineCriticalMode({event, critical = false, fastForward = false} = {}) {
const isFF = fastForward || (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)); const isFF = fastForward || (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey));
if ( event?.altKey ) critical = true; if (event?.altKey) critical = true;
return {isFF, isCritical: critical}; return {isFF, isCritical: critical};
} }

View file

@ -16,7 +16,7 @@
export default class D20Roll extends Roll { export default class D20Roll extends Roll {
constructor(formula, data, options) { constructor(formula, data, options) {
super(formula, data, options); super(formula, data, options);
if ( !((this.terms[0] instanceof Die) && (this.terms[0].faces === 20)) ) { if (!(this.terms[0] instanceof Die && this.terms[0].faces === 20)) {
throw new Error(`Invalid D20Roll formula provided ${this._formula}`); throw new Error(`Invalid D20Roll formula provided ${this._formula}`);
} }
this.configureModifiers(); this.configureModifiers();
@ -31,8 +31,8 @@ export default class D20Roll extends Roll {
static ADV_MODE = { static ADV_MODE = {
NORMAL: 0, NORMAL: 0,
ADVANTAGE: 1, ADVANTAGE: 1,
DISADVANTAGE: -1, DISADVANTAGE: -1
} };
/** /**
* The HTML template path used to configure evaluation of this Roll * The HTML template path used to configure evaluation of this Roll
@ -71,28 +71,26 @@ export default class D20Roll extends Roll {
d20.modifiers = []; d20.modifiers = [];
// Halfling Lucky // Halfling Lucky
if ( this.options.halflingLucky ) d20.modifiers.push("r1=1"); if (this.options.halflingLucky) d20.modifiers.push("r1=1");
// Reliable Talent // Reliable Talent
if ( this.options.reliableTalent ) d20.modifiers.push("min10"); if (this.options.reliableTalent) d20.modifiers.push("min10");
// Handle Advantage or Disadvantage // Handle Advantage or Disadvantage
if ( this.hasAdvantage ) { if (this.hasAdvantage) {
d20.number = this.options.elvenAccuracy ? 3 : 2; d20.number = this.options.elvenAccuracy ? 3 : 2;
d20.modifiers.push("kh"); d20.modifiers.push("kh");
d20.options.advantage = true; d20.options.advantage = true;
} } else if (this.hasDisadvantage) {
else if ( this.hasDisadvantage ) {
d20.number = 2; d20.number = 2;
d20.modifiers.push("kl"); d20.modifiers.push("kl");
d20.options.disadvantage = true; d20.options.disadvantage = true;
} } else d20.number = 1;
else d20.number = 1;
// Assign critical and fumble thresholds // Assign critical and fumble thresholds
if ( this.options.critical ) d20.options.critical = this.options.critical; if (this.options.critical) d20.options.critical = this.options.critical;
if ( this.options.fumble ) d20.options.fumble = this.options.fumble; if (this.options.fumble) d20.options.fumble = this.options.fumble;
if ( this.options.targetValue ) d20.options.target = this.options.targetValue; if (this.options.targetValue) d20.options.target = this.options.targetValue;
// Re-compile the underlying formula // Re-compile the underlying formula
this._formula = this.constructor.getFormula(this.terms); this._formula = this.constructor.getFormula(this.terms);
@ -101,22 +99,21 @@ export default class D20Roll extends Roll {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @inheritdoc */ /** @inheritdoc */
async toMessage(messageData={}, options={}) { async toMessage(messageData = {}, options = {}) {
// Evaluate the roll now so we have the results available to determine whether reliable talent came into play // Evaluate the roll now so we have the results available to determine whether reliable talent came into play
if ( !this._evaluated ) await this.evaluate({async: true}); if (!this._evaluated) await this.evaluate({async: true});
// Add appropriate advantage mode message flavor and sw5e roll flags // Add appropriate advantage mode message flavor and sw5e roll flags
messageData.flavor = messageData.flavor || this.options.flavor; messageData.flavor = messageData.flavor || this.options.flavor;
if ( this.hasAdvantage ) messageData.flavor += ` (${game.i18n.localize("SW5E.Advantage")})`; if (this.hasAdvantage) messageData.flavor += ` (${game.i18n.localize("SW5E.Advantage")})`;
else if ( this.hasDisadvantage ) messageData.flavor += ` (${game.i18n.localize("SW5E.Disadvantage")})`; else if (this.hasDisadvantage) messageData.flavor += ` (${game.i18n.localize("SW5E.Disadvantage")})`;
// Add reliable talent to the d20-term flavor text if it applied // Add reliable talent to the d20-term flavor text if it applied
if ( this.options.reliableTalent ) { if (this.options.reliableTalent) {
const d20 = this.dice[0]; const d20 = this.dice[0];
const isRT = d20.results.every(r => !r.active || (r.result < 10)); const isRT = d20.results.every((r) => !r.active || r.result < 10);
const label = `(${game.i18n.localize("SW5E.FlagsReliableTalent")})`; const label = `(${game.i18n.localize("SW5E.FlagsReliableTalent")})`;
if ( isRT ) d20.options.flavor = d20.options.flavor ? `${d20.options.flavor} (${label})` : label; if (isRT) d20.options.flavor = d20.options.flavor ? `${d20.options.flavor} (${label})` : label;
} }
// Record the preferred rollMode // Record the preferred rollMode
@ -140,8 +137,17 @@ export default class D20Roll extends Roll {
* @param {object} options Additional Dialog customization options * @param {object} options Additional Dialog customization options
* @returns {Promise<D20Roll|null>} A resulting D20Roll object constructed with the dialog, or null if the dialog was closed * @returns {Promise<D20Roll|null>} A resulting D20Roll object constructed with the dialog, or null if the dialog was closed
*/ */
async configureDialog({title, defaultRollMode, defaultAction=D20Roll.ADV_MODE.NORMAL, chooseModifier=false, defaultAbility, template}={}, options={}) { async configureDialog(
{
title,
defaultRollMode,
defaultAction = D20Roll.ADV_MODE.NORMAL,
chooseModifier = false,
defaultAbility,
template
} = {},
options = {}
) {
// Render the Dialog inner HTML // Render the Dialog inner HTML
const content = await renderTemplate(template ?? this.constructor.EVALUATION_TEMPLATE, { const content = await renderTemplate(template ?? this.constructor.EVALUATION_TEMPLATE, {
formula: `${this.formula} + @bonus`, formula: `${this.formula} + @bonus`,
@ -154,32 +160,39 @@ export default class D20Roll extends Roll {
let defaultButton = "normal"; let defaultButton = "normal";
switch (defaultAction) { switch (defaultAction) {
case D20Roll.ADV_MODE.ADVANTAGE: defaultButton = "advantage"; break; case D20Roll.ADV_MODE.ADVANTAGE:
case D20Roll.ADV_MODE.DISADVANTAGE: defaultButton = "disadvantage"; break; defaultButton = "advantage";
break;
case D20Roll.ADV_MODE.DISADVANTAGE:
defaultButton = "disadvantage";
break;
} }
// Create the Dialog window and await submission of the form // Create the Dialog window and await submission of the form
return new Promise(resolve => { return new Promise((resolve) => {
new Dialog({ new Dialog(
{
title, title,
content, content,
buttons: { buttons: {
advantage: { advantage: {
label: game.i18n.localize("SW5E.Advantage"), label: game.i18n.localize("SW5E.Advantage"),
callback: html => resolve(this._onDialogSubmit(html, D20Roll.ADV_MODE.ADVANTAGE)) callback: (html) => resolve(this._onDialogSubmit(html, D20Roll.ADV_MODE.ADVANTAGE))
}, },
normal: { normal: {
label: game.i18n.localize("SW5E.Normal"), label: game.i18n.localize("SW5E.Normal"),
callback: html => resolve(this._onDialogSubmit(html, D20Roll.ADV_MODE.NORMAL)) callback: (html) => resolve(this._onDialogSubmit(html, D20Roll.ADV_MODE.NORMAL))
}, },
disadvantage: { disadvantage: {
label: game.i18n.localize("SW5E.Disadvantage"), label: game.i18n.localize("SW5E.Disadvantage"),
callback: html => resolve(this._onDialogSubmit(html, D20Roll.ADV_MODE.DISADVANTAGE)) callback: (html) => resolve(this._onDialogSubmit(html, D20Roll.ADV_MODE.DISADVANTAGE))
} }
}, },
default: defaultButton, default: defaultButton,
close: () => resolve(null) close: () => resolve(null)
}, options).render(true); },
options
).render(true);
}); });
} }
@ -195,16 +208,16 @@ export default class D20Roll extends Roll {
const form = html[0].querySelector("form"); const form = html[0].querySelector("form");
// Append a situational bonus term // Append a situational bonus term
if ( form.bonus.value ) { if (form.bonus.value) {
const bonus = new Roll(form.bonus.value, this.data); const bonus = new Roll(form.bonus.value, this.data);
if ( !(bonus.terms[0] instanceof OperatorTerm) ) this.terms.push(new OperatorTerm({operator: "+"})); if (!(bonus.terms[0] instanceof OperatorTerm)) this.terms.push(new OperatorTerm({operator: "+"}));
this.terms = this.terms.concat(bonus.terms); this.terms = this.terms.concat(bonus.terms);
} }
// Customize the modifier // Customize the modifier
if ( form.ability?.value ) { if (form.ability?.value) {
const abl = this.data.abilities[form.ability.value]; const abl = this.data.abilities[form.ability.value];
this.terms.findSplice(t => t.term === "@mod", new NumericTerm({number: abl.mod})); this.terms.findSplice((t) => t.term === "@mod", new NumericTerm({number: abl.mod}));
this.options.flavor += ` (${CONFIG.SW5E.abilities[form.ability.value]})`; this.options.flavor += ` (${CONFIG.SW5E.abilities[form.ability.value]})`;
} }

View file

@ -13,7 +13,7 @@ export default class DamageRoll extends Roll {
constructor(formula, data, options) { constructor(formula, data, options) {
super(formula, data, options); super(formula, data, options);
// For backwards compatibility, skip rolls which do not have the "critical" option defined // For backwards compatibility, skip rolls which do not have the "critical" option defined
if ( this.options.critical !== undefined ) this.configureDamage(); if (this.options.critical !== undefined) this.configureDamage();
} }
/** /**
@ -42,44 +42,44 @@ export default class DamageRoll extends Roll {
*/ */
configureDamage() { configureDamage() {
let flatBonus = 0; let flatBonus = 0;
for ( let [i, term] of this.terms.entries() ) { for (let [i, term] of this.terms.entries()) {
// Multiply dice terms // Multiply dice terms
if ( term instanceof DiceTerm ) { if (term instanceof DiceTerm) {
term.options.baseNumber = term.options.baseNumber ?? term.number; // Reset back term.options.baseNumber = term.options.baseNumber ?? term.number; // Reset back
term.number = term.options.baseNumber; term.number = term.options.baseNumber;
if ( this.isCritical ) { if (this.isCritical) {
let cm = this.options.criticalMultiplier ?? 2; let cm = this.options.criticalMultiplier ?? 2;
// Powerful critical - maximize damage and reduce the multiplier by 1 // Powerful critical - maximize damage and reduce the multiplier by 1
if ( this.options.powerfulCritical ) { if (this.options.powerfulCritical) {
flatBonus += (term.number * term.faces); flatBonus += term.number * term.faces;
cm = Math.max(1, cm-1); cm = Math.max(1, cm - 1);
} }
// Alter the damage term // Alter the damage term
let cb = (this.options.criticalBonusDice && (i === 0)) ? this.options.criticalBonusDice : 0; let cb = this.options.criticalBonusDice && i === 0 ? this.options.criticalBonusDice : 0;
term.alter(cm, cb); term.alter(cm, cb);
term.options.critical = true; term.options.critical = true;
} }
} }
// Multiply numeric terms // Multiply numeric terms
else if ( this.options.multiplyNumeric && (term instanceof NumericTerm) ) { else if (this.options.multiplyNumeric && term instanceof NumericTerm) {
term.options.baseNumber = term.options.baseNumber ?? term.number; // Reset back term.options.baseNumber = term.options.baseNumber ?? term.number; // Reset back
term.number = term.options.baseNumber; term.number = term.options.baseNumber;
if ( this.isCritical ) { if (this.isCritical) {
term.number *= (this.options.criticalMultiplier ?? 2); term.number *= this.options.criticalMultiplier ?? 2;
term.options.critical = true; term.options.critical = true;
} }
} }
} }
// Add powerful critical bonus // Add powerful critical bonus
if ( this.options.powerfulCritical && (flatBonus > 0) ) { if (this.options.powerfulCritical && flatBonus > 0) {
this.terms.push(new OperatorTerm({operator: "+"})); this.terms.push(new OperatorTerm({operator: "+"}));
this.terms.push(new NumericTerm({number: flatBonus}, {flavor: game.i18n.localize("SW5E.PowerfulCritical")})); this.terms.push(
new NumericTerm({number: flatBonus}, {flavor: game.i18n.localize("SW5E.PowerfulCritical")})
);
} }
// Re-compile the underlying formula // Re-compile the underlying formula
@ -89,9 +89,9 @@ export default class DamageRoll extends Roll {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @inheritdoc */ /** @inheritdoc */
toMessage(messageData={}, options={}) { toMessage(messageData = {}, options = {}) {
messageData.flavor = messageData.flavor || this.options.flavor; messageData.flavor = messageData.flavor || this.options.flavor;
if ( this.isCritical ) { if (this.isCritical) {
const label = game.i18n.localize("SW5E.CriticalHit"); const label = game.i18n.localize("SW5E.CriticalHit");
messageData.flavor = messageData.flavor ? `${messageData.flavor} (${label})` : label; messageData.flavor = messageData.flavor ? `${messageData.flavor} (${label})` : label;
} }
@ -114,34 +114,39 @@ export default class DamageRoll extends Roll {
* @param {object} options Additional Dialog customization options * @param {object} options Additional Dialog customization options
* @returns {Promise<D20Roll|null>} A resulting D20Roll object constructed with the dialog, or null if the dialog was closed * @returns {Promise<D20Roll|null>} A resulting D20Roll object constructed with the dialog, or null if the dialog was closed
*/ */
async configureDialog({title, defaultRollMode, defaultCritical=false, template, allowCritical=true}={}, options={}) { async configureDialog(
{title, defaultRollMode, defaultCritical = false, template, allowCritical = true} = {},
options = {}
) {
// Render the Dialog inner HTML // Render the Dialog inner HTML
const content = await renderTemplate(template ?? this.constructor.EVALUATION_TEMPLATE, { const content = await renderTemplate(template ?? this.constructor.EVALUATION_TEMPLATE, {
formula: `${this.formula} + @bonus`, formula: `${this.formula} + @bonus`,
defaultRollMode, defaultRollMode,
rollModes: CONFIG.Dice.rollModes, rollModes: CONFIG.Dice.rollModes
}); });
// Create the Dialog window and await submission of the form // Create the Dialog window and await submission of the form
return new Promise(resolve => { return new Promise((resolve) => {
new Dialog({ new Dialog(
{
title, title,
content, content,
buttons: { buttons: {
critical: { critical: {
condition: allowCritical, condition: allowCritical,
label: game.i18n.localize("SW5E.CriticalHit"), label: game.i18n.localize("SW5E.CriticalHit"),
callback: html => resolve(this._onDialogSubmit(html, true)) callback: (html) => resolve(this._onDialogSubmit(html, true))
}, },
normal: { normal: {
label: game.i18n.localize(allowCritical ? "SW5E.Normal" : "SW5E.Roll"), label: game.i18n.localize(allowCritical ? "SW5E.Normal" : "SW5E.Roll"),
callback: html => resolve(this._onDialogSubmit(html, false)) callback: (html) => resolve(this._onDialogSubmit(html, false))
} }
}, },
default: defaultCritical ? "critical" : "normal", default: defaultCritical ? "critical" : "normal",
close: () => resolve(null) close: () => resolve(null)
}, options).render(true); },
options
).render(true);
}); });
} }
@ -157,9 +162,9 @@ export default class DamageRoll extends Roll {
const form = html[0].querySelector("form"); const form = html[0].querySelector("form");
// Append a situational bonus term // Append a situational bonus term
if ( form.bonus.value ) { if (form.bonus.value) {
const bonus = new Roll(form.bonus.value, this.data); const bonus = new Roll(form.bonus.value, this.data);
if ( !(bonus.terms[0] instanceof OperatorTerm) ) this.terms.push(new OperatorTerm({operator: "+"})); if (!(bonus.terms[0] instanceof OperatorTerm)) this.terms.push(new OperatorTerm({operator: "+"}));
this.terms = this.terms.concat(bonus.terms); this.terms = this.terms.concat(bonus.terms);
} }

23
module/effects.js vendored
View file

@ -8,15 +8,17 @@ export function onManageActiveEffect(event, owner) {
const a = event.currentTarget; const a = event.currentTarget;
const li = a.closest("li"); const li = a.closest("li");
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 owner.createEmbeddedDocuments("ActiveEffect", [{ return owner.createEmbeddedDocuments("ActiveEffect", [
label: game.i18n.localize("SW5E.EffectNew"), {
icon: "icons/svg/aura.svg", "label": game.i18n.localize("SW5E.EffectNew"),
origin: owner.uuid, "icon": "icons/svg/aura.svg",
"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"
}]); }
]);
case "edit": case "edit":
return effect.sheet.render(true); return effect.sheet.render(true);
case "delete": case "delete":
@ -32,7 +34,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: {
@ -53,10 +54,10 @@ export function prepareActiveEffectCategories(effects) {
}; };
// Iterate over active effects, classifying them into categories // Iterate over active effects, classifying them into categories
for ( let e of effects ) { for (let e of effects) {
e._getSourceName(); // Trigger a lookup for the source name e._getSourceName(); // Trigger a lookup for the source name
if ( e.data.disabled ) categories.inactive.effects.push(e); if (e.data.disabled) categories.inactive.effects.push(e);
else if ( e.isTemporary ) categories.temporary.effects.push(e); else if (e.isTemporary) categories.temporary.effects.push(e);
else categories.passive.effects.push(e); else categories.passive.effects.push(e);
} }
return categories; return categories;

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
import TraitSelector from "../apps/trait-selector.js"; import TraitSelector from "../apps/trait-selector.js";
import { onManageActiveEffect, prepareActiveEffectCategories } from "../effects.js"; import {onManageActiveEffect, prepareActiveEffectCategories} from "../effects.js";
/** /**
* Override and extend the core ItemSheet implementation to handle specific item types * Override and extend the core ItemSheet implementation to handle specific item types
@ -26,7 +26,7 @@ export default class ItemSheet5e extends ItemSheet {
classes: ["sw5e", "sheet", "item"], classes: ["sw5e", "sheet", "item"],
resizable: true, resizable: true,
scrollY: [".tab.details"], scrollY: [".tab.details"],
tabs: [{ navSelector: ".tabs", contentSelector: ".sheet-body", initial: "description" }] tabs: [{navSelector: ".tabs", contentSelector: ".sheet-body", initial: "description"}]
}); });
} }
@ -64,7 +64,7 @@ export default class ItemSheet5e extends ItemSheet {
// Original maximum uses formula // Original maximum uses formula
const sourceMax = foundry.utils.getProperty(this.item.data._source, "data.uses.max"); const sourceMax = foundry.utils.getProperty(this.item.data._source, "data.uses.max");
if ( sourceMax ) itemData.data.uses.max = sourceMax; if (sourceMax) itemData.data.uses.max = sourceMax;
// Vehicles // Vehicles
data.isCrewed = itemData.data.activation?.type === "crew"; data.isCrewed = itemData.data.activation?.type === "crew";
@ -102,14 +102,14 @@ export default class ItemSheet5e extends ItemSheet {
} }
return ammo; return ammo;
}, },
{ [item._id]: `${item.name} (${item.data.quantity})` } {[item._id]: `${item.name} (${item.data.quantity})`}
); );
} }
// Attributes // Attributes
else if (consume.type === "attribute") { else if (consume.type === "attribute") {
const attributes = TokenDocument.getTrackedAttributes(actor.data.data); const attributes = TokenDocument.getTrackedAttributes(actor.data.data);
attributes.bar.forEach(a => a.push("value")); attributes.bar.forEach((a) => a.push("value"));
return attributes.bar.concat(attributes.value).reduce((obj, a) => { return attributes.bar.concat(attributes.value).reduce((obj, a) => {
let k = a.join("."); let k = a.join(".");
obj[k] = k; obj[k] = k;
@ -135,8 +135,11 @@ export default class ItemSheet5e extends ItemSheet {
if (uses.per && uses.max) { if (uses.per && uses.max) {
const label = const label =
uses.per === "charges" uses.per === "charges"
? ` (${game.i18n.format("SW5E.AbilityUseChargesLabel", { value: uses.value })})` ? ` (${game.i18n.format("SW5E.AbilityUseChargesLabel", {value: uses.value})})`
: ` (${game.i18n.format("SW5E.AbilityUseConsumableLabel", { max: uses.max, per: uses.per })})`; : ` (${game.i18n.format("SW5E.AbilityUseConsumableLabel", {
max: uses.max,
per: uses.per
})})`;
obj[i.id] = i.name + label; obj[i.id] = i.name + label;
} }
@ -261,7 +264,7 @@ export default class ItemSheet5e extends ItemSheet {
/** @inheritdoc */ /** @inheritdoc */
_getSubmitData(updateData = {}) { _getSubmitData(updateData = {}) {
// Create the expanded update data object // Create the expanded update data object
const fd = new FormDataExtended(this.form, { editors: this.editors }); const fd = new FormDataExtended(this.form, {editors: this.editors});
let data = fd.toObject(); let data = fd.toObject();
if (updateData) data = mergeObject(data, updateData); if (updateData) data = mergeObject(data, updateData);
else data = expandObject(data); else data = expandObject(data);
@ -283,7 +286,10 @@ export default class ItemSheet5e extends ItemSheet {
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._onConfigureTraits.bind(this)); html.find(".trait-selector.class-skills").click(this._onConfigureTraits.bind(this));
html.find(".effect-control").click((ev) => { html.find(".effect-control").click((ev) => {
if (this.item.isOwned) return ui.notifications.warn("Managing Active Effects within an Owned Item is not currently supported and will be added in a subsequent update."); if (this.item.isOwned)
return ui.notifications.warn(
"Managing Active Effects within an Owned Item is not currently supported and will be added in a subsequent update."
);
onManageActiveEffect(ev, this.item); onManageActiveEffect(ev, this.item);
}); });
} }
@ -305,7 +311,7 @@ export default class ItemSheet5e extends ItemSheet {
if (a.classList.contains("add-damage")) { if (a.classList.contains("add-damage")) {
await this._onSubmit(event); // Submit any unsaved changes await this._onSubmit(event); // Submit any unsaved changes
const damage = this.item.data.data.damage; const damage = this.item.data.data.damage;
return this.item.update({ "data.damage.parts": damage.parts.concat([["", ""]]) }); return this.item.update({"data.damage.parts": damage.parts.concat([["", ""]])});
} }
// Remove a damage component // Remove a damage component
@ -314,7 +320,7 @@ export default class ItemSheet5e extends ItemSheet {
const li = a.closest(".damage-part"); const li = a.closest(".damage-part");
const damage = foundry.utils.deepClone(this.item.data.data.damage); const damage = foundry.utils.deepClone(this.item.data.data.damage);
damage.parts.splice(Number(li.dataset.damagePart), 1); damage.parts.splice(Number(li.dataset.damagePart), 1);
return this.item.update({ "data.damage.parts": damage.parts }); return this.item.update({"data.damage.parts": damage.parts});
} }
} }
@ -336,15 +342,18 @@ export default class ItemSheet5e extends ItemSheet {
allowCustom: false allowCustom: false
}; };
switch(a.dataset.options) { switch (a.dataset.options) {
case 'saves': case "saves":
options.choices = CONFIG.SW5E.abilities; options.choices = CONFIG.SW5E.abilities;
options.valueKey = null; options.valueKey = null;
break; break;
case 'skills': case "skills":
const skills = this.item.data.data.skills; const skills = this.item.data.data.skills;
const choiceSet = skills.choices && skills.choices.length ? skills.choices : Object.keys(CONFIG.SW5E.skills); const choiceSet =
options.choices = Object.fromEntries(Object.entries(CONFIG.SW5E.skills).filter(skill => choiceSet.includes(skill[0]))); skills.choices && skills.choices.length ? skills.choices : Object.keys(CONFIG.SW5E.skills);
options.choices = Object.fromEntries(
Object.entries(CONFIG.SW5E.skills).filter((skill) => choiceSet.includes(skill[0]))
);
options.maximum = skills.number; options.maximum = skills.number;
break; break;
} }

View file

@ -1,4 +1,3 @@
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Hotbar Macros */ /* Hotbar Macros */
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -11,14 +10,14 @@
* @returns {Promise} * @returns {Promise}
*/ */
export async function create5eMacro(data, slot) { export async function create5eMacro(data, slot) {
if ( data.type !== "Item" ) return; if (data.type !== "Item") return;
if (!( "data" in data ) ) return ui.notifications.warn("You can only create macro buttons for owned Items"); if (!("data" in data)) return ui.notifications.warn("You can only create macro buttons for owned Items");
const item = data.data; const item = data.data;
// 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,
type: "script", type: "script",
@ -42,14 +41,16 @@ export async function create5eMacro(data, slot) {
export function rollItemMacro(itemName) { export function rollItemMacro(itemName) {
const speaker = ChatMessage.getSpeaker(); const speaker = ChatMessage.getSpeaker();
let actor; let actor;
if ( speaker.token ) actor = game.actors.tokens[speaker.token]; if (speaker.token) actor = game.actors.tokens[speaker.token];
if ( !actor ) actor = game.actors.get(speaker.actor); if (!actor) actor = game.actors.get(speaker.actor);
// 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(
} else if ( items.length === 0 ) { `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) {
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}`);
} }
const item = items[0]; const item = items[0];

View file

@ -2,59 +2,62 @@
* Perform a system migration for the entire World, applying migrations for Actors, Items, and Compendium packs * Perform a system migration for the entire World, applying migrations for Actors, Items, and Compendium packs
* @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.contents ) { for await (let a of game.actors.contents) {
try { try {
console.log(`Checking Actor entity ${a.name} for migration needs`); console.log(`Checking Actor entity ${a.name} for migration needs`);
const updateData = await migrateActorData(a.data); const updateData = await migrateActorData(a.data);
if ( !foundry.utils.isObjectEmpty(updateData) ) { if (!foundry.utils.isObjectEmpty(updateData)) {
console.log(`Migrating Actor entity ${a.name}`); console.log(`Migrating Actor entity ${a.name}`);
await a.update(updateData, {enforceTypes: false}); await a.update(updateData, {enforceTypes: false});
} }
} catch(err) { } catch (err) {
err.message = `Failed sw5e system migration for Actor ${a.name}: ${err.message}`; err.message = `Failed sw5e system migration for Actor ${a.name}: ${err.message}`;
console.error(err); console.error(err);
} }
} }
// Migrate World Items // Migrate World Items
for ( let i of game.items.contents ) { for (let i of game.items.contents) {
try { try {
const updateData = migrateItemData(i.toObject()); const updateData = migrateItemData(i.toObject());
if ( !foundry.utils.isObjectEmpty(updateData) ) { if (!foundry.utils.isObjectEmpty(updateData)) {
console.log(`Migrating Item entity ${i.name}`); console.log(`Migrating Item entity ${i.name}`);
await i.update(updateData, {enforceTypes: false}); await i.update(updateData, {enforceTypes: false});
} }
} catch(err) { } catch (err) {
err.message = `Failed sw5e system migration for Item ${i.name}: ${err.message}`; err.message = `Failed sw5e system migration for Item ${i.name}: ${err.message}`;
console.error(err); console.error(err);
} }
} }
// Migrate Actor Override Tokens // Migrate Actor Override Tokens
for ( let s of game.scenes.contents ) { for (let s of game.scenes.contents) {
try { try {
const updateData = await migrateSceneData(s.data); const updateData = await migrateSceneData(s.data);
if ( !foundry.utils.isObjectEmpty(updateData) ) { if (!foundry.utils.isObjectEmpty(updateData)) {
console.log(`Migrating Scene entity ${s.name}`); console.log(`Migrating Scene entity ${s.name}`);
await s.update(updateData, {enforceTypes: false}); await s.update(updateData, {enforceTypes: false});
// If we do not do this, then synthetic token actors remain in cache // If we do not do this, then synthetic token actors remain in cache
// with the un-updated actorData. // with the un-updated actorData.
s.tokens.contents.forEach(t => t._actor = null); s.tokens.contents.forEach((t) => (t._actor = null));
} }
} catch(err) { } catch (err) {
err.message = `Failed sw5e system migration for Scene ${s.name}: ${err.message}`; err.message = `Failed sw5e system migration for Scene ${s.name}: ${err.message}`;
console.error(err); console.error(err);
} }
} }
// Migrate World Compendium Packs // Migrate World Compendium Packs
for ( let p of game.packs ) { for (let p of game.packs) {
if ( p.metadata.package !== "world" ) continue; if (p.metadata.package !== "world") continue;
if ( !["Actor", "Item", "Scene"].includes(p.metadata.entity) ) continue; if (!["Actor", "Item", "Scene"].includes(p.metadata.entity)) continue;
await migrateCompendium(p); await migrateCompendium(p);
} }
@ -70,9 +73,9 @@ export const migrateWorld = async function() {
* @param pack * @param pack
* @return {Promise} * @return {Promise}
*/ */
export const migrateCompendium = async function(pack) { export const migrateCompendium = async function (pack) {
const entity = pack.metadata.entity; const entity = pack.metadata.entity;
if ( !["Actor", "Item", "Scene"].includes(entity) ) return; if (!["Actor", "Item", "Scene"].includes(entity)) return;
// Unlock the pack for editing // Unlock the pack for editing
const wasLocked = pack.locked; const wasLocked = pack.locked;
@ -83,7 +86,7 @@ export const migrateCompendium = async function(pack) {
const documents = await pack.getDocuments(); const documents = await pack.getDocuments();
// Iterate over compendium entries - applying fine-tuned migration functions // Iterate over compendium entries - applying fine-tuned migration functions
for await ( let doc of documents ) { for await (let doc of documents) {
let updateData = {}; let updateData = {};
try { try {
switch (entity) { switch (entity) {
@ -97,15 +100,13 @@ export const migrateCompendium = async function(pack) {
updateData = await migrateSceneData(doc.data); updateData = await migrateSceneData(doc.data);
break; break;
} }
if ( foundry.utils.isObjectEmpty(updateData) ) continue; if (foundry.utils.isObjectEmpty(updateData)) continue;
// Save the entry, if data was changed // Save the entry, if data was changed
await doc.update(updateData); await doc.update(updateData);
console.log(`Migrated ${entity} entity ${doc.name} in Compendium ${pack.collection}`); console.log(`Migrated ${entity} entity ${doc.name} in Compendium ${pack.collection}`);
} } catch (err) {
// Handle migration failures // Handle migration failures
catch(err) {
err.message = `Failed sw5e system migration for entity ${doc.name} in pack ${pack.collection}: ${err.message}`; err.message = `Failed sw5e system migration for entity ${doc.name} in pack ${pack.collection}: ${err.message}`;
console.error(err); console.error(err);
} }
@ -126,18 +127,18 @@ export const migrateCompendium = async function(pack) {
* @param {object} actor The actor data object to update * @param {object} actor The actor data object to update
* @return {Object} The updateData to apply * @return {Object} The updateData to apply
*/ */
export const migrateActorData = async function(actor) { export const migrateActorData = async function (actor) {
const updateData = {}; const updateData = {};
// Actor Data Updates // Actor Data Updates
if(actor.data) { if (actor.data) {
_migrateActorMovement(actor, updateData); _migrateActorMovement(actor, updateData);
_migrateActorSenses(actor, updateData); _migrateActorSenses(actor, updateData);
_migrateActorType(actor, updateData); _migrateActorType(actor, updateData);
} }
// Migrate Owned Items // Migrate Owned Items
if ( !!actor.items ) { if (!!actor.items) {
const items = await actor.items.reduce(async (memo, i) => { const items = await actor.items.reduce(async (memo, i) => {
const results = await memo; const results = await memo;
@ -146,14 +147,15 @@ export const migrateActorData = async function(actor) {
let itemUpdate = await migrateActorItemData(itemData, actor); let itemUpdate = await migrateActorItemData(itemData, actor);
// Prepared, Equipped, and Proficient for NPC actors // Prepared, Equipped, and Proficient for NPC actors
if ( actor.type === "npc" ) { if (actor.type === "npc") {
if (getProperty(itemData.data, "preparation.prepared") === false) itemUpdate["data.preparation.prepared"] = true; if (getProperty(itemData.data, "preparation.prepared") === false)
itemUpdate["data.preparation.prepared"] = true;
if (getProperty(itemData.data, "equipped") === false) itemUpdate["data.equipped"] = true; if (getProperty(itemData.data, "equipped") === false) itemUpdate["data.equipped"] = true;
if (getProperty(itemData.data, "proficient") === false) itemUpdate["data.proficient"] = true; if (getProperty(itemData.data, "proficient") === false) itemUpdate["data.proficient"] = true;
} }
// Update the Owned Item // Update the Owned Item
if ( !isObjectEmpty(itemUpdate) ) { if (!isObjectEmpty(itemUpdate)) {
itemUpdate._id = itemData._id; itemUpdate._id = itemData._id;
console.log(`Migrating Actor ${actor.name}'s ${i.name}`); console.log(`Migrating Actor ${actor.name}'s ${i.name}`);
results.push(expandObject(itemUpdate)); results.push(expandObject(itemUpdate));
@ -162,7 +164,7 @@ export const migrateActorData = async function(actor) {
return results; return results;
}, []); }, []);
if ( items.length > 0 ) updateData.items = items; if (items.length > 0) updateData.items = items;
} }
// Update NPC data with new datamodel information // Update NPC data with new datamodel information
@ -178,14 +180,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);
@ -195,7 +195,7 @@ function cleanActorData(actorData) {
obj[f] = null; obj[f] = null;
return obj; return obj;
}, {}); }, {});
if ( actorData.flags.sw5e ) { if (actorData.flags.sw5e) {
actorData.flags.sw5e = filterObject(actorData.flags.sw5e, allowedFlags); actorData.flags.sw5e = filterObject(actorData.flags.sw5e, allowedFlags);
} }
@ -203,7 +203,6 @@ function cleanActorData(actorData) {
return actorData; return actorData;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**
@ -212,7 +211,7 @@ function cleanActorData(actorData) {
* @param {object} item Item data to migrate * @param {object} item Item data to migrate
* @return {object} The updateData to apply * @return {object} The updateData to apply
*/ */
export const migrateItemData = function(item) { export const migrateItemData = function (item) {
const updateData = {}; const updateData = {};
_migrateItemClassPowerCasting(item, updateData); _migrateItemClassPowerCasting(item, updateData);
_migrateItemAttunement(item, updateData); _migrateItemAttunement(item, updateData);
@ -226,7 +225,7 @@ export const migrateItemData = function(item) {
* @param item * @param item
* @param actor * @param actor
*/ */
export const migrateActorItemData = async function(item, actor) { export const migrateActorItemData = async function (item, actor) {
const updateData = {}; const updateData = {};
_migrateItemClassPowerCasting(item, updateData); _migrateItemClassPowerCasting(item, updateData);
_migrateItemAttunement(item, updateData); _migrateItemAttunement(item, updateData);
@ -242,23 +241,23 @@ export const migrateActorItemData = async function(item, actor) {
* @param {Object} scene The Scene data to Update * @param {Object} scene The Scene data to Update
* @return {Object} The updateData to apply * @return {Object} The updateData to apply
*/ */
export const migrateSceneData = async function(scene) { export const migrateSceneData = async function (scene) {
const tokens = await Promise.all(scene.tokens.map(async token => { const tokens = await Promise.all(
scene.tokens.map(async (token) => {
const t = token.toJSON(); const t = token.toJSON();
if (!t.actorId || t.actorLink) { if (!t.actorId || t.actorLink) {
t.actorData = {}; t.actorData = {};
} } else if (!game.actors.has(t.actorId)) {
else if (!game.actors.has(t.actorId)) {
t.actorId = null; t.actorId = null;
t.actorData = {}; t.actorData = {};
} else if ( !t.actorLink ) { } else if (!t.actorLink) {
const actorData = duplicate(t.actorData); const actorData = duplicate(t.actorData);
actorData.type = token.actor?.type; actorData.type = token.actor?.type;
const update = migrateActorData(actorData); const update = migrateActorData(actorData);
['items', 'effects'].forEach(embeddedName => { ["items", "effects"].forEach((embeddedName) => {
if (!update[embeddedName]?.length) return; if (!update[embeddedName]?.length) return;
const updates = new Map(update[embeddedName].map(u => [u._id, u])); const updates = new Map(update[embeddedName].map((u) => [u._id, u]));
t.actorData[embeddedName].forEach(original => { t.actorData[embeddedName].forEach((original) => {
const update = updates.get(original._id); const update = updates.get(original._id);
if (update) mergeObject(original, update); if (update) mergeObject(original, update);
}); });
@ -268,7 +267,8 @@ export const migrateActorItemData = async function(item, actor) {
mergeObject(t.actorData, update); mergeObject(t.actorData, update);
} }
return t; return t;
})); })
);
return {tokens}; return {tokens};
}; };
@ -284,7 +284,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
@ -292,13 +291,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;
@ -310,11 +316,13 @@ function _updateNPCData(actor) {
updateData["data.details.powerTechLevel"] = monsterData.details.powerTechLevel; updateData["data.details.powerTechLevel"] = monsterData.details.powerTechLevel;
// push missing powers onto actor // push missing powers onto actor
let newPowers = []; let newPowers = [];
for ( let i of monster.items ) { for (let i of monster.items) {
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));
@ -331,17 +339,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
@ -350,21 +356,21 @@ 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;
} }
// Remove the old attribute // Remove the old attribute
updateData["data.attributes.-=speed"] = null; updateData["data.attributes.-=speed"] = null;
} }
return updateData return updateData;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -378,7 +384,7 @@ function _migrateActorPowers(actorData, updateData) {
// If new Force & Tech data is not present, create it // If new Force & Tech data is not present, create it
let hasNewAttrib = ad?.attributes?.force?.level !== undefined; let hasNewAttrib = ad?.attributes?.force?.level !== undefined;
if ( !hasNewAttrib ) { if (!hasNewAttrib) {
updateData["data.attributes.force.known.value"] = 0; updateData["data.attributes.force.known.value"] = 0;
updateData["data.attributes.force.known.max"] = 0; updateData["data.attributes.force.known.max"] = 0;
updateData["data.attributes.force.points.value"] = 0; updateData["data.attributes.force.points.value"] = 0;
@ -399,14 +405,14 @@ function _migrateActorPowers(actorData, updateData) {
// If new Power F/T split data is not present, create it // If new Power F/T split data is not present, create it
const hasNewLimit = ad?.powers?.power1?.foverride !== undefined; const hasNewLimit = ad?.powers?.power1?.foverride !== undefined;
if ( !hasNewLimit ) { if (!hasNewLimit) {
for (let i = 1; i <= 9; i++) { for (let i = 1; i <= 9; i++) {
// add new // add new
updateData["data.powers.power" + i + ".fvalue"] = getProperty(ad.powers,"power" + i + ".value"); updateData["data.powers.power" + i + ".fvalue"] = getProperty(ad.powers, "power" + i + ".value");
updateData["data.powers.power" + i + ".fmax"] = getProperty(ad.powers,"power" + i + ".max"); updateData["data.powers.power" + i + ".fmax"] = getProperty(ad.powers, "power" + i + ".max");
updateData["data.powers.power" + i + ".foverride"] = null; updateData["data.powers.power" + i + ".foverride"] = null;
updateData["data.powers.power" + i + ".tvalue"] = getProperty(ad.powers,"power" + i + ".value"); updateData["data.powers.power" + i + ".tvalue"] = getProperty(ad.powers, "power" + i + ".value");
updateData["data.powers.power" + i + ".tmax"] = getProperty(ad.powers,"power" + i + ".max"); updateData["data.powers.power" + i + ".tmax"] = getProperty(ad.powers, "power" + i + ".max");
updateData["data.powers.power" + i + ".toverride"] = null; updateData["data.powers.power" + i + ".toverride"] = null;
//remove old //remove old
updateData["data.powers.power" + i + ".-=value"] = null; updateData["data.powers.power" + i + ".-=value"] = null;
@ -415,7 +421,7 @@ function _migrateActorPowers(actorData, updateData) {
} }
// If new Bonus Power DC data is not present, create it // If new Bonus Power DC data is not present, create it
const hasNewBonus = ad?.bonuses?.power?.forceLightDC !== undefined; const hasNewBonus = ad?.bonuses?.power?.forceLightDC !== undefined;
if ( !hasNewBonus ) { if (!hasNewBonus) {
updateData["data.bonuses.power.forceLightDC"] = ""; updateData["data.bonuses.power.forceLightDC"] = "";
updateData["data.bonuses.power.forceDarkDC"] = ""; updateData["data.bonuses.power.forceDarkDC"] = "";
updateData["data.bonuses.power.forceUnivDC"] = ""; updateData["data.bonuses.power.forceUnivDC"] = "";
@ -425,7 +431,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;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -436,28 +442,28 @@ function _migrateActorPowers(actorData, updateData) {
*/ */
function _migrateActorSenses(actor, updateData) { function _migrateActorSenses(actor, updateData) {
const ad = actor.data; const ad = actor.data;
if ( ad?.traits?.senses === undefined ) return; if (ad?.traits?.senses === undefined) return;
const original = ad.traits.senses || ""; const original = ad.traits.senses || "";
if ( typeof original !== "string" ) return; if (typeof original !== "string") return;
// Try to match old senses with the format like "Darkvision 60 ft, Blindsight 30 ft" // Try to match old senses with the format like "Darkvision 60 ft, Blindsight 30 ft"
const pattern = /([A-z]+)\s?([0-9]+)\s?([A-z]+)?/; const pattern = /([A-z]+)\s?([0-9]+)\s?([A-z]+)?/;
let wasMatched = false; let wasMatched = false;
// Match each comma-separated term // Match each comma-separated term
for ( let s of original.split(",") ) { for (let s of original.split(",")) {
s = s.trim(); s = s.trim();
const match = s.match(pattern); const match = s.match(pattern);
if ( !match ) continue; if (!match) continue;
const type = match[1].toLowerCase(); const type = match[1].toLowerCase();
if ( type in CONFIG.SW5E.senses ) { if (type in CONFIG.SW5E.senses) {
updateData[`data.attributes.senses.${type}`] = Number(match[2]).toNearest(0.5); updateData[`data.attributes.senses.${type}`] = Number(match[2]).toNearest(0.5);
wasMatched = true; wasMatched = true;
} }
} }
// If nothing was matched, but there was an old string - put the whole thing in "special" // If nothing was matched, but there was an old string - put the whole thing in "special"
if ( !wasMatched && !!original ) { if (!wasMatched && !!original) {
updateData["data.attributes.senses.special"] = original; updateData["data.attributes.senses.special"] = original;
} }
@ -475,15 +481,15 @@ function _migrateActorSenses(actor, updateData) {
function _migrateActorType(actor, updateData) { function _migrateActorType(actor, updateData) {
const ad = actor.data; const ad = actor.data;
const original = ad.details?.type; const original = ad.details?.type;
if ( typeof original !== "string" ) return; if (typeof original !== "string") return;
// New default data structure // New default data structure
let data = { let data = {
"value": "", value: "",
"subtype": "", subtype: "",
"swarm": "", swarm: "",
"custom": "" custom: ""
} };
// Specifics // Specifics
// (Some of these have weird names, these need to be addressed individually) // (Some of these have weird names, these need to be addressed individually)
@ -507,13 +513,14 @@ function _migrateActorType(actor, updateData) {
const pattern = /^(?:swarm of (?<size>[\w\-]+) )?(?<type>[^(]+?)(?:\((?<subtype>[^)]+)\))?$/i; const pattern = /^(?:swarm of (?<size>[\w\-]+) )?(?<type>[^(]+?)(?:\((?<subtype>[^)]+)\))?$/i;
const match = original.trim().match(pattern); const match = original.trim().match(pattern);
if (match) { if (match) {
// Match a known creature type // Match a known creature type
const typeLc = match.groups.type.trim().toLowerCase(); const typeLc = match.groups.type.trim().toLowerCase();
const typeMatch = Object.entries(CONFIG.SW5E.creatureTypes).find(([k, v]) => { const typeMatch = Object.entries(CONFIG.SW5E.creatureTypes).find(([k, v]) => {
return (typeLc === k) || return (
(typeLc === game.i18n.localize(v).toLowerCase()) || typeLc === k ||
(typeLc === game.i18n.localize(`${v}Pl`).toLowerCase()); typeLc === game.i18n.localize(v).toLowerCase() ||
typeLc === game.i18n.localize(`${v}Pl`).toLowerCase()
);
}); });
if (typeMatch) data.value = typeMatch[0]; if (typeMatch) data.value = typeMatch[0];
else { else {
@ -527,7 +534,7 @@ function _migrateActorType(actor, updateData) {
if (match.groups.size || isNamedSwarm) { if (match.groups.size || isNamedSwarm) {
const sizeLc = match.groups.size ? match.groups.size.trim().toLowerCase() : "tiny"; const sizeLc = match.groups.size ? match.groups.size.trim().toLowerCase() : "tiny";
const sizeMatch = Object.entries(CONFIG.SW5E.actorSizes).find(([k, v]) => { const sizeMatch = Object.entries(CONFIG.SW5E.actorSizes).find(([k, v]) => {
return (sizeLc === k) || (sizeLc === game.i18n.localize(v).toLowerCase()); return sizeLc === k || sizeLc === game.i18n.localize(v).toLowerCase();
}); });
data.swarm = sizeMatch ? sizeMatch[0] : "tiny"; data.swarm = sizeMatch ? sizeMatch[0] : "tiny";
} else data.swarm = ""; } else data.swarm = "";
@ -551,8 +558,8 @@ function _migrateActorType(actor, updateData) {
* @private * @private
*/ */
function _migrateItemClassPowerCasting(item, updateData) { function _migrateItemClassPowerCasting(item, updateData) {
if (item.type === "class"){ if (item.type === "class") {
switch (item.name){ switch (item.name) {
case "Consular": case "Consular":
updateData["data.powercasting"] = { updateData["data.powercasting"] = {
progression: "consular", progression: "consular",
@ -560,7 +567,6 @@ function _migrateItemClassPowerCasting(item, updateData) {
}; };
break; break;
case "Engineer": case "Engineer":
updateData["data.powercasting"] = { updateData["data.powercasting"] = {
progression: "engineer", progression: "engineer",
ability: "" ability: ""
@ -608,7 +614,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;
@ -626,11 +636,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 => {
//}) //})
@ -647,7 +656,7 @@ async function _migrateItemPower(item, actor, updateData) {
* @private * @private
*/ */
function _migrateItemAttunement(item, updateData) { function _migrateItemAttunement(item, updateData) {
if ( item.data?.attuned === undefined ) return updateData; if (item.data?.attuned === undefined) return updateData;
updateData["data.attunement"] = CONFIG.SW5E.attunementTypes.NONE; updateData["data.attunement"] = CONFIG.SW5E.attunementTypes.NONE;
updateData["data.-=attuned"] = null; updateData["data.-=attuned"] = null;
return updateData; return updateData;
@ -667,13 +676,13 @@ export async function purgeFlags(pack) {
}; };
await pack.configure({locked: false}); await pack.configure({locked: false});
const content = await pack.getContent(); const content = await pack.getContent();
for ( let entity of content ) { for (let entity of content) {
const update = {_id: entity.id, flags: cleanFlags(entity.data.flags)}; const update = {_id: entity.id, flags: cleanFlags(entity.data.flags)};
if ( pack.entity === "Actor" ) { if (pack.entity === "Actor") {
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}`);
@ -683,20 +692,18 @@ 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
* @private * @private
*/ */
export function removeDeprecatedObjects(data) { export function removeDeprecatedObjects(data) {
for ( let [k, v] of Object.entries(data) ) { for (let [k, v] of Object.entries(data)) {
if ( getType(v) === "Object" ) { if (getType(v) === "Object") {
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

@ -1,11 +1,10 @@
import { SW5E } from "../config.js"; import {SW5E} from "../config.js";
/** /**
* A helper class for building MeasuredTemplates for 5e powers and abilities * A helper class for building MeasuredTemplates for 5e powers and abilities
* @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
@ -14,7 +13,7 @@ export default class AbilityTemplate extends MeasuredTemplate {
static fromItem(item) { static fromItem(item) {
const target = getProperty(item.data, "data.target") || {}; const target = getProperty(item.data, "data.target") || {};
const templateShape = SW5E.areaTargetTypes[target.type]; const templateShape = SW5E.areaTargetTypes[target.type];
if ( !templateShape ) return null; if (!templateShape) return null;
// Prepare template data // Prepare template data
const templateData = { const templateData = {
@ -28,7 +27,7 @@ export default class AbilityTemplate extends MeasuredTemplate {
}; };
// Additional type-specific data // Additional type-specific data
switch ( templateShape ) { switch (templateShape) {
case "cone": case "cone":
templateData.angle = CONFIG.MeasuredTemplate.defaults.angle; templateData.angle = CONFIG.MeasuredTemplate.defaults.angle;
break; break;
@ -67,7 +66,7 @@ export default class AbilityTemplate extends MeasuredTemplate {
this.layer.preview.addChild(this); this.layer.preview.addChild(this);
// Hide the sheet that originated the preview // Hide the sheet that originated the preview
if ( this.actorSheet ) this.actorSheet.minimize(); if (this.actorSheet) this.actorSheet.minimize();
// Activate interactivity // Activate interactivity
this.activatePreviewListeners(initialLayer); this.activatePreviewListeners(initialLayer);
@ -84,10 +83,10 @@ export default class AbilityTemplate extends MeasuredTemplate {
let moveTime = 0; let moveTime = 0;
// Update placement (mouse-move) // Update placement (mouse-move)
handlers.mm = event => { handlers.mm = (event) => {
event.stopPropagation(); event.stopPropagation();
let now = Date.now(); // Apply a 20ms throttle let now = Date.now(); // Apply a 20ms throttle
if ( now - moveTime <= 20 ) return; if (now - moveTime <= 20) return;
const center = event.data.getLocalPosition(this.layer); const center = event.data.getLocalPosition(this.layer);
const snapped = canvas.grid.getSnappedPosition(center.x, center.y, 2); const snapped = canvas.grid.getSnappedPosition(center.x, center.y, 2);
this.data.update({x: snapped.x, y: snapped.y}); this.data.update({x: snapped.x, y: snapped.y});
@ -96,7 +95,7 @@ export default class AbilityTemplate extends MeasuredTemplate {
}; };
// Cancel the workflow (right-click) // Cancel the workflow (right-click)
handlers.rc = event => { handlers.rc = (event) => {
this.layer.preview.removeChildren(); this.layer.preview.removeChildren();
canvas.stage.off("mousemove", handlers.mm); canvas.stage.off("mousemove", handlers.mm);
canvas.stage.off("mousedown", handlers.lc); canvas.stage.off("mousedown", handlers.lc);
@ -107,7 +106,7 @@ export default class AbilityTemplate extends MeasuredTemplate {
}; };
// Confirm the workflow (left-click) // Confirm the workflow (left-click)
handlers.lc = event => { handlers.lc = (event) => {
handlers.rc(event); handlers.rc(event);
const destination = canvas.grid.getSnappedPosition(this.data.x, this.data.y, 2); const destination = canvas.grid.getSnappedPosition(this.data.x, this.data.y, 2);
this.data.update(destination); this.data.update(destination);
@ -115,12 +114,12 @@ export default class AbilityTemplate extends MeasuredTemplate {
}; };
// Rotate the template by 3 degree increments (mouse-wheel) // Rotate the template by 3 degree increments (mouse-wheel)
handlers.mw = event => { handlers.mw = (event) => {
if ( event.ctrlKey ) event.preventDefault(); // Avoid zooming the browser window if (event.ctrlKey) event.preventDefault(); // Avoid zooming the browser window
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.update({direction: this.data.direction + (snap * Math.sign(event.deltaY))}); this.data.update({direction: 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
}); });
/** /**
@ -92,7 +91,7 @@ export const registerSystemSettings = function() {
config: true, config: true,
default: false, default: false,
type: Boolean, type: Boolean,
onChange: s => { onChange: (s) => {
ui.chat.render(); ui.chat.render();
} }
}); });
@ -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

@ -3,9 +3,8 @@
* Pre-loaded templates are compiled and cached for fast access when rendering * Pre-loaded templates are compiled and cached for fast access when rendering
* @return {Promise} * @return {Promise}
*/ */
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",

View file

@ -3,11 +3,10 @@
* @extends {TokenDocument} * @extends {TokenDocument}
*/ */
export class TokenDocument5e extends TokenDocument { export class TokenDocument5e extends TokenDocument {
/** @inheritdoc */ /** @inheritdoc */
getBarAttribute(...args) { getBarAttribute(...args) {
const data = super.getBarAttribute(...args); const data = super.getBarAttribute(...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);
} }
@ -15,19 +14,16 @@ export class TokenDocument5e extends TokenDocument {
} }
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**
* Extend the base Token class to implement additional system-specific logic. * Extend the base Token class to implement additional system-specific logic.
* @extends {Token} * @extends {Token}
*/ */
export class Token5e extends Token { export class Token5e extends Token {
/** @inheritdoc */ /** @inheritdoc */
_drawBar(number, bar, data) { _drawBar(number, bar, data) {
if ( data.attribute === "attributes.hp" ) return this._drawHPBar(number, bar, data); if (data.attribute === "attributes.hp") return this._drawHPBar(number, bar, data);
return super._drawBar(number, bar, data); return super._drawBar(number, bar, data);
} }
@ -41,7 +37,6 @@ export class Token5e extends Token {
* @private * @private
*/ */
_drawHPBar(number, bar, data) { _drawHPBar(number, bar, data) {
// Extract health data // Extract health data
let {value, max, temp, tempmax} = this.document.actor.data.data.attributes.hp; let {value, max, temp, tempmax} = this.document.actor.data.data.attributes.hp;
temp = Number(temp || 0); temp = Number(temp || 0);
@ -58,42 +53,50 @@ export class Token5e extends Token {
// Determine colors to use // Determine colors to use
const blk = 0x000000; const blk = 0x000000;
const hpColor = PIXI.utils.rgb2hex([(1-(colorPct/2)), colorPct, 0]); const hpColor = PIXI.utils.rgb2hex([1 - colorPct / 2, colorPct, 0]);
const c = CONFIG.SW5E.tokenHPColors; const c = CONFIG.SW5E.tokenHPColors;
// Determine the container size (logic borrowed from core) // Determine the container size (logic borrowed from core)
const w = this.w; const w = this.w;
let h = Math.max((canvas.dimensions.size / 12), 8); let h = Math.max(canvas.dimensions.size / 12, 8);
if ( this.data.height >= 2 ) h *= 1.6; if (this.data.height >= 2) h *= 1.6;
const bs = Math.clamped(h / 8, 1, 2); const bs = Math.clamped(h / 8, 1, 2);
const bs1 = bs+1; const bs1 = bs + 1;
// Overall bar container // Overall bar container
bar.clear() bar.clear();
bar.beginFill(blk, 0.5).lineStyle(bs, blk, 1.0).drawRoundedRect(0, 0, w, h, 3); bar.beginFill(blk, 0.5).lineStyle(bs, blk, 1.0).drawRoundedRect(0, 0, w, h, 3);
// Temporary maximum HP // Temporary maximum HP
if (tempmax > 0) { if (tempmax > 0) {
const pct = max / effectiveMax; const pct = max / effectiveMax;
bar.beginFill(c.tempmax, 1.0).lineStyle(1, blk, 1.0).drawRoundedRect(pct*w, 0, (1-pct)*w, h, 2); bar.beginFill(c.tempmax, 1.0)
.lineStyle(1, blk, 1.0)
.drawRoundedRect(pct * w, 0, (1 - pct) * w, h, 2);
} }
// Maximum HP penalty // Maximum HP penalty
else if (tempmax < 0) { else if (tempmax < 0) {
const pct = (max + tempmax) / max; const pct = (max + tempmax) / max;
bar.beginFill(c.negmax, 1.0).lineStyle(1, blk, 1.0).drawRoundedRect(pct*w, 0, (1-pct)*w, h, 2); bar.beginFill(c.negmax, 1.0)
.lineStyle(1, blk, 1.0)
.drawRoundedRect(pct * w, 0, (1 - pct) * w, h, 2);
} }
// Health bar // Health bar
bar.beginFill(hpColor, 1.0).lineStyle(bs, blk, 1.0).drawRoundedRect(0, 0, valuePct*w, h, 2) bar.beginFill(hpColor, 1.0)
.lineStyle(bs, blk, 1.0)
.drawRoundedRect(0, 0, valuePct * w, h, 2);
// Temporary hit points // Temporary hit points
if ( temp > 0 ) { if (temp > 0) {
bar.beginFill(c.temp, 1.0).lineStyle(0).drawRoundedRect(bs1, bs1, (tempPct*w)-(2*bs1), h-(2*bs1), 1); bar.beginFill(c.temp, 1.0)
.lineStyle(0)
.drawRoundedRect(bs1, bs1, tempPct * w - 2 * bs1, h - 2 * bs1, 1);
} }
// Set position // Set position
let posY = (number === 0) ? (this.h - h) : 0; let posY = number === 0 ? this.h - h : 0;
bar.position.set(0, posY); bar.position.set(0, posY);
} }
} }

173
sw5e.js
View file

@ -8,17 +8,17 @@
*/ */
// Import Modules // Import Modules
import { SW5E } from "./module/config.js"; import {SW5E} from "./module/config.js";
import { registerSystemSettings } from "./module/settings.js"; import {registerSystemSettings} from "./module/settings.js";
import { preloadHandlebarsTemplates } from "./module/templates.js"; import {preloadHandlebarsTemplates} from "./module/templates.js";
import { _getInitiativeFormula } from "./module/combat.js"; import {_getInitiativeFormula} from "./module/combat.js";
import { measureDistances } from "./module/canvas.js"; import {measureDistances} from "./module/canvas.js";
// Import Documents // Import Documents
import Actor5e from "./module/actor/entity.js"; import Actor5e from "./module/actor/entity.js";
import Item5e from "./module/item/entity.js"; import Item5e from "./module/item/entity.js";
import CharacterImporter from "./module/characterImporter.js"; import CharacterImporter from "./module/characterImporter.js";
import { TokenDocument5e, Token5e } from "./module/token.js" import {TokenDocument5e, Token5e} from "./module/token.js";
// Import Applications // Import Applications
import AbilityTemplate from "./module/pixi/ability-template.js"; import AbilityTemplate from "./module/pixi/ability-template.js";
@ -46,10 +46,7 @@ import * as migrations from "./module/migration.js";
/* Foundry VTT Initialization */ /* Foundry VTT Initialization */
/* -------------------------------------------- */ /* -------------------------------------------- */
// Keep on while migrating to Foundry version 0.8 Hooks.once("init", function () {
CONFIG.debug.hooks = true;
Hooks.once("init", function() {
console.log(`SW5e | Initializing SW5E System\n${SW5E.ASCII}`); console.log(`SW5e | Initializing SW5E System\n${SW5E.ASCII}`);
// Create a SW5E namespace within the game global // Create a SW5E namespace within the game global
@ -77,7 +74,7 @@ Hooks.once("init", function() {
Actor5e, Actor5e,
Item5e, Item5e,
TokenDocument5e, TokenDocument5e,
Token5e, Token5e
}, },
macros: macros, macros: macros,
migrations: migrations, migrations: migrations,
@ -91,11 +88,7 @@ Hooks.once("init", function() {
CONFIG.Token.documentClass = TokenDocument5e; CONFIG.Token.documentClass = TokenDocument5e;
CONFIG.Token.objectClass = Token5e; CONFIG.Token.objectClass = Token5e;
CONFIG.time.roundTime = 6; CONFIG.time.roundTime = 6;
CONFIG.fontFamilies = [ CONFIG.fontFamilies = ["Engli-Besh", "Open Sans", "Russo One"];
"Engli-Besh",
"Open Sans",
"Russo One"
];
CONFIG.Dice.DamageRoll = dice.DamageRoll; CONFIG.Dice.DamageRoll = dice.DamageRoll;
CONFIG.Dice.D20Roll = dice.D20Roll; CONFIG.Dice.D20Roll = dice.D20Roll;
@ -145,14 +138,37 @@ Hooks.once("init", function() {
// makeDefault: true, // makeDefault: true,
// label: "SW5E.SheetClassStarship" // label: "SW5E.SheetClassStarship"
// }); // });
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', 'deployment', 'deploymentfeature', 'starship', 'starshipfeature', 'starshipmod', 'venture'], types: [
"weapon",
"equipment",
"consumable",
"tool",
"loot",
"class",
"power",
"feat",
"species",
"backpack",
"archetype",
"classfeature",
"background",
"fightingmastery",
"fightingstyle",
"lightsaberform",
"deployment",
"deploymentfeature",
"starship",
"starshipfeature",
"starshipmod",
"venture"
],
makeDefault: true, makeDefault: true,
label: "SW5E.SheetClassItem" label: "SW5E.SheetClassItem"
}); });
@ -161,7 +177,6 @@ Hooks.once("init", function() {
return preloadHandlebarsTemplates(); return preloadHandlebarsTemplates();
}); });
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Foundry VTT Setup */ /* Foundry VTT Setup */
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -169,31 +184,77 @@ 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",
"starshipRolessm", "starshipRolesmed", "starshipRoleslg", "starshipRoleshuge", "starshipRolesgrg", "starshipSkills", "actorSizes",
"powerComponents", "powerLevels", "powerPreparationModes", "powerScalingModes", "powerSchools", "targetTypes", "alignments",
"timePeriods", "toolProficiencies", "weaponProficiencies", "weaponProperties", "weaponSizes", "weaponTypes" "armorProficiencies",
"armorPropertiesTypes",
"conditionTypes",
"consumableTypes",
"cover",
"currencies",
"damageResistanceTypes",
"damageTypes",
"distanceUnits",
"equipmentTypes",
"healingTypes",
"itemActionTypes",
"languages",
"limitedUsePeriods",
"movementTypes",
"movementUnits",
"polymorphSettings",
"proficiencyLevels",
"senses",
"skills",
"starshipRolessm",
"starshipRolesmed",
"starshipRoleslg",
"starshipRoleshuge",
"starshipRolesgrg",
"starshipSkills",
"powerComponents",
"powerLevels",
"powerPreparationModes",
"powerScalingModes",
"powerSchools",
"targetTypes",
"timePeriods",
"toolProficiencies",
"weaponProficiencies",
"weaponProperties",
"weaponSizes",
"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
for ( let o of toLocalize ) { for (let o of toLocalize) {
const localized = Object.entries(CONFIG.SW5E[o]).map(e => { const localized = Object.entries(CONFIG.SW5E[o]).map((e) => {
return [e[0], game.i18n.localize(e[1])]; return [e[0], game.i18n.localize(e[1])];
}); });
if ( !noSort.includes(o) ) localized.sort((a, b) => a[1].localeCompare(b[1])); if (!noSort.includes(o)) localized.sort((a, b) => a[1].localeCompare(b[1]));
CONFIG.SW5E[o] = localized.reduce((obj, e) => { CONFIG.SW5E[o] = localized.reduce((obj, e) => {
obj[e[0]] = e[1]; obj[e[0]] = e[1];
return obj; return obj;
@ -202,7 +263,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);
}); });
@ -210,23 +271,25 @@ 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));
// Determine whether a system migration is required and feasible // Determine whether a system migration is required and feasible
if ( !game.user.isGM ) return; if (!game.user.isGM) return;
const currentVersion = game.settings.get("sw5e", "systemMigrationVersion"); const currentVersion = game.settings.get("sw5e", "systemMigrationVersion");
const NEEDS_MIGRATION_VERSION = "1.3.5.R1-A6"; const NEEDS_MIGRATION_VERSION = "1.3.5.R1-A6";
// Check for R1 SW5E versions // Check for R1 SW5E versions
const SW5E_NEEDS_MIGRATION_VERSION = "R1-A6"; const SW5E_NEEDS_MIGRATION_VERSION = "R1-A6";
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 && needsMigration !== "") return; if (!needsMigration && needsMigration !== "") return;
// Perform the migration // Perform the migration
if ( currentVersion && isNewerVersion(COMPATIBLE_MIGRATION_VERSION, currentVersion) ) { if (currentVersion && isNewerVersion(COMPATIBLE_MIGRATION_VERSION, currentVersion)) {
const warning = `Your SW5e system data is from too old a Foundry version and cannot be reliably migrated to the latest version. The process will be attempted, but errors may occur.`; const warning = `Your SW5e system data is from too old a Foundry version and cannot be reliably migrated to the latest version. The process will be attempted, but errors may occur.`;
ui.notifications.error(warning, {permanent: true}); ui.notifications.error(warning, {permanent: true});
} }
@ -237,19 +300,17 @@ Hooks.once("ready", function() {
/* Canvas Initialization */ /* Canvas Initialization */
/* -------------------------------------------- */ /* -------------------------------------------- */
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;
}); });
/* -------------------------------------------- */ /* -------------------------------------------- */
/* 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);
@ -262,38 +323,36 @@ 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);
}); });
Hooks.on("renderActorDirectory", (app, html, data)=> { Hooks.on("renderActorDirectory", (app, html, data) => {
setFolderBackground(html); setFolderBackground(html);
CharacterImporter.addImportButton(html); CharacterImporter.addImportButton(html);
}); });
Hooks.on("renderItemDirectory", (app, html, data)=> { Hooks.on("renderItemDirectory", (app, html, data) => {
setFolderBackground(html); setFolderBackground(html);
}); });
Hooks.on("renderJournalDirectory", (app, html, data)=> { Hooks.on("renderJournalDirectory", (app, html, data) => {
setFolderBackground(html); setFolderBackground(html);
}); });
Hooks.on("renderRollTableDirectory", (app, html, data)=> { Hooks.on("renderRollTableDirectory", (app, html, data) => {
setFolderBackground(html); setFolderBackground(html);
}); });
Hooks.on("ActorSheet5eCharacterNew", (app, html, data) => { Hooks.on("ActorSheet5eCharacterNew", (app, html, data) => {
console.log("renderSwaltSheet"); console.log("renderSwaltSheet");
}); });
// FIXME: This helper is needed for the vehicle sheet. It should probably be refactored. // FIXME: This helper is needed for the vehicle sheet. It should probably be refactored.
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); });
})
} }