Initial styling config and auto-format of files

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

7
.prettierrc Normal file
View file

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

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@ import TraitSelector from "../../../apps/trait-selector.js";
import ActorSheetFlags from "../../../apps/actor-flags.js"; import ActorSheetFlags from "../../../apps/actor-flags.js";
import ActorMovementConfig from "../../../apps/movement-config.js"; import ActorMovementConfig from "../../../apps/movement-config.js";
import ActorSensesConfig from "../../../apps/senses-config.js"; import ActorSensesConfig from "../../../apps/senses-config.js";
import {SW5E} from '../../../config.js'; import {SW5E} from "../../../config.js";
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js"; import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js";
/** /**
@ -48,7 +48,8 @@ export default class ActorSheet5e extends ActorSheet {
/** @override */ /** @override */
get template() { get template() {
if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/newActor/expanded-limited-sheet.html"; if (!game.user.isGM && this.actor.limited)
return "systems/sw5e/templates/actors/newActor/expanded-limited-sheet.html";
return `systems/sw5e/templates/actors/newActor/${this.actor.data.type}-sheet.html`; return `systems/sw5e/templates/actors/newActor/${this.actor.data.type}-sheet.html`;
} }
@ -56,7 +57,6 @@ export default class ActorSheet5e extends ActorSheet {
/** @override */ /** @override */
getData() { getData() {
// Basic data // Basic data
let isOwner = this.entity.owner; let isOwner = this.entity.owner;
const data = { const data = {
@ -67,8 +67,8 @@ export default class ActorSheet5e extends ActorSheet {
cssClass: isOwner ? "editable" : "locked", cssClass: isOwner ? "editable" : "locked",
isCharacter: this.entity.data.type === "character", isCharacter: this.entity.data.type === "character",
isNPC: this.entity.data.type === "npc", isNPC: this.entity.data.type === "npc",
isVehicle: this.entity.data.type === 'vehicle', isVehicle: this.entity.data.type === "vehicle",
config: CONFIG.SW5E, config: CONFIG.SW5E
}; };
// The Actor and its Items // The Actor and its Items
@ -83,7 +83,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(data.actor.data.abilities)) { for (let [a, abl] of Object.entries(data.actor.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];
@ -91,7 +91,7 @@ export default class ActorSheet5e extends ActorSheet {
// Skills // Skills
if (data.actor.data.skills) { if (data.actor.data.skills) {
for ( let [s, skl] of Object.entries(data.actor.data.skills)) { for (let [s, skl] of Object.entries(data.actor.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];
@ -115,7 +115,7 @@ export default class ActorSheet5e extends ActorSheet {
data.effects = prepareActiveEffectCategories(this.entity.effects); data.effects = prepareActiveEffectCategories(this.entity.effects);
// Return data to the sheet // Return data to the sheet
return data return data;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -127,17 +127,21 @@ 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}`]);
} }
@ -145,12 +149,12 @@ export default class ActorSheet5e extends ActorSheet {
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
@ -158,7 +162,7 @@ export default class ActorSheet5e extends ActorSheet {
return { return {
primary: `${movement.walk || 0} ${movement.units}`, primary: `${movement.walk || 0} ${movement.units}`,
special: speeds.length ? speeds.map(s => s[1]).join(", ") : "" special: speeds.length ? speeds.map(s => s[1]).join(", ") : ""
} };
} }
} }
@ -167,12 +171,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;
} }
@ -185,20 +189,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) => {
@ -207,8 +211,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";
} }
@ -229,44 +233,44 @@ export default class ActorSheet5e extends ActorSheet {
// Define some mappings // Define some mappings
const sections = { const sections = {
"atwill": -20, atwill: -20,
"innate": -10, innate: -10
}; };
// Label power slot uses headers // Label power slot uses headers
const useLabels = { const useLabels = {
"-20": "-", "-20": "-",
"-10": "-", "-10": "-",
"0": "∞" 0: "∞"
}; };
// 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}`;
@ -281,9 +285,9 @@ export default class ActorSheet5e extends ActorSheet {
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, {
@ -296,7 +300,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]});
} }
@ -322,28 +326,28 @@ export default class ActorSheet5e extends ActorSheet {
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;
}); });
@ -374,59 +378,58 @@ export default class ActorSheet5e extends ActorSheet {
* @param html {HTML} The prepared HTML object ready to be rendered into the DOM * @param html {HTML} The prepared HTML object ready to be rendered into the DOM
*/ */
activateListeners(html) { activateListeners(html) {
// Activate Item Filters // Activate Item Filters
const filterLists = html.find(".filter-list"); const filterLists = html.find(".filter-list");
filterLists.each(this._initializeFilterItemList.bind(this)); filterLists.each(this._initializeFilterItemList.bind(this));
filterLists.on("click", ".filter-item", this._onToggleFilter.bind(this)); filterLists.on("click", ".filter-item", this._onToggleFilter.bind(this));
// Item summaries // Item summaries
html.find('.item .item-name.rollable h4').click(event => this._onItemSummary(event)); html.find(".item .item-name.rollable h4").click(event => this._onItemSummary(event));
// Editable Only Listeners // Editable Only Listeners
if ( this.isEditable ) { if (this.isEditable) {
// Input focus and update // Input focus and update
const inputs = html.find("input"); const inputs = html.find("input");
inputs.focus(ev => ev.currentTarget.select()); inputs.focus(ev => ev.currentTarget.select());
inputs.addBack().find('[data-dtype="Number"]').change(this._onChangeInputDelta.bind(this)); inputs.addBack().find('[data-dtype="Number"]').change(this._onChangeInputDelta.bind(this));
// Ability Proficiency // Ability Proficiency
html.find('.ability-proficiency').click(this._onToggleAbilityProficiency.bind(this)); html.find(".ability-proficiency").click(this._onToggleAbilityProficiency.bind(this));
// Toggle Skill Proficiency // Toggle Skill Proficiency
html.find('.skill-proficiency').on("click contextmenu", this._onCycleSkillProficiency.bind(this)); html.find(".skill-proficiency").on("click contextmenu", this._onCycleSkillProficiency.bind(this));
// Trait Selector // Trait Selector
html.find('.trait-selector').click(this._onTraitSelector.bind(this)); html.find(".trait-selector").click(this._onTraitSelector.bind(this));
// Configure Special Flags // Configure Special Flags
html.find('.config-button').click(this._onConfigMenu.bind(this)); html.find(".config-button").click(this._onConfigMenu.bind(this));
// Owned Item management // Owned Item management
html.find('.item-create').click(this._onItemCreate.bind(this)); html.find(".item-create").click(this._onItemCreate.bind(this));
html.find('.item-edit').click(this._onItemEdit.bind(this)); html.find(".item-edit").click(this._onItemEdit.bind(this));
html.find('.item-delete').click(this._onItemDelete.bind(this)); html.find(".item-delete").click(this._onItemDelete.bind(this));
html.find('.item-uses input').click(ev => ev.target.select()).change(this._onUsesChange.bind(this)); html
html.find('.slot-max-override').click(this._onPowerSlotOverride.bind(this)); .find(".item-uses input")
.click(ev => ev.target.select())
.change(this._onUsesChange.bind(this));
html.find(".slot-max-override").click(this._onPowerSlotOverride.bind(this));
// Active Effect management // Active Effect management
html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.entity)); html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.entity));
} }
// Owner Only Listeners // Owner Only Listeners
if ( this.actor.owner ) { if (this.actor.owner) {
// Ability Checks // Ability Checks
html.find('.ability-name').click(this._onRollAbilityTest.bind(this)); html.find(".ability-name").click(this._onRollAbilityTest.bind(this));
// Roll Skill Checks // Roll Skill Checks
html.find('.skill-name').click(this._onRollSkillCheck.bind(this)); html.find(".skill-name").click(this._onRollSkillCheck.bind(this));
// Item Rolling // Item Rolling
html.find('.item .item-image').click(event => this._onItemRoll(event)); html.find(".item .item-image").click(event => this._onItemRoll(event));
html.find('.item .item-recharge').click(event => this._onItemRecharge(event)); html.find(".item .item-recharge").click(event => this._onItemRecharge(event));
} }
// Otherwise remove rollable classes // Otherwise remove rollable classes
@ -447,8 +450,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");
} }
} }
@ -464,10 +467,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);
} }
} }
@ -482,7 +485,7 @@ export default class ActorSheet5e extends ActorSheet {
_onConfigMenu(event) { _onConfigMenu(event) {
event.preventDefault(); event.preventDefault();
const button = event.currentTarget; const button = event.currentTarget;
switch ( button.dataset.action ) { switch (button.dataset.action) {
case "movement": case "movement":
new ActorMovementConfig(this.object).render(true); new ActorMovementConfig(this.object).render(true);
break; break;
@ -512,10 +515,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
@ -526,8 +529,8 @@ export default class ActorSheet5e extends ActorSheet {
/** @override */ /** @override */
async _onDropActor(event, data) { async _onDropActor(event, data) {
const canPolymorph = game.user.isGM || (this.actor.owner && game.settings.get('sw5e', 'allowPolymorphing')); const canPolymorph = game.user.isGM || (this.actor.owner && game.settings.get("sw5e", "allowPolymorphing"));
if ( !canPolymorph ) return false; if (!canPolymorph) return false;
// Get the target actor // Get the target actor
let sourceActor = null; let sourceActor = null;
@ -537,78 +540,82 @@ export default class ActorSheet5e extends ActorSheet {
} 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'), {
content: { title: game.i18n.localize("SW5E.PolymorphPromptTitle"),
options: game.settings.get('sw5e', 'polymorphSettings'), content: {
i18n: SW5E.polymorphSettings, options: game.settings.get("sw5e", "polymorphSettings"),
isToken: this.actor.isToken i18n: SW5E.polymorphSettings,
}, isToken: this.actor.isToken
default: 'accept',
buttons: {
accept: {
icon: '<i class="fas fa-check"></i>',
label: game.i18n.localize('SW5E.PolymorphAcceptSettings'),
callback: html => this.actor.transformInto(sourceActor, rememberOptions(html))
}, },
wildshape: { default: "accept",
icon: '<i class="fas fa-paw"></i>', buttons: {
label: game.i18n.localize('SW5E.PolymorphWildShape'), accept: {
callback: html => this.actor.transformInto(sourceActor, { icon: '<i class="fas fa-check"></i>',
keepBio: true, label: game.i18n.localize("SW5E.PolymorphAcceptSettings"),
keepClass: true, callback: html => this.actor.transformInto(sourceActor, rememberOptions(html))
keepMental: true, },
mergeSaves: true, wildshape: {
mergeSkills: true, icon: '<i class="fas fa-paw"></i>',
transformTokens: rememberOptions(html).transformTokens label: game.i18n.localize("SW5E.PolymorphWildShape"),
}) callback: html =>
}, this.actor.transformInto(sourceActor, {
polymorph: { keepBio: true,
icon: '<i class="fas fa-pastafarianism"></i>', keepClass: true,
label: game.i18n.localize('SW5E.Polymorph'), keepMental: true,
callback: html => this.actor.transformInto(sourceActor, { mergeSaves: true,
transformTokens: rememberOptions(html).transformTokens mergeSkills: true,
}) transformTokens: rememberOptions(html).transformTokens
}, })
cancel: { },
icon: '<i class="fas fa-times"></i>', polymorph: {
label: game.i18n.localize('Cancel') icon: '<i class="fas fa-pastafarianism"></i>',
label: game.i18n.localize("SW5E.Polymorph"),
callback: html =>
this.actor.transformInto(sourceActor, {
transformTokens: rememberOptions(html).transformTokens
})
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: game.i18n.localize("Cancel")
}
} }
},
{
classes: ["dialog", "sw5e"],
width: 600,
template: "systems/sw5e/templates/apps/polymorph-prompt.html"
} }
}, { ).render(true);
classes: ['dialog', 'sw5e'],
width: 600,
template: 'systems/sw5e/templates/apps/polymorph-prompt.html'
}).render(true);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
async _onDropItemCreate(itemData) { async _onDropItemCreate(itemData) {
// Create a Consumable power scroll on the Inventory tab // Create a Consumable power scroll on the Inventory tab
if ( (itemData.type === "power") && (this._tabs[0].active === "inventory") ) { if (itemData.type === "power" && this._tabs[0].active === "inventory") {
const scroll = await Item5e.createScrollFromPower(itemData); const scroll = await Item5e.createScrollFromPower(itemData);
itemData = scroll.data; itemData = scroll.data;
} }
// Ignore certain statuses // Ignore certain statuses
if ( itemData.data ) { if (itemData.data) {
["attunement", "equipped", "proficient", "prepared"].forEach(k => delete itemData.data[k]); ["attunement", "equipped", "proficient", "prepared"].forEach(k => delete itemData.data[k]);
} }
@ -623,7 +630,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;
@ -648,12 +655,12 @@ export default class ActorSheet5e extends ActorSheet {
* @private * @private
*/ */
async _onUsesChange(event) { async _onUsesChange(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.getOwnedItem(itemId); const item = this.actor.getOwnedItem(itemId);
const uses = Math.clamped(0, parseInt(event.target.value), item.data.data.uses.max); const uses = Math.clamped(0, parseInt(event.target.value), item.data.data.uses.max);
event.target.value = uses; event.target.value = uses;
return item.update({ 'data.uses.value': uses }); return item.update({"data.uses.value": uses});
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -681,7 +688,7 @@ export default class ActorSheet5e extends ActorSheet {
const itemId = event.currentTarget.closest(".item").dataset.itemId; const itemId = event.currentTarget.closest(".item").dataset.itemId;
const item = this.actor.getOwnedItem(itemId); const item = this.actor.getOwnedItem(itemId);
return item.rollRecharge(); return item.rollRecharge();
}; }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -692,11 +699,11 @@ export default class ActorSheet5e extends ActorSheet {
_onItemSummary(event) { _onItemSummary(event) {
event.preventDefault(); event.preventDefault();
let li = $(event.currentTarget).parents(".item"), let li = $(event.currentTarget).parents(".item"),
item = this.actor.getOwnedItem(li.data("item-id")), item = this.actor.getOwnedItem(li.data("item-id")),
chatData = item.getChatData({secrets: this.actor.owner}); chatData = item.getChatData({secrets: this.actor.owner});
// 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 {
@ -808,7 +815,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);
this.render(); this.render();
} }
@ -825,8 +832,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};
new TraitSelector(this.actor, options).render(true) new TraitSelector(this.actor, options).render(true);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -836,13 +843,13 @@ export default class ActorSheet5e extends ActorSheet {
let buttons = super._getHeaderButtons(); let buttons = super._getHeaderButtons();
// Add button to revert polymorph // Add button to revert polymorph
if ( !this.actor.isPolymorphed || this.actor.isToken ) return buttons; if (!this.actor.isPolymorphed || this.actor.isToken) return buttons;
buttons.unshift({ buttons.unshift({
label: 'SW5E.PolymorphRestoreTransformation', label: "SW5E.PolymorphRestoreTransformation",
class: "restore-transformation", class: "restore-transformation",
icon: "fas fa-backward", icon: "fas fa-backward",
onclick: ev => this.actor.revertOriginalForm() onclick: ev => this.actor.revertOriginalForm()
}); });
return buttons; return buttons;
} }
} }

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";
@ -16,18 +15,19 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
* Define default rendering options for the NPC sheet * Define default rendering options for the NPC sheet
* @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", {
contentSelector: ".sheet-body", navSelector: ".root-tabs",
initial: "attributes" contentSelector: ".sheet-body",
}], 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]);
@ -57,9 +57,11 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
// Experience Tracking // Experience Tracking
sheetData["disableExperience"] = game.settings.get("sw5e", "disableExperienceTracking"); sheetData["disableExperience"] = game.settings.get("sw5e", "disableExperienceTracking");
sheetData["classLabels"] = this.actor.itemTypes.class.map(c => c.name).join(", "); sheetData["classLabels"] = this.actor.itemTypes.class.map(c => c.name).join(", ");
sheetData["multiclassLabels"] = this.actor.itemTypes.class.map(c => { sheetData["multiclassLabels"] = this.actor.itemTypes.class
return [c.data.data.archetype, c.name, c.data.data.levels].filterJoin(' ') .map(c => {
}).join(', '); return [c.data.data.archetype, c.name, c.data.data.levels].filterJoin(" ");
})
.join(", ");
// Return data for rendering // Return data for rendering
return sheetData; return sheetData;
@ -72,60 +74,74 @@ 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, species, archetypes, classfeatures, backgrounds, fightingstyles, fightingmasteries, lightsaberforms] = data.items.reduce((arr, item) => { let [
items,
forcepowers,
techpowers,
feats,
classes,
species,
archetypes,
classfeatures,
backgrounds,
fightingstyles,
fightingmasteries,
lightsaberforms
] = data.items.reduce(
(arr, item) => {
// Item details
item.img = item.img || DEFAULT_TOKEN;
item.isStack = Number.isNumeric(item.data.quantity) && item.data.quantity !== 1;
item.attunement = {
[CONFIG.SW5E.attunementTypes.REQUIRED]: {
icon: "fa-sun",
cls: "not-attuned",
title: "SW5E.AttunementRequired"
},
[CONFIG.SW5E.attunementTypes.ATTUNED]: {
icon: "fa-sun",
cls: "attuned",
title: "SW5E.AttunementAttuned"
}
}[item.data.attunement];
// Item details // Item usage
item.img = item.img || DEFAULT_TOKEN; item.hasUses = item.data.uses && item.data.uses.max > 0;
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1); item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && item.data.recharge.charged === false;
item.attunement = { item.isDepleted = item.isOnCooldown && item.data.uses.per && item.data.uses.value > 0;
[CONFIG.SW5E.attunementTypes.REQUIRED]: { item.hasTarget = !!item.data.target && !["none", ""].includes(item.data.target.type);
icon: "fa-sun",
cls: "not-attuned",
title: "SW5E.AttunementRequired"
},
[CONFIG.SW5E.attunementTypes.ATTUNED]: {
icon: "fa-sun",
cls: "attuned",
title: "SW5E.AttunementAttuned"
}
}[item.data.attunement];
// Item usage // Item toggle state
item.hasUses = item.data.uses && (item.data.uses.max > 0); this._prepareItemToggleState(item);
item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false);
item.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0));
item.hasTarget = !!item.data.target && !(["none",""].includes(item.data.target.type));
// Item toggle state // Classify items into types
this._prepareItemToggleState(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);
// Classify items into types else if (item.type === "feat") arr[3].push(item);
if ( item.type === "power" && ["lgt", "drk", "uni"].includes(item.data.school) ) arr[1].push(item); else if (item.type === "class") arr[4].push(item);
else if ( item.type === "power" && ["tec"].includes(item.data.school) ) arr[2].push(item); else if (item.type === "species") arr[5].push(item);
else if ( item.type === "feat" ) arr[3].push(item); else if (item.type === "archetype") arr[6].push(item);
else if ( item.type === "class" ) arr[4].push(item); else if (item.type === "classfeature") arr[7].push(item);
else if ( item.type === "species" ) arr[5].push(item); else if (item.type === "background") arr[8].push(item);
else if ( item.type === "archetype" ) arr[6].push(item); else if (item.type === "fightingstyle") arr[9].push(item);
else if ( item.type === "classfeature" ) arr[7].push(item); else if (item.type === "fightingmastery") arr[10].push(item);
else if ( item.type === "background" ) arr[8].push(item); else if (item.type === "lightsaberform") arr[11].push(item);
else if ( item.type === "fightingstyle" ) arr[9].push(item); else if (Object.keys(inventory).includes(item.type)) arr[0].push(item);
else if ( item.type === "fightingmastery" ) arr[10].push(item); return arr;
else if ( item.type === "lightsaberform" ) arr[11].push(item); },
else if ( Object.keys(inventory).includes(item.type ) ) arr[0].push(item); [[], [], [], [], [], [], [], [], [], [], [], []]
return arr; );
}, [[], [], [], [], [], [], [], [], [], [], [], []]);
// Apply active item filters // Apply active item filters
items = this._filterItems(items, this._filters.inventory); items = this._filterItems(items, this._filters.inventory);
@ -134,7 +150,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 = Math.round(i.data.quantity * i.data.weight * 10) / 10; i.totalWeight = Math.round(i.data.quantity * i.data.weight * 10) / 10;
@ -147,19 +163,66 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
// Organize Features // Organize Features
const features = { const features = {
classes: { label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true }, classes: {label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true},
classfeatures: { label: "SW5E.ItemTypeClassFeats", items: [], hasActions: true, dataset: {type: "classfeature"}, isClassfeature: true }, classfeatures: {
archetype: { label: "SW5E.ItemTypeArchetype", items: [], hasActions: false, dataset: {type: "archetype"}, isArchetype: true }, label: "SW5E.ItemTypeClassFeats",
species: { label: "SW5E.ItemTypeSpecies", items: [], hasActions: false, dataset: {type: "species"}, isSpecies: true }, items: [],
background: { label: "SW5E.ItemTypeBackground", items: [], hasActions: false, dataset: {type: "background"}, isBackground: true }, hasActions: true,
fightingstyles: { label: "SW5E.ItemTypeFightingStylePl", items: [], hasActions: false, dataset: {type: "fightingstyle"}, isFightingstyle: true }, dataset: {type: "classfeature"},
fightingmasteries: { label: "SW5E.ItemTypeFightingMasteryPl", items: [], hasActions: false, dataset: {type: "fightingmastery"}, isFightingmastery: true }, isClassfeature: true
lightsaberforms: { label: "SW5E.ItemTypeLightsaberFormPl", items: [], hasActions: false, dataset: {type: "lightsaberform"}, isLightsaberform: true }, },
active: { label: "SW5E.FeatureActive", items: [], hasActions: true, dataset: {type: "feat", "activation.type": "action"} }, archetype: {
passive: { label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"} } 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.levels - a.levels); classes.sort((a, b) => b.levels - a.levels);
@ -189,14 +252,13 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
_prepareItemToggleState(item) { _prepareItemToggleState(item) {
if (item.type === "power") { if (item.type === "power") {
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");
@ -211,19 +273,19 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
* Activate event listeners using the prepared sheet HTML * Activate event listeners using the prepared sheet HTML
* @param html {HTML} The prepared HTML object ready to be rendered into the DOM * @param html {HTML} The prepared HTML object ready to be rendered into the DOM
*/ */
activateListeners(html) { activateListeners(html) {
super.activateListeners(html); super.activateListeners(html);
if ( !this.options.editable ) return; if (!this.options.editable) 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));
@ -263,9 +325,9 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
}); });
// Item Delete Confirmation // Item Delete Confirmation
html.find('.item-delete').off("click"); html.find(".item-delete").off("click");
html.find('.item-delete').click(event => { html.find(".item-delete").click(event => {
let li = $(event.currentTarget).parents('.item'); let li = $(event.currentTarget).parents(".item");
let itemId = li.attr("data-item-id"); let itemId = li.attr("data-item-id");
let item = this.actor.getOwnedItem(itemId); let item = this.actor.getOwnedItem(itemId);
new Dialog({ new Dialog({
@ -274,17 +336,17 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
buttons: { buttons: {
Yes: { Yes: {
icon: '<i class="fa fa-check"></i>', icon: '<i class="fa fa-check"></i>',
label: 'Yes', label: "Yes",
callback: dlg => { callback: dlg => {
this.actor.deleteOwnedItem(itemId); this.actor.deleteOwnedItem(itemId);
} }
}, },
cancel: { cancel: {
icon: '<i class="fas fa-times"></i>', icon: '<i class="fas fa-times"></i>',
label: 'No' label: "No"
}, }
}, },
default: 'cancel' default: "cancel"
}).render(true); }).render(true);
}); });
} }
@ -299,7 +361,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":
@ -309,7 +371,6 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**
* Handle toggling the state of an Owned Item within the Actor * Handle toggling the state of an Owned Item within the Actor
* @param {Event} event The triggering click event * @param {Event} event The triggering click event
@ -353,14 +414,13 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
/** @override */ /** @override */
async _onDropItemCreate(itemData) { async _onDropItemCreate(itemData) {
// Increment the number of class levels a character instead of creating a new item // Increment the number of class levels a character instead of creating a new item
if ( itemData.type === "class" ) { if (itemData.type === "class") {
const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name); const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name);
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});
} }
@ -428,9 +488,9 @@ async function addFavorites(app, html, data) {
value: data.actor.data.powers.power9.value, value: data.actor.data.powers.power9.value,
max: data.actor.data.powers.power9.max max: data.actor.data.powers.power9.max
} }
} };
let powerCount = 0 let powerCount = 0;
let items = data.actor.items; let items = data.actor.items;
for (let item of items) { for (let item of items) {
if (item.type == "class") continue; if (item.type == "class") continue;
@ -441,24 +501,28 @@ async function addFavorites(app, html, data) {
} }
let isFav = item.flags.favtab.isFavourite; let isFav = item.flags.favtab.isFavourite;
if (app.options.editable) { if (app.options.editable) {
let favBtn = $(`<a class="item-control item-toggle item-fav ${isFav ? "active" : ""}" data-fav="${isFav}" title="${isFav ? "Remove from Favourites" : "Add to Favourites"}"><i class="fas fa-star"></i></a>`); let favBtn = $(
`<a class="item-control item-toggle item-fav ${isFav ? "active" : ""}" data-fav="${isFav}" title="${
isFav ? "Remove from Favourites" : "Add to Favourites"
}"><i class="fas fa-star"></i></a>`
);
favBtn.click(ev => { favBtn.click(ev => {
app.actor.getOwnedItem(item._id).update({ app.actor.getOwnedItem(item._id).update({
"flags.favtab.isFavourite": !item.flags.favtab.isFavourite "flags.favtab.isFavourite": !item.flags.favtab.isFavourite
}); });
}); });
html.find(`.item[data-item-id="${item._id}"]`).find('.item-controls').prepend(favBtn); html.find(`.item[data-item-id="${item._id}"]`).find(".item-controls").prepend(favBtn);
} }
if (isFav) { if (isFav) {
item.powerComps = ""; item.powerComps = "";
if (item.data.components) { if (item.data.components) {
let comps = item.data.components; let comps = item.data.components;
let v = (comps.vocal) ? "V" : ""; let v = comps.vocal ? "V" : "";
let s = (comps.somatic) ? "S" : ""; let s = comps.somatic ? "S" : "";
let m = (comps.material) ? "M" : ""; let m = comps.material ? "M" : "";
let c = (comps.concentration) ? true : false; let c = comps.concentration ? true : false;
let r = (comps.ritual) ? true : false; let r = comps.ritual ? true : false;
item.powerComps = `${v}${s}${m}`; item.powerComps = `${v}${s}${m}`;
item.powerCon = c; item.powerCon = c;
item.powerRit = r; item.powerRit = r;
@ -466,15 +530,15 @@ async function addFavorites(app, html, data) {
item.editable = app.options.editable; item.editable = app.options.editable;
switch (item.type) { switch (item.type) {
case 'feat': case "feat":
if (item.flags.favtab.sort === undefined) { if (item.flags.favtab.sort === undefined) {
item.flags.favtab.sort = (favFeats.count + 1) * 100000; // initial sort key if not present item.flags.favtab.sort = (favFeats.count + 1) * 100000; // initial sort key if not present
} }
favFeats.push(item); favFeats.push(item);
break; break;
case 'power': case "power":
if (item.data.preparation.mode) { if (item.data.preparation.mode) {
item.powerPrepMode = ` (${CONFIG.SW5E.powerPreparationModes[item.data.preparation.mode]})` item.powerPrepMode = ` (${CONFIG.SW5E.powerPreparationModes[item.data.preparation.mode]})`;
} }
if (item.data.level) { if (item.data.level) {
favPowers[item.data.level].powers.push(item); favPowers[item.data.level].powers.push(item);
@ -500,58 +564,58 @@ async function addFavorites(app, html, data) {
// html.find('.favourite .item-controls').css('flex', '0 0 22px'); // html.find('.favourite .item-controls').css('flex', '0 0 22px');
// } // }
let tabContainer = html.find('.favtabtarget'); let tabContainer = html.find(".favtabtarget");
data.favItems = favItems.length > 0 ? favItems.sort((a, b) => (a.flags.favtab.sort) - (b.flags.favtab.sort)) : false; data.favItems = favItems.length > 0 ? favItems.sort((a, b) => a.flags.favtab.sort - b.flags.favtab.sort) : false;
data.favFeats = favFeats.length > 0 ? favFeats.sort((a, b) => (a.flags.favtab.sort) - (b.flags.favtab.sort)) : false; data.favFeats = favFeats.length > 0 ? favFeats.sort((a, b) => a.flags.favtab.sort - b.flags.favtab.sort) : false;
data.favPowers = powerCount > 0 ? favPowers : false; data.favPowers = powerCount > 0 ? favPowers : false;
data.editable = app.options.editable; data.editable = app.options.editable;
await loadTemplates(['systems/sw5e/templates/actors/newActor/item.hbs']); await loadTemplates(["systems/sw5e/templates/actors/newActor/item.hbs"]);
let favtabHtml = $(await renderTemplate('systems/sw5e/templates/actors/newActor/template.hbs', data)); let favtabHtml = $(await renderTemplate("systems/sw5e/templates/actors/newActor/template.hbs", data));
favtabHtml.find('.item-name h4').click(event => app._onItemSummary(event)); favtabHtml.find(".item-name h4").click(event => app._onItemSummary(event));
if (app.options.editable) { if (app.options.editable) {
favtabHtml.find('.item-image').click(ev => app._onItemRoll(ev)); favtabHtml.find(".item-image").click(ev => app._onItemRoll(ev));
let handler = ev => app._onDragStart(ev); let handler = ev => app._onDragStart(ev);
favtabHtml.find('.item').each((i, li) => { favtabHtml.find(".item").each((i, li) => {
if (li.classList.contains("inventory-header")) return; if (li.classList.contains("inventory-header")) return;
li.setAttribute("draggable", true); li.setAttribute("draggable", true);
li.addEventListener("dragstart", handler, false); li.addEventListener("dragstart", handler, false);
}); });
//favtabHtml.find('.item-toggle').click(event => app._onToggleItem(event)); //favtabHtml.find('.item-toggle').click(event => app._onToggleItem(event));
favtabHtml.find('.item-edit').click(ev => { favtabHtml.find(".item-edit").click(ev => {
let itemId = $(ev.target).parents('.item')[0].dataset.itemId; let itemId = $(ev.target).parents(".item")[0].dataset.itemId;
app.actor.getOwnedItem(itemId).sheet.render(true); app.actor.getOwnedItem(itemId).sheet.render(true);
}); });
favtabHtml.find('.item-fav').click(ev => { favtabHtml.find(".item-fav").click(ev => {
let itemId = $(ev.target).parents('.item')[0].dataset.itemId; let itemId = $(ev.target).parents(".item")[0].dataset.itemId;
let val = !app.actor.getOwnedItem(itemId).data.flags.favtab.isFavourite let val = !app.actor.getOwnedItem(itemId).data.flags.favtab.isFavourite;
app.actor.getOwnedItem(itemId).update({ app.actor.getOwnedItem(itemId).update({
"flags.favtab.isFavourite": val "flags.favtab.isFavourite": val
}); });
}); });
// Sorting // Sorting
favtabHtml.find('.item').on('drop', ev => { favtabHtml.find(".item").on("drop", ev => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
let dropData = JSON.parse(ev.originalEvent.dataTransfer.getData('text/plain')); let dropData = JSON.parse(ev.originalEvent.dataTransfer.getData("text/plain"));
// if (dropData.actorId !== app.actor.id || dropData.data.type === 'power') return; // if (dropData.actorId !== app.actor.id || dropData.data.type === 'power') return;
if (dropData.actorId !== app.actor.id) return; if (dropData.actorId !== app.actor.id) return;
let list = null; let list = null;
if (dropData.data.type === 'feat') list = favFeats; if (dropData.data.type === "feat") list = favFeats;
else list = favItems; else list = favItems;
let dragSource = list.find(i => i._id === dropData.data._id); let dragSource = list.find(i => i._id === dropData.data._id);
let siblings = list.filter(i => i._id !== dropData.data._id); let siblings = list.filter(i => i._id !== dropData.data._id);
let targetId = ev.target.closest('.item').dataset.itemId; let targetId = ev.target.closest(".item").dataset.itemId;
let dragTarget = siblings.find(s => s._id === targetId); let dragTarget = siblings.find(s => s._id === targetId);
if (dragTarget === undefined) return; if (dragTarget === undefined) return;
const sortUpdates = SortingHelpers.performIntegerSort(dragSource, { const sortUpdates = SortingHelpers.performIntegerSort(dragSource, {
target: dragTarget, target: dragTarget,
siblings: siblings, siblings: siblings,
sortKey: 'flags.favtab.sort' sortKey: "flags.favtab.sort"
}); });
const updateData = sortUpdates.map(u => { const updateData = sortUpdates.map(u => {
const update = u.update; const update = u.update;
@ -572,63 +636,62 @@ async function addFavorites(app, html, data) {
//} //}
// try { // try {
// if (game.modules.get("betterrolls5e") && game.modules.get("betterrolls5e").active) BetterRolls.addItemContent(app.object, favtabHtml, ".item .item-name h4", ".item-properties", ".item > .rollable div"); // if (game.modules.get("betterrolls5e") && game.modules.get("betterrolls5e").active) BetterRolls.addItemContent(app.object, favtabHtml, ".item .item-name h4", ".item-properties", ".item > .rollable div");
// } // }
// catch (err) { // catch (err) {
// // Better Rolls not found! // // Better Rolls not found!
// } // }
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 => { for (const group in data.options.subTabs) {
let subgroup = event.target.closest('[data-subgroup]').getAttribute('data-subgroup'); data.options.subTabs[group].forEach(tab => {
let target = event.target.closest('[data-target]').getAttribute('data-target'); if (tab.active) {
html.find(`[data-subgroup=${subgroup}]`).removeClass('active'); html.find(`[data-subgroup=${group}][data-target=${tab.target}]`).addClass("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 => {
if(el.target == target) {
el.active = true;
} else { } else {
el.active = false; html.find(`[data-subgroup=${group}][data-target=${tab.target}]`).removeClass("active");
} }
return el; });
}) }
})
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 => {
if (el.target == target) {
el.active = true;
} else {
el.active = false;
}
return el;
});
});
} }
Hooks.on("renderActorSheet5eCharacterNew", (app, html, data) => { Hooks.on("renderActorSheet5eCharacterNew", (app, html, data) => {
addFavorites(app, html, data); addFavorites(app, html, data);
addSubTabs(app, html, data); addSubTabs(app, html, data);
}); });

View file

@ -6,22 +6,23 @@ 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 */
static get defaultOptions() { static get defaultOptions() {
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", {
contentSelector: ".sheet-body", navSelector: ".root-tabs",
initial: "attributes" contentSelector: ".sheet-body",
}], initial: "attributes"
}
]
}); });
} }
@ -32,28 +33,40 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
* @private * @private
*/ */
_prepareItems(data) { _prepareItems(data) {
// Categorize Items as Features and Powers // Categorize Items as Features and Powers
const features = { const features = {
weapons: { label: game.i18n.localize("SW5E.AttackPl"), items: [] , hasActions: true, dataset: {type: "weapon", "weapon-type": "natural"} }, weapons: {
actions: { label: game.i18n.localize("SW5E.ActionPl"), items: [] , hasActions: true, dataset: {type: "feat", "activation.type": "action"} }, label: game.i18n.localize("SW5E.AttackPl"),
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(
item.img = item.img || DEFAULT_TOKEN; (arr, item) => {
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1); item.img = item.img || DEFAULT_TOKEN;
item.hasUses = item.data.uses && (item.data.uses.max > 0); item.isStack = Number.isNumeric(item.data.quantity) && item.data.quantity !== 1;
item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false); item.hasUses = item.data.uses && item.data.uses.max > 0;
item.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0)); item.isOnCooldown = 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 arr[2].push(item); else if (item.type === "power" && ["tec"].includes(item.data.school)) arr[1].push(item);
return arr; else arr[2].push(item);
}, [[], [], []]); return arr;
},
[[], [], []]
);
// Apply item filters // Apply item filters
forcepowers = this._filterItems(forcepowers, this._filters.forcePowerbook); forcepowers = this._filterItems(forcepowers, this._filters.forcePowerbook);
@ -65,13 +78,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
@ -80,7 +92,6 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
data.techPowerbook = techPowerbook; data.techPowerbook = techPowerbook;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
@ -100,13 +111,12 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
/** @override */ /** @override */
_updateObject(event, formData) { _updateObject(event, formData) {
// Format NPC Challenge Rating // Format NPC Challenge Rating
const crs = {"1/8": 0.125, "1/4": 0.25, "1/2": 0.5}; const crs = {"1/8": 0.125, "1/4": 0.25, "1/2": 0.5};
let crv = "data.details.cr"; let crv = "data.details.cr";
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
super._updateObject(event, formData); super._updateObject(event, formData);
@ -117,7 +127,7 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
activateListeners(html) { activateListeners(html) {
super.activateListeners(html); super.activateListeners(html);
html.find(".health .rollable").click(this._onRollHPFormula.bind(this)); html.find(".health .rollable").click(this._onRollHPFormula.bind(this));
} }
@ -132,10 +142,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

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

View file

@ -3,7 +3,7 @@ import TraitSelector from "../../../apps/trait-selector.js";
import ActorSheetFlags from "../../../apps/actor-flags.js"; import ActorSheetFlags from "../../../apps/actor-flags.js";
import ActorMovementConfig from "../../../apps/movement-config.js"; import ActorMovementConfig from "../../../apps/movement-config.js";
import ActorSensesConfig from "../../../apps/senses-config.js"; import ActorSensesConfig from "../../../apps/senses-config.js";
import {SW5E} from '../../../config.js'; import {SW5E} from "../../../config.js";
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js"; import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js";
/** /**
@ -46,7 +46,7 @@ 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`;
} }
@ -54,7 +54,6 @@ export default class ActorSheet5e extends ActorSheet {
/** @override */ /** @override */
getData() { getData() {
// Basic data // Basic data
let isOwner = this.entity.owner; let isOwner = this.entity.owner;
const data = { const data = {
@ -65,8 +64,8 @@ export default class ActorSheet5e extends ActorSheet {
cssClass: isOwner ? "editable" : "locked", cssClass: isOwner ? "editable" : "locked",
isCharacter: this.entity.data.type === "character", isCharacter: this.entity.data.type === "character",
isNPC: this.entity.data.type === "npc", isNPC: this.entity.data.type === "npc",
isVehicle: this.entity.data.type === 'vehicle', isVehicle: this.entity.data.type === "vehicle",
config: CONFIG.SW5E, config: CONFIG.SW5E
}; };
// The Actor and its Items // The Actor and its Items
@ -81,7 +80,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(data.actor.data.abilities)) { for (let [a, abl] of Object.entries(data.actor.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];
@ -89,7 +88,7 @@ export default class ActorSheet5e extends ActorSheet {
// Skills // Skills
if (data.actor.data.skills) { if (data.actor.data.skills) {
for ( let [s, skl] of Object.entries(data.actor.data.skills)) { for (let [s, skl] of Object.entries(data.actor.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];
@ -113,7 +112,7 @@ export default class ActorSheet5e extends ActorSheet {
data.effects = prepareActiveEffectCategories(this.entity.effects); data.effects = prepareActiveEffectCategories(this.entity.effects);
// Return data to the sheet // Return data to the sheet
return data return data;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -125,17 +124,21 @@ 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}`]);
} }
@ -143,12 +146,12 @@ export default class ActorSheet5e extends ActorSheet {
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
@ -156,7 +159,7 @@ export default class ActorSheet5e extends ActorSheet {
return { return {
primary: `${movement.walk || 0} ${movement.units}`, primary: `${movement.walk || 0} ${movement.units}`,
special: speeds.length ? speeds.map(s => s[1]).join(", ") : "" special: speeds.length ? speeds.map(s => s[1]).join(", ") : ""
} };
} }
} }
@ -165,12 +168,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;
} }
@ -183,20 +186,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) => {
@ -205,8 +208,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";
} }
@ -227,45 +230,45 @@ export default class ActorSheet5e extends ActorSheet {
// Define some mappings // Define some mappings
const sections = { const sections = {
"atwill": -20, atwill: -20,
"innate": -10, innate: -10,
"pact": 0.5 pact: 0.5
}; };
// Label power slot uses headers // Label power slot uses headers
const useLabels = { const useLabels = {
"-20": "-", "-20": "-",
"-10": "-", "-10": "-",
"0": "&infin;" 0: "&infin;"
}; };
// Format a powerbook entry for a certain indexed level // Format a powerbook entry for a certain indexed level
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}, dataset: {type: "power", level: prepMode in sections ? 1 : i, "preparation.mode": prepMode},
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}`;
@ -274,8 +277,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
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;
registerSection("pact", sections.pact, config, { registerSection("pact", sections.pact, config, {
@ -293,9 +296,9 @@ export default class ActorSheet5e extends ActorSheet {
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, {
@ -308,7 +311,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]});
} }
@ -334,28 +337,28 @@ export default class ActorSheet5e extends ActorSheet {
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;
}); });
@ -386,59 +389,58 @@ export default class ActorSheet5e extends ActorSheet {
* @param html {HTML} The prepared HTML object ready to be rendered into the DOM * @param html {HTML} The prepared HTML object ready to be rendered into the DOM
*/ */
activateListeners(html) { activateListeners(html) {
// Activate Item Filters // Activate Item Filters
const filterLists = html.find(".filter-list"); const filterLists = html.find(".filter-list");
filterLists.each(this._initializeFilterItemList.bind(this)); filterLists.each(this._initializeFilterItemList.bind(this));
filterLists.on("click", ".filter-item", this._onToggleFilter.bind(this)); filterLists.on("click", ".filter-item", this._onToggleFilter.bind(this));
// Item summaries // Item summaries
html.find('.item .item-name.rollable h4').click(event => this._onItemSummary(event)); html.find(".item .item-name.rollable h4").click(event => this._onItemSummary(event));
// Editable Only Listeners // Editable Only Listeners
if ( this.isEditable ) { if (this.isEditable) {
// Input focus and update // Input focus and update
const inputs = html.find("input"); const inputs = html.find("input");
inputs.focus(ev => ev.currentTarget.select()); inputs.focus(ev => ev.currentTarget.select());
inputs.addBack().find('[data-dtype="Number"]').change(this._onChangeInputDelta.bind(this)); inputs.addBack().find('[data-dtype="Number"]').change(this._onChangeInputDelta.bind(this));
// Ability Proficiency // Ability Proficiency
html.find('.ability-proficiency').click(this._onToggleAbilityProficiency.bind(this)); html.find(".ability-proficiency").click(this._onToggleAbilityProficiency.bind(this));
// Toggle Skill Proficiency // Toggle Skill Proficiency
html.find('.skill-proficiency').on("click contextmenu", this._onCycleSkillProficiency.bind(this)); html.find(".skill-proficiency").on("click contextmenu", this._onCycleSkillProficiency.bind(this));
// Trait Selector // Trait Selector
html.find('.trait-selector').click(this._onTraitSelector.bind(this)); html.find(".trait-selector").click(this._onTraitSelector.bind(this));
// Configure Special Flags // Configure Special Flags
html.find('.config-button').click(this._onConfigMenu.bind(this)); html.find(".config-button").click(this._onConfigMenu.bind(this));
// Owned Item management // Owned Item management
html.find('.item-create').click(this._onItemCreate.bind(this)); html.find(".item-create").click(this._onItemCreate.bind(this));
html.find('.item-edit').click(this._onItemEdit.bind(this)); html.find(".item-edit").click(this._onItemEdit.bind(this));
html.find('.item-delete').click(this._onItemDelete.bind(this)); html.find(".item-delete").click(this._onItemDelete.bind(this));
html.find('.item-uses input').click(ev => ev.target.select()).change(this._onUsesChange.bind(this)); html
html.find('.slot-max-override').click(this._onPowerSlotOverride.bind(this)); .find(".item-uses input")
.click(ev => ev.target.select())
.change(this._onUsesChange.bind(this));
html.find(".slot-max-override").click(this._onPowerSlotOverride.bind(this));
// Active Effect management // Active Effect management
html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.entity)); html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.entity));
} }
// Owner Only Listeners // Owner Only Listeners
if ( this.actor.owner ) { if (this.actor.owner) {
// Ability Checks // Ability Checks
html.find('.ability-name').click(this._onRollAbilityTest.bind(this)); html.find(".ability-name").click(this._onRollAbilityTest.bind(this));
// Roll Skill Checks // Roll Skill Checks
html.find('.skill-name').click(this._onRollSkillCheck.bind(this)); html.find(".skill-name").click(this._onRollSkillCheck.bind(this));
// Item Rolling // Item Rolling
html.find('.item .item-image').click(event => this._onItemRoll(event)); html.find(".item .item-image").click(event => this._onItemRoll(event));
html.find('.item .item-recharge').click(event => this._onItemRecharge(event)); html.find(".item .item-recharge").click(event => this._onItemRecharge(event));
} }
// Otherwise remove rollable classes // Otherwise remove rollable classes
@ -459,8 +461,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");
} }
} }
@ -476,10 +478,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);
} }
} }
@ -494,7 +496,7 @@ export default class ActorSheet5e extends ActorSheet {
_onConfigMenu(event) { _onConfigMenu(event) {
event.preventDefault(); event.preventDefault();
const button = event.currentTarget; const button = event.currentTarget;
switch ( button.dataset.action ) { switch (button.dataset.action) {
case "movement": case "movement":
new ActorMovementConfig(this.object).render(true); new ActorMovementConfig(this.object).render(true);
break; break;
@ -524,10 +526,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
@ -538,8 +540,8 @@ export default class ActorSheet5e extends ActorSheet {
/** @override */ /** @override */
async _onDropActor(event, data) { async _onDropActor(event, data) {
const canPolymorph = game.user.isGM || (this.actor.owner && game.settings.get('sw5e', 'allowPolymorphing')); const canPolymorph = game.user.isGM || (this.actor.owner && game.settings.get("sw5e", "allowPolymorphing"));
if ( !canPolymorph ) return false; if (!canPolymorph) return false;
// Get the target actor // Get the target actor
let sourceActor = null; let sourceActor = null;
@ -549,78 +551,82 @@ export default class ActorSheet5e extends ActorSheet {
} 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'), {
content: { title: game.i18n.localize("SW5E.PolymorphPromptTitle"),
options: game.settings.get('sw5e', 'polymorphSettings'), content: {
i18n: SW5E.polymorphSettings, options: game.settings.get("sw5e", "polymorphSettings"),
isToken: this.actor.isToken i18n: SW5E.polymorphSettings,
}, isToken: this.actor.isToken
default: 'accept',
buttons: {
accept: {
icon: '<i class="fas fa-check"></i>',
label: game.i18n.localize('SW5E.PolymorphAcceptSettings'),
callback: html => this.actor.transformInto(sourceActor, rememberOptions(html))
}, },
wildshape: { default: "accept",
icon: '<i class="fas fa-paw"></i>', buttons: {
label: game.i18n.localize('SW5E.PolymorphWildShape'), accept: {
callback: html => this.actor.transformInto(sourceActor, { icon: '<i class="fas fa-check"></i>',
keepBio: true, label: game.i18n.localize("SW5E.PolymorphAcceptSettings"),
keepClass: true, callback: html => this.actor.transformInto(sourceActor, rememberOptions(html))
keepMental: true, },
mergeSaves: true, wildshape: {
mergeSkills: true, icon: '<i class="fas fa-paw"></i>',
transformTokens: rememberOptions(html).transformTokens label: game.i18n.localize("SW5E.PolymorphWildShape"),
}) callback: html =>
}, this.actor.transformInto(sourceActor, {
polymorph: { keepBio: true,
icon: '<i class="fas fa-pastafarianism"></i>', keepClass: true,
label: game.i18n.localize('SW5E.Polymorph'), keepMental: true,
callback: html => this.actor.transformInto(sourceActor, { mergeSaves: true,
transformTokens: rememberOptions(html).transformTokens mergeSkills: true,
}) transformTokens: rememberOptions(html).transformTokens
}, })
cancel: { },
icon: '<i class="fas fa-times"></i>', polymorph: {
label: game.i18n.localize('Cancel') icon: '<i class="fas fa-pastafarianism"></i>',
label: game.i18n.localize("SW5E.Polymorph"),
callback: html =>
this.actor.transformInto(sourceActor, {
transformTokens: rememberOptions(html).transformTokens
})
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: game.i18n.localize("Cancel")
}
} }
},
{
classes: ["dialog", "sw5e"],
width: 600,
template: "systems/sw5e/templates/apps/polymorph-prompt.html"
} }
}, { ).render(true);
classes: ['dialog', 'sw5e'],
width: 600,
template: 'systems/sw5e/templates/apps/polymorph-prompt.html'
}).render(true);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
async _onDropItemCreate(itemData) { async _onDropItemCreate(itemData) {
// Create a Consumable power scroll on the Inventory tab // Create a Consumable power scroll on the Inventory tab
if ( (itemData.type === "power") && (this._tabs[0].active === "inventory") ) { if (itemData.type === "power" && this._tabs[0].active === "inventory") {
const scroll = await Item5e.createScrollFromPower(itemData); const scroll = await Item5e.createScrollFromPower(itemData);
itemData = scroll.data; itemData = scroll.data;
} }
// Ignore certain statuses // Ignore certain statuses
if ( itemData.data ) { if (itemData.data) {
["attunement", "equipped", "proficient", "prepared"].forEach(k => delete itemData.data[k]); ["attunement", "equipped", "proficient", "prepared"].forEach(k => delete itemData.data[k]);
} }
@ -635,7 +641,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;
@ -660,12 +666,12 @@ export default class ActorSheet5e extends ActorSheet {
* @private * @private
*/ */
async _onUsesChange(event) { async _onUsesChange(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.getOwnedItem(itemId); const item = this.actor.getOwnedItem(itemId);
const uses = Math.clamped(0, parseInt(event.target.value), item.data.data.uses.max); const uses = Math.clamped(0, parseInt(event.target.value), item.data.data.uses.max);
event.target.value = uses; event.target.value = uses;
return item.update({ 'data.uses.value': uses }); return item.update({"data.uses.value": uses});
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -693,7 +699,7 @@ export default class ActorSheet5e extends ActorSheet {
const itemId = event.currentTarget.closest(".item").dataset.itemId; const itemId = event.currentTarget.closest(".item").dataset.itemId;
const item = this.actor.getOwnedItem(itemId); const item = this.actor.getOwnedItem(itemId);
return item.rollRecharge(); return item.rollRecharge();
}; }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -704,11 +710,11 @@ export default class ActorSheet5e extends ActorSheet {
_onItemSummary(event) { _onItemSummary(event) {
event.preventDefault(); event.preventDefault();
let li = $(event.currentTarget).parents(".item"), let li = $(event.currentTarget).parents(".item"),
item = this.actor.getOwnedItem(li.data("item-id")), item = this.actor.getOwnedItem(li.data("item-id")),
chatData = item.getChatData({secrets: this.actor.owner}); chatData = item.getChatData({secrets: this.actor.owner});
// 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 {
@ -820,7 +826,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);
this.render(); this.render();
} }
@ -837,8 +843,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};
new TraitSelector(this.actor, options).render(true) new TraitSelector(this.actor, options).render(true);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -848,13 +854,13 @@ export default class ActorSheet5e extends ActorSheet {
let buttons = super._getHeaderButtons(); let buttons = super._getHeaderButtons();
// Add button to revert polymorph // Add button to revert polymorph
if ( !this.actor.isPolymorphed || this.actor.isToken ) return buttons; if (!this.actor.isPolymorphed || this.actor.isToken) return buttons;
buttons.unshift({ buttons.unshift({
label: 'SW5E.PolymorphRestoreTransformation', label: "SW5E.PolymorphRestoreTransformation",
class: "restore-transformation", class: "restore-transformation",
icon: "fas fa-backward", icon: "fas fa-backward",
onclick: ev => this.actor.revertOriginalForm() onclick: ev => this.actor.revertOriginalForm()
}); });
return buttons; return buttons;
} }
} }

View file

@ -7,13 +7,12 @@ 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}
*/ */
static get defaultOptions() { static get defaultOptions() {
return mergeObject(super.defaultOptions, { return mergeObject(super.defaultOptions, {
classes: ["sw5e", "sheet", "actor", "character"], classes: ["sw5e", "sheet", "actor", "character"],
width: 720, width: 720,
height: 736 height: 736
@ -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]);
@ -46,9 +45,11 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
// Experience Tracking // Experience Tracking
sheetData["disableExperience"] = game.settings.get("sw5e", "disableExperienceTracking"); sheetData["disableExperience"] = game.settings.get("sw5e", "disableExperienceTracking");
sheetData["classLabels"] = this.actor.itemTypes.class.map(c => c.name).join(", "); sheetData["classLabels"] = this.actor.itemTypes.class.map(c => c.name).join(", ");
sheetData["multiclassLabels"] = this.actor.itemTypes.class.map(c => { sheetData["multiclassLabels"] = this.actor.itemTypes.class
return [c.data.data.archetype, c.name, c.data.data.levels].filterJoin(' ') .map(c => {
}).join(', '); return [c.data.data.archetype, c.name, c.data.data.levels].filterJoin(" ");
})
.join(", ");
// Return data for rendering // Return data for rendering
return sheetData; return sheetData;
@ -61,59 +62,72 @@ 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.img = item.img || DEFAULT_TOKEN;
item.isStack = Number.isNumeric(item.data.quantity) && item.data.quantity !== 1;
item.attunement = {
[CONFIG.SW5E.attunementTypes.REQUIRED]: {
icon: "fa-sun",
cls: "not-attuned",
title: "SW5E.AttunementRequired"
},
[CONFIG.SW5E.attunementTypes.ATTUNED]: {
icon: "fa-sun",
cls: "attuned",
title: "SW5E.AttunementAttuned"
}
}[item.data.attunement];
// Item details // Item usage
item.img = item.img || DEFAULT_TOKEN; item.hasUses = item.data.uses && item.data.uses.max > 0;
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1); item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && item.data.recharge.charged === false;
item.attunement = { item.isDepleted = item.isOnCooldown && item.data.uses.per && item.data.uses.value > 0;
[CONFIG.SW5E.attunementTypes.REQUIRED]: { item.hasTarget = !!item.data.target && !["none", ""].includes(item.data.target.type);
icon: "fa-sun",
cls: "not-attuned",
title: "SW5E.AttunementRequired"
},
[CONFIG.SW5E.attunementTypes.ATTUNED]: {
icon: "fa-sun",
cls: "attuned",
title: "SW5E.AttunementAttuned"
}
}[item.data.attunement];
// Item usage // Item toggle state
item.hasUses = item.data.uses && (item.data.uses.max > 0); this._prepareItemToggleState(item);
item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false);
item.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0));
item.hasTarget = !!item.data.target && !(["none",""].includes(item.data.target.type));
// Item toggle state // Classify items into types
this._prepareItemToggleState(item); if (item.type === "power") arr[1].push(item);
else if (item.type === "feat") arr[2].push(item);
// Classify items into types else if (item.type === "class") arr[3].push(item);
if ( item.type === "power" ) arr[1].push(item); else if (item.type === "species") arr[4].push(item);
else if ( item.type === "feat" ) arr[2].push(item); else if (item.type === "archetype") arr[5].push(item);
else if ( item.type === "class" ) arr[3].push(item); else if (item.type === "classfeature") arr[6].push(item);
else if ( item.type === "species" ) arr[4].push(item); else if (item.type === "background") arr[7].push(item);
else if ( item.type === "archetype" ) arr[5].push(item); else if (item.type === "fightingstyle") arr[8].push(item);
else if ( item.type === "classfeature" ) arr[6].push(item); else if (item.type === "fightingmastery") arr[9].push(item);
else if ( item.type === "background" ) arr[7].push(item); else if (item.type === "lightsaberform") arr[10].push(item);
else if ( item.type === "fightingstyle" ) arr[8].push(item); else if (Object.keys(inventory).includes(item.type)) arr[0].push(item);
else if ( item.type === "fightingmastery" ) arr[9].push(item); return arr;
else if ( item.type === "lightsaberform" ) arr[10].push(item); },
else if ( Object.keys(inventory).includes(item.type ) ) arr[0].push(item); [[], [], [], [], [], [], [], [], [], [], []]
return arr; );
}, [[], [], [], [], [], [], [], [], [], [], []]);
// Apply active item filters // Apply active item filters
items = this._filterItems(items, this._filters.inventory); items = this._filterItems(items, this._filters.inventory);
@ -121,7 +135,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);
@ -131,24 +145,71 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
// Organize Powerbook and count the number of prepared powers (excluding always, at will, etc...) // Organize Powerbook and count the number of prepared powers (excluding always, at will, etc...)
const powerbook = this._preparePowerbook(data, powers); const powerbook = this._preparePowerbook(data, powers);
const nPrepared = powers.filter(s => { const nPrepared = powers.filter(s => {
return (s.data.level > 0) && (s.data.preparation.mode === "prepared") && s.data.preparation.prepared; return s.data.level > 0 && s.data.preparation.mode === "prepared" && s.data.preparation.prepared;
}).length; }).length;
// Organize Features // Organize Features
const features = { const features = {
classes: { label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true }, classes: {label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true},
classfeatures: { label: "SW5E.ItemTypeClassFeats", items: [], hasActions: true, dataset: {type: "classfeature"}, isClassfeature: true }, classfeatures: {
archetype: { label: "SW5E.ItemTypeArchetype", items: [], hasActions: false, dataset: {type: "archetype"}, isArchetype: true }, label: "SW5E.ItemTypeClassFeats",
species: { label: "SW5E.ItemTypeSpecies", items: [], hasActions: false, dataset: {type: "species"}, isSpecies: true }, items: [],
background: { label: "SW5E.ItemTypeBackground", items: [], hasActions: false, dataset: {type: "background"}, isBackground: true }, hasActions: true,
fightingstyles: { label: "SW5E.ItemTypeFightingStylePl", items: [], hasActions: false, dataset: {type: "fightingstyle"}, isFightingstyle: true }, dataset: {type: "classfeature"},
fightingmasteries: { label: "SW5E.ItemTypeFightingMasteryPl", items: [], hasActions: false, dataset: {type: "fightingmastery"}, isFightingmastery: true }, isClassfeature: true
lightsaberforms: { label: "SW5E.ItemTypeLightsaberFormPl", items: [], hasActions: false, dataset: {type: "lightsaberform"}, isLightsaberform: true }, },
active: { label: "SW5E.FeatureActive", items: [], hasActions: true, dataset: {type: "feat", "activation.type": "action"} }, archetype: {
passive: { label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"} } 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.levels - a.levels); classes.sort((a, b) => b.levels - a.levels);
@ -178,14 +239,13 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
_prepareItemToggleState(item) { _prepareItemToggleState(item) {
if (item.type === "power") { if (item.type === "power") {
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");
@ -200,16 +260,16 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
* Activate event listeners using the prepared sheet HTML * Activate event listeners using the prepared sheet HTML
* @param html {HTML} The prepared HTML object ready to be rendered into the DOM * @param html {HTML} The prepared HTML object ready to be rendered into the DOM
*/ */
activateListeners(html) { activateListeners(html) {
super.activateListeners(html); super.activateListeners(html);
if ( !this.options.editable ) return; if (!this.options.editable) return;
// Item State Toggling // Item State Toggling
html.find('.item-toggle').click(this._onToggleItem.bind(this)); html.find(".item-toggle").click(this._onToggleItem.bind(this));
// Short and Long Rest // Short and Long Rest
html.find('.short-rest').click(this._onShortRest.bind(this)); html.find(".short-rest").click(this._onShortRest.bind(this));
html.find('.long-rest').click(this._onLongRest.bind(this)); html.find(".long-rest").click(this._onLongRest.bind(this));
// Rollable sheet actions // Rollable sheet actions
html.find(".rollable[data-action]").click(this._onSheetAction.bind(this)); html.find(".rollable[data-action]").click(this._onSheetAction.bind(this));
@ -225,7 +285,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":
@ -278,14 +338,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});
} }
@ -295,4 +354,4 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
// Default drop handling if levels were not added // Default drop handling if levels were not added
super._onDropItemCreate(itemData); super._onDropItemCreate(itemData);
} }
} }

View file

@ -6,10 +6,9 @@ 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, {
classes: ["sw5e", "sheet", "actor", "npc"], classes: ["sw5e", "sheet", "actor", "npc"],
width: 600, width: 600,
height: 680 height: 680
@ -23,27 +22,39 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
* @private * @private
*/ */
_prepareItems(data) { _prepareItems(data) {
// Categorize Items as Features and Powers // Categorize Items as Features and Powers
const features = { const features = {
weapons: { label: game.i18n.localize("SW5E.AttackPl"), items: [] , hasActions: true, dataset: {type: "weapon", "weapon-type": "natural"} }, weapons: {
actions: { label: game.i18n.localize("SW5E.ActionPl"), items: [] , hasActions: true, dataset: {type: "feat", "activation.type": "action"} }, label: game.i18n.localize("SW5E.AttackPl"),
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(
item.img = item.img || DEFAULT_TOKEN; (arr, item) => {
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1); item.img = item.img || DEFAULT_TOKEN;
item.hasUses = item.data.uses && (item.data.uses.max > 0); item.isStack = Number.isNumeric(item.data.quantity) && item.data.quantity !== 1;
item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false); item.hasUses = item.data.uses && item.data.uses.max > 0;
item.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0)); item.isOnCooldown = 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);
else arr[1].push(item); if (item.type === "power") arr[0].push(item);
return arr; else arr[1].push(item);
}, [[], []]); return arr;
},
[[], []]
);
// Apply item filters // Apply item filters
powers = this._filterItems(powers, this._filters.powerbook); powers = this._filterItems(powers, this._filters.powerbook);
@ -53,13 +64,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
@ -67,7 +77,6 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
data.powerbook = powerbook; data.powerbook = powerbook;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
@ -87,13 +96,12 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
/** @override */ /** @override */
_updateObject(event, formData) { _updateObject(event, formData) {
// Format NPC Challenge Rating // Format NPC Challenge Rating
const crs = {"1/8": 0.125, "1/4": 0.25, "1/2": 0.5}; const crs = {"1/8": 0.125, "1/4": 0.25, "1/2": 0.5};
let crv = "data.details.cr"; let crv = "data.details.cr";
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
super._updateObject(event, formData); super._updateObject(event, formData);
@ -104,7 +112,7 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
activateListeners(html) { activateListeners(html) {
super.activateListeners(html); super.activateListeners(html);
html.find(".health .rollable").click(this._onRollHPFormula.bind(this)); html.find(".health .rollable").click(this._onRollHPFormula.bind(this));
} }
@ -119,7 +127,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,7 +25,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
*/ */
static get newCargo() { static get newCargo() {
return { return {
name: '', name: "",
quantity: 1 quantity: 1
}; };
} }
@ -40,7 +40,6 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
* @private * @private
*/ */
_computeEncumbrance(totalWeight, actorData) { _computeEncumbrance(totalWeight, actorData) {
// Compute currency weight // Compute currency weight
const totalCoins = Object.values(actorData.data.currency).reduce((acc, denom) => acc + denom, 0); const totalCoins = Object.values(actorData.data.currency).reduce((acc, denom) => acc + denom, 0);
totalWeight += totalCoins / CONFIG.SW5E.encumbrance.currencyPerWeight; totalWeight += totalCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
@ -57,7 +56,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);
} }
@ -69,25 +68,24 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
* @private * @private
*/ */
_prepareCrewedItem(item) { _prepareCrewedItem(item) {
// Determine crewed status // Determine crewed status
const isCrewed = item.data.crewed; const isCrewed = item.data.crewed;
item.toggleClass = isCrewed ? 'active' : ''; item.toggleClass = isCrewed ? "active" : "";
item.toggleTitle = game.i18n.localize(`SW5E.${isCrewed ? 'Crewed' : 'Uncrewed'}`); item.toggleTitle = game.i18n.localize(`SW5E.${isCrewed ? "Crewed" : "Uncrewed"}`);
// Handle crew actions // Handle crew actions
if (item.type === 'feat' && item.data.activation.type === 'crew') { if (item.type === "feat" && item.data.activation.type === "crew") {
item.crew = item.data.activation.cost; item.crew = item.data.activation.cost;
item.cover = game.i18n.localize(`SW5E.${item.data.cover ? 'CoverTotal' : 'None'}`); item.cover = game.i18n.localize(`SW5E.${item.data.cover ? "CoverTotal" : "None"}`);
if (item.data.cover === .5) item.cover = '½'; if (item.data.cover === 0.5) item.cover = "½";
else if (item.data.cover === .75) item.cover = '¾'; else if (item.data.cover === 0.75) item.cover = "¾";
else if (item.data.cover === null) item.cover = '—'; else if (item.data.cover === null) item.cover = "—";
if (item.crew < 1 || item.crew === null) item.crew = '—'; if (item.crew < 1 || item.crew === null) item.crew = "—";
} }
// Prepare vehicle weapons // Prepare vehicle weapons
if (item.type === 'equipment' || item.type === 'weapon') { if (item.type === "equipment" || item.type === "weapon") {
item.threshold = item.data.hp.dt ? item.data.hp.dt : '—'; item.threshold = item.data.hp.dt ? item.data.hp.dt : "—";
} }
} }
@ -98,128 +96,140 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
* @private * @private
*/ */
_prepareItems(data) { _prepareItems(data) {
const cargoColumns = [{ const cargoColumns = [
label: game.i18n.localize('SW5E.Quantity'), {
css: 'item-qty', label: game.i18n.localize("SW5E.Quantity"),
property: 'quantity', css: "item-qty",
editable: 'Number' property: "quantity",
}]; editable: "Number"
}
];
const equipmentColumns = [{ const equipmentColumns = [
label: game.i18n.localize('SW5E.Quantity'), {
css: 'item-qty', label: game.i18n.localize("SW5E.Quantity"),
property: 'data.quantity' css: "item-qty",
}, { property: "data.quantity"
label: game.i18n.localize('SW5E.AC'), },
css: 'item-ac', {
property: 'data.armor.value' label: game.i18n.localize("SW5E.AC"),
}, { css: "item-ac",
label: game.i18n.localize('SW5E.HP'), property: "data.armor.value"
css: 'item-hp', },
property: 'data.hp.value', {
editable: 'Number' label: game.i18n.localize("SW5E.HP"),
}, { css: "item-hp",
label: game.i18n.localize('SW5E.Threshold'), property: "data.hp.value",
css: 'item-threshold', editable: "Number"
property: 'threshold' },
}]; {
label: game.i18n.localize("SW5E.Threshold"),
css: "item-threshold",
property: "threshold"
}
];
const features = { const features = {
actions: { actions: {
label: game.i18n.localize('SW5E.ActionPl'), label: game.i18n.localize("SW5E.ActionPl"),
items: [], items: [],
crewable: true, crewable: true,
dataset: {type: 'feat', 'activation.type': 'crew'}, dataset: {type: "feat", "activation.type": "crew"},
columns: [{ columns: [
label: game.i18n.localize('SW5E.VehicleCrew'), {
css: 'item-crew', label: game.i18n.localize("SW5E.VehicleCrew"),
property: 'crew' css: "item-crew",
}, { property: "crew"
label: game.i18n.localize('SW5E.Cover'), },
css: 'item-cover', {
property: 'cover' label: game.i18n.localize("SW5E.Cover"),
}] css: "item-cover",
property: "cover"
}
]
}, },
equipment: { equipment: {
label: game.i18n.localize('SW5E.ItemTypeEquipment'), label: game.i18n.localize("SW5E.ItemTypeEquipment"),
items: [], items: [],
crewable: true, crewable: true,
dataset: {type: 'equipment', 'armor.type': 'vehicle'}, dataset: {type: "equipment", "armor.type": "vehicle"},
columns: equipmentColumns columns: equipmentColumns
}, },
passive: { passive: {
label: game.i18n.localize('SW5E.Features'), label: game.i18n.localize("SW5E.Features"),
items: [], items: [],
dataset: {type: 'feat'} dataset: {type: "feat"}
}, },
reactions: { reactions: {
label: game.i18n.localize('SW5E.ReactionPl'), label: game.i18n.localize("SW5E.ReactionPl"),
items: [], items: [],
dataset: {type: 'feat', 'activation.type': 'reaction'} dataset: {type: "feat", "activation.type": "reaction"}
}, },
weapons: { weapons: {
label: game.i18n.localize('SW5E.ItemTypeWeaponPl'), label: game.i18n.localize("SW5E.ItemTypeWeaponPl"),
items: [], items: [],
crewable: true, crewable: true,
dataset: {type: 'weapon', 'weapon-type': 'siege'}, dataset: {type: "weapon", "weapon-type": "siege"},
columns: equipmentColumns columns: equipmentColumns
} }
}; };
const cargo = { const cargo = {
crew: { crew: {
label: game.i18n.localize('SW5E.VehicleCrew'), label: game.i18n.localize("SW5E.VehicleCrew"),
items: data.data.cargo.crew, items: data.data.cargo.crew,
css: 'cargo-row crew', css: "cargo-row crew",
editableName: true, editableName: true,
dataset: {type: 'crew'}, dataset: {type: "crew"},
columns: cargoColumns columns: cargoColumns
}, },
passengers: { passengers: {
label: game.i18n.localize('SW5E.VehiclePassengers'), label: game.i18n.localize("SW5E.VehiclePassengers"),
items: data.data.cargo.passengers, items: data.data.cargo.passengers,
css: 'cargo-row passengers', css: "cargo-row passengers",
editableName: true, editableName: true,
dataset: {type: 'passengers'}, dataset: {type: "passengers"},
columns: cargoColumns columns: cargoColumns
}, },
cargo: { cargo: {
label: game.i18n.localize('SW5E.VehicleCargo'), label: game.i18n.localize("SW5E.VehicleCargo"),
items: [], items: [],
dataset: {type: 'loot'}, dataset: {type: "loot"},
columns: [{ columns: [
label: game.i18n.localize('SW5E.Quantity'), {
css: 'item-qty', label: game.i18n.localize("SW5E.Quantity"),
property: 'data.quantity', css: "item-qty",
editable: 'Number' property: "data.quantity",
}, { editable: "Number"
label: game.i18n.localize('SW5E.Price'), },
css: 'item-price', {
property: 'data.price', label: game.i18n.localize("SW5E.Price"),
editable: 'Number' css: "item-price",
}, { property: "data.price",
label: game.i18n.localize('SW5E.Weight'), editable: "Number"
css: 'item-weight', },
property: 'data.weight', {
editable: 'Number' label: game.i18n.localize("SW5E.Weight"),
}] css: "item-weight",
property: "data.weight",
editable: "Number"
}
]
} }
}; };
let totalWeight = 0; let totalWeight = 0;
for (const item of data.items) { for (const item of data.items) {
this._prepareCrewedItem(item); this._prepareCrewedItem(item);
if (item.type === 'weapon') features.weapons.items.push(item); if (item.type === "weapon") features.weapons.items.push(item);
else if (item.type === 'equipment') features.equipment.items.push(item); else if (item.type === "equipment") features.equipment.items.push(item);
else if (item.type === 'loot') { else if (item.type === "loot") {
totalWeight += (item.data.weight || 0) * item.data.quantity; totalWeight += (item.data.weight || 0) * item.data.quantity;
cargo.cargo.items.push(item); cargo.cargo.items.push(item);
} } else if (item.type === "feat") {
else if (item.type === 'feat') { if (!item.data.activation.type || item.data.activation.type === "none") {
if (!item.data.activation.type || item.data.activation.type === 'none') {
features.passive.items.push(item); features.passive.items.push(item);
} } else if (item.data.activation.type === "reaction") features.reactions.items.push(item);
else if (item.data.activation.type === 'reaction') features.reactions.items.push(item);
else features.actions.items.push(item); else features.actions.items.push(item);
} }
} }
@ -238,21 +248,24 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
super.activateListeners(html); super.activateListeners(html);
if (!this.options.editable) return; if (!this.options.editable) return;
html.find('.item-toggle').click(this._onToggleItem.bind(this)); html.find(".item-toggle").click(this._onToggleItem.bind(this));
html.find('.item-hp input') html
.find(".item-hp input")
.click(evt => evt.target.select()) .click(evt => evt.target.select())
.change(this._onHPChange.bind(this)); .change(this._onHPChange.bind(this));
html.find('.item:not(.cargo-row) input[data-property]') html
.find(".item:not(.cargo-row) input[data-property]")
.click(evt => evt.target.select()) .click(evt => evt.target.select())
.change(this._onEditInSheet.bind(this)); .change(this._onEditInSheet.bind(this));
html.find('.cargo-row input') html
.find(".cargo-row input")
.click(evt => evt.target.select()) .click(evt => evt.target.select())
.change(this._onCargoRowChange.bind(this)); .change(this._onCargoRowChange.bind(this));
if (this.actor.data.data.attributes.actions.stations) { if (this.actor.data.data.attributes.actions.stations) {
html.find('.counter.actions, .counter.action-thresholds').hide(); html.find(".counter.actions, .counter.action-thresholds").hide();
} }
} }
@ -267,9 +280,9 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
_onCargoRowChange(event) { _onCargoRowChange(event) {
event.preventDefault(); event.preventDefault();
const target = event.currentTarget; const target = event.currentTarget;
const row = target.closest('.item'); const row = target.closest(".item");
const idx = Number(row.dataset.itemId); const idx = Number(row.dataset.itemId);
const property = row.classList.contains('crew') ? 'crew' : 'passengers'; const property = row.classList.contains("crew") ? "crew" : "passengers";
// Get the cargo entry // Get the cargo entry
const cargo = duplicate(this.actor.data.data.cargo[property]); const cargo = duplicate(this.actor.data.data.cargo[property]);
@ -277,10 +290,10 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
if (!entry) return null; if (!entry) return null;
// Update the cargo value // Update the cargo value
const key = target.dataset.property || 'name'; const key = target.dataset.property || "name";
const type = target.dataset.dtype; const type = target.dataset.dtype;
let value = target.value; let value = target.value;
if (type === 'Number') value = Number(value); if (type === "Number") value = Number(value);
entry[key] = value; entry[key] = value;
// Perform the Actor update // Perform the Actor update
@ -297,14 +310,18 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
*/ */
_onEditInSheet(event) { _onEditInSheet(event) {
event.preventDefault(); event.preventDefault();
const itemID = event.currentTarget.closest('.item').dataset.itemId; const itemID = event.currentTarget.closest(".item").dataset.itemId;
const item = this.actor.items.get(itemID); const item = this.actor.items.get(itemID);
const property = event.currentTarget.dataset.property; const property = event.currentTarget.dataset.property;
const type = event.currentTarget.dataset.dtype; const type = event.currentTarget.dataset.dtype;
let value = event.currentTarget.value; let value = event.currentTarget.value;
switch (type) { switch (type) {
case 'Number': value = parseInt(value); break; case "Number":
case 'Boolean': value = value === 'true'; break; value = parseInt(value);
break;
case "Boolean":
value = value === "true";
break;
} }
return item.update({[`${property}`]: value}); return item.update({[`${property}`]: value});
} }
@ -321,7 +338,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
event.preventDefault(); event.preventDefault();
const target = event.currentTarget; const target = event.currentTarget;
const type = target.dataset.type; const type = target.dataset.type;
if (type === 'crew' || type === 'passengers') { if (type === "crew" || type === "passengers") {
const cargo = duplicate(this.actor.data.data.cargo[type]); const cargo = duplicate(this.actor.data.data.cargo[type]);
cargo.push(this.constructor.newCargo); cargo.push(this.constructor.newCargo);
return this.actor.update({[`data.cargo.${type}`]: cargo}); return this.actor.update({[`data.cargo.${type}`]: cargo});
@ -339,10 +356,10 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
*/ */
_onItemDelete(event) { _onItemDelete(event) {
event.preventDefault(); event.preventDefault();
const row = event.currentTarget.closest('.item'); const row = event.currentTarget.closest(".item");
if (row.classList.contains('cargo-row')) { if (row.classList.contains("cargo-row")) {
const idx = Number(row.dataset.itemId); const idx = Number(row.dataset.itemId);
const type = row.classList.contains('crew') ? 'crew' : 'passengers'; const type = row.classList.contains("crew") ? "crew" : "passengers";
const cargo = duplicate(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx); const cargo = duplicate(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx);
return this.actor.update({[`data.cargo.${type}`]: cargo}); return this.actor.update({[`data.cargo.${type}`]: cargo});
} }
@ -360,11 +377,11 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
*/ */
_onHPChange(event) { _onHPChange(event) {
event.preventDefault(); event.preventDefault();
const itemID = event.currentTarget.closest('.item').dataset.itemId; const itemID = event.currentTarget.closest(".item").dataset.itemId;
const item = this.actor.items.get(itemID); const item = this.actor.items.get(itemID);
const hp = Math.clamped(0, parseInt(event.currentTarget.value), item.data.data.hp.max); const hp = Math.clamped(0, parseInt(event.currentTarget.value), item.data.data.hp.max);
event.currentTarget.value = hp; event.currentTarget.value = hp;
return item.update({'data.hp.value': hp}); return item.update({"data.hp.value": hp});
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -377,9 +394,9 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
*/ */
_onToggleItem(event) { _onToggleItem(event) {
event.preventDefault(); event.preventDefault();
const itemID = event.currentTarget.closest('.item').dataset.itemId; const itemID = event.currentTarget.closest(".item").dataset.itemId;
const item = this.actor.items.get(itemID); const item = this.actor.items.get(itemID);
const crewed = !!item.data.data.crewed; const crewed = !!item.data.data.crewed;
return item.update({'data.crewed': !crewed}); return item.update({"data.crewed": !crewed});
} }
}; }

View file

@ -3,7 +3,7 @@
* @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;
@ -34,7 +34,7 @@ export default class AbilityUseDialog extends Dialog {
const quantity = itemData.quantity || 0; const quantity = itemData.quantity || 0;
const recharge = itemData.recharge || {}; const recharge = itemData.recharge || {};
const recharges = !!recharge.value; const recharges = !!recharge.value;
const sufficientUses = (quantity > 0 && !uses.value) || uses.value > 0; const sufficientUses = (quantity > 0 && !uses.value) || uses.value > 0;
// Prepare dialog form data // Prepare dialog form data
const data = { const data = {
@ -49,7 +49,7 @@ export default class AbilityUseDialog extends Dialog {
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);
@ -57,7 +57,7 @@ export default class AbilityUseDialog extends Dialog {
// Create the Dialog and return data as a Promise // Create the Dialog and return data as a Promise
const icon = data.isPower ? "fa-magic" : "fa-fist-raised"; const icon = data.isPower ? "fa-magic" : "fa-fist-raised";
const label = game.i18n.localize("SW5E.AbilityUse" + (data.isPower ? "Cast" : "Use")); const label = game.i18n.localize("SW5E.AbilityUse" + (data.isPower ? "Cast" : "Use"));
return new Promise((resolve) => { return new Promise(resolve => {
const dlg = new this(item, { const dlg = new this(item, {
title: `${item.name}: Usage Configuration`, title: `${item.name}: Usage Configuration`,
content: html, content: html,
@ -87,14 +87,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 +101,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)
const label = CONFIG.SW5E.powerLevels[i]; .reduce((arr, i) => {
const l = actorData.powers["power"+i] || {tmax: 0, toverride: null}; if (i < lvl) return arr;
let max = parseInt(l.override || l.tmax || 0); const label = CONFIG.SW5E.powerLevels[i];
let slots = Math.clamped(parseInt(l.tvalue || 0), 0, max); const l = actorData.powers["power" + i] || {tmax: 0, toverride: null};
if ( max > 0 ) lmax = i; let max = parseInt(l.override || l.tmax || 0);
if ((max > 0) && (slots > 0) && (points > i)){ let slots = Math.clamped(parseInt(l.tvalue || 0), 0, max);
arr.push({ if (max > 0) lmax = i;
level: i, if (max > 0 && slots > 0 && points > i) {
label: i > 0 ? game.i18n.format('SW5E.PowerLevelSlot', {level: label, n: slots}) : label, arr.push({
canCast: max > 0, level: i,
hasSlots: slots > 0 label: i > 0 ? game.i18n.format("SW5E.PowerLevelSlot", {level: label, n: slots}) : label,
}); canCast: max > 0,
} hasSlots: slots > 0
return arr; });
}, []).filter(sl => sl.level <= lmax); }
return arr;
}, [])
.filter(sl => sl.level <= lmax);
} }
const canCast = powerLevels.some(l => l.hasSlots); const canCast = powerLevels.some(l => l.hasSlots);
if ( !canCast ) data.errors.push(game.i18n.format("SW5E.PowerCastNoSlots", { if (!canCast)
level: CONFIG.SW5E.powerLevels[lvl], data.errors.push(
name: data.item.name game.i18n.format("SW5E.PowerCastNoSlots", {
})); level: CONFIG.SW5E.powerLevels[lvl],
name: data.item.name
})
);
// Merge power casting data // Merge power casting data
return mergeObject(data, { isPower: true, consumePowerSlot, powerLevels }); return mergeObject(data, {isPower: true, consumePowerSlot, powerLevels});
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -179,27 +182,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: item.type, type: item.type
}) });
} }
// 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: item.data.consumableType, type: item.data.consumableType,
value: uses.value, value: uses.value,
@ -222,7 +224,5 @@ export default class AbilityUseDialog extends Dialog {
/* -------------------------------------------- */ /* -------------------------------------------- */
static _handleSubmit(formData, item) { static _handleSubmit(formData, item) {}
}
} }

View file

@ -7,7 +7,7 @@ export default class ActorSheetFlags extends BaseEntitySheet {
const options = super.defaultOptions; const options = super.defaultOptions;
return mergeObject(options, { return mergeObject(options, {
id: "actor-flags", id: "actor-flags",
classes: ["sw5e"], classes: ["sw5e"],
template: "systems/sw5e/templates/apps/actor-flags.html", template: "systems/sw5e/templates/apps/actor-flags.html",
width: 500, width: 500,
closeOnSubmit: true closeOnSubmit: true
@ -18,7 +18,7 @@ export default class ActorSheetFlags extends BaseEntitySheet {
/** @override */ /** @override */
get title() { get title() {
return `${game.i18n.localize('SW5E.FlagsTitle')}: ${this.object.name}`; return `${game.i18n.localize("SW5E.FlagsTitle")}: ${this.object.name}`;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -42,12 +42,12 @@ export default class ActorSheetFlags extends BaseEntitySheet {
_getFlags() { _getFlags() {
const flags = {}; const flags = {};
const baseData = this.entity._data; const baseData = this.entity._data;
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 = duplicate(v); let flag = duplicate(v);
flag.type = v.type.name; flag.type = v.type.name;
flag.isCheckbox = v.type === Boolean; flag.isCheckbox = v.type === Boolean;
flag.isSelect = v.hasOwnProperty('choices'); flag.isSelect = v.hasOwnProperty("choices");
flag.value = getProperty(baseData.flags, `sw5e.${k}`); flag.value = getProperty(baseData.flags, `sw5e.${k}`);
flags[v.section][`flags.sw5e.${k}`] = flag; flags[v.section][`flags.sw5e.${k}`] = flag;
} }
@ -80,7 +80,7 @@ export default class ActorSheetFlags extends BaseEntitySheet {
{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;
@ -97,11 +97,11 @@ export default class ActorSheetFlags extends BaseEntitySheet {
let unset = false; let unset = false;
const flags = updateData.flags.sw5e; const flags = updateData.flags.sw5e;
//clone flags to dnd5e for module compatability //clone flags to dnd5e for module compatability
updateData.flags.dnd5e = updateData.flags.sw5e updateData.flags.dnd5e = updateData.flags.sw5e;
for ( let [k, v] of Object.entries(flags) ) { for (let [k, v] of Object.entries(flags)) {
if ( [undefined, null, "", false, 0].includes(v) ) { if ([undefined, null, "", false, 0].includes(v)) {
delete flags[k]; delete flags[k];
if ( hasProperty(actor._data.flags, `sw5e.${k}`) ) { if (hasProperty(actor._data.flags, `sw5e.${k}`)) {
unset = true; unset = true;
flags[`-=${k}`] = null; flags[`-=${k}`] = null;
} }
@ -109,8 +109,8 @@ export default class ActorSheetFlags extends BaseEntitySheet {
} }
// 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

@ -24,8 +24,8 @@ export default class LongRestDialog extends Dialog {
getData() { getData() {
const data = super.getData(); const data = super.getData();
const variant = game.settings.get("sw5e", "restVariant"); const variant = game.settings.get("sw5e", "restVariant");
data.promptNewDay = variant !== "gritty"; // It's always a new day when resting 1 week data.promptNewDay = variant !== "gritty"; // It's always a new day when resting 1 week
data.newDay = variant === "normal"; // It's probably a new day when resting normally (8 hours) data.newDay = variant === "normal"; // It's probably a new day when resting normally (8 hours)
return data; return data;
} }
@ -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: "Long Rest", title: "Long Rest",
@ -49,8 +49,7 @@ export default class LongRestDialog extends Dialog {
let newDay = false; let newDay = false;
if (game.settings.get("sw5e", "restVariant") === "normal") if (game.settings.get("sw5e", "restVariant") === "normal")
newDay = html.find('input[name="newDay"]')[0].checked; newDay = html.find('input[name="newDay"]')[0].checked;
else if(game.settings.get("sw5e", "restVariant") === "gritty") else if (game.settings.get("sw5e", "restVariant") === "gritty") newDay = true;
newDay = true;
resolve(newDay); resolve(newDay);
} }
}, },
@ -60,10 +59,10 @@ 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,10 +3,9 @@
* @implements {BaseEntitySheet} * @implements {BaseEntitySheet}
*/ */
export default class ActorMovementConfig extends BaseEntitySheet { export default class ActorMovementConfig extends BaseEntitySheet {
/** @override */ /** @override */
static get defaultOptions() { static get defaultOptions() {
return mergeObject(super.defaultOptions, { return mergeObject(super.defaultOptions, {
classes: ["sw5e"], classes: ["sw5e"],
template: "systems/sw5e/templates/apps/movement-config.html", template: "systems/sw5e/templates/apps/movement-config.html",
width: 300, width: 300,
@ -28,9 +27,9 @@ export default class ActorMovementConfig extends BaseEntitySheet {
const data = { const data = {
movement: duplicate(this.entity._data.data.attributes.movement), movement: duplicate(this.entity._data.data.attributes.movement),
units: CONFIG.SW5E.movementUnits units: CONFIG.SW5E.movementUnits
} };
for ( let [k, v] of Object.entries(data.movement) ) { for (let [k, v] of Object.entries(data.movement)) {
if ( ["units", "hover"].includes(k) ) continue; if (["units", "hover"].includes(k)) continue;
data.movement[k] = Number.isNumeric(v) ? v.toNearest(0.1) : 0; data.movement[k] = Number.isNumeric(v) ? v.toNearest(0.1) : 0;
} }
return data; return data;

View file

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

View file

@ -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);
/** /**
@ -24,9 +24,9 @@ export default class ShortRestDialog extends Dialog {
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
static get defaultOptions() { static get defaultOptions() {
return mergeObject(super.defaultOptions, { return mergeObject(super.defaultOptions, {
template: "systems/sw5e/templates/apps/short-rest.html", template: "systems/sw5e/templates/apps/short-rest.html",
classes: ["sw5e", "dialog"] classes: ["sw5e", "dialog"]
}); });
} }
@ -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; const d = item.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);
@ -52,14 +52,13 @@ export default class ShortRestDialog extends Dialog {
// Determine rest type // Determine rest type
const variant = game.settings.get("sw5e", "restVariant"); const variant = game.settings.get("sw5e", "restVariant");
data.promptNewDay = variant !== "epic"; // It's never a new day when only resting 1 minute data.promptNewDay = variant !== "epic"; // It's never a new day when only resting 1 minute
data.newDay = false; // It may be a new day, but not by default data.newDay = false; // It may be a new day, but not by default
return data; return data;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @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: "Short Rest", title: "Short Rest",
@ -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,11 +3,10 @@
* @implements {FormApplication} * @implements {FormApplication}
*/ */
export default class TraitSelector extends FormApplication { export default class TraitSelector extends FormApplication {
/** @override */ /** @override */
static get defaultOptions() { static get defaultOptions() {
return mergeObject(super.defaultOptions, { return mergeObject(super.defaultOptions, {
id: "trait-selector", id: "trait-selector",
classes: ["sw5e"], classes: ["sw5e"],
title: "Actor Trait Selection", title: "Actor Trait Selection",
template: "systems/sw5e/templates/apps/trait-selector.html", template: "systems/sw5e/templates/apps/trait-selector.html",
@ -27,33 +26,32 @@ export default class TraitSelector extends FormApplication {
* @type {String} * @type {String}
*/ */
get attribute() { get attribute() {
return this.options.name; return this.options.name;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
getData() { getData() {
// Get current values // Get current values
let attr = getProperty(this.object._data, this.attribute); let attr = getProperty(this.object._data, this.attribute);
if ( getType(attr) !== "Object" ) attr = {value: [], custom: ""}; if (getType(attr) !== "Object") attr = {value: [], custom: ""};
// Populate choices // Populate choices
const choices = duplicate(this.options.choices); const choices = duplicate(this.options.choices);
for ( let [k, v] of Object.entries(choices) ) { for (let [k, v] of Object.entries(choices)) {
choices[k] = { choices[k] = {
label: v, label: v,
chosen: attr ? attr.value.includes(k) : false chosen: attr ? attr.value.includes(k) : false
} };
} }
// Return data // Return data
return { return {
allowCustom: this.options.allowCustom, allowCustom: this.options.allowCustom,
choices: choices, choices: choices,
custom: attr ? attr.custom : "" custom: attr ? attr.custom : ""
} };
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -64,21 +62,21 @@ export default class TraitSelector extends FormApplication {
// Obtain choices // Obtain choices
const chosen = []; const chosen = [];
for ( let [k, v] of Object.entries(formData) ) { for (let [k, v] of Object.entries(formData)) {
if ( (k !== "custom") && v ) chosen.push(k); if (k !== "custom" && v) chosen.push(k);
} }
updateData[`${this.attribute}.value`] = chosen; updateData[`${this.attribute}.value`] = chosen;
// Validate the number chosen // Validate the number chosen
if ( this.options.minimum && (chosen.length < this.options.minimum) ) { if (this.options.minimum && chosen.length < this.options.minimum) {
return ui.notifications.error(`You must choose at least ${this.options.minimum} options`); return ui.notifications.error(`You must choose at least ${this.options.minimum} options`);
} }
if ( this.options.maximum && (chosen.length > this.options.maximum) ) { if (this.options.maximum && chosen.length > this.options.maximum) {
return ui.notifications.error(`You may choose no more than ${this.options.maximum} options`); return ui.notifications.error(`You may choose no more than ${this.options.maximum} options`);
} }
// Include custom // Include custom
if ( this.options.allowCustom ) { if (this.options.allowCustom) {
updateData[`${this.attribute}.custom`] = formData.custom; updateData[`${this.attribute}.custom`] = formData.custom;
} }

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;
@ -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;
} }
@ -44,9 +44,9 @@ export const measureDistances = function(segments, options={}) {
* TODO: This should probably be replaced with a formal Token class extension * TODO: This should probably be replaced with a formal Token class extension
*/ */
const _TokenGetBarAttribute = Token.prototype.getBarAttribute; const _TokenGetBarAttribute = Token.prototype.getBarAttribute;
export const getBarAttribute = function(...args) { export const getBarAttribute = function (...args) {
const data = _TokenGetBarAttribute.bind(this)(...args); const data = _TokenGetBarAttribute.bind(this)(...args);
if ( data && (data.attribute === "attributes.hp") ) { if (data && data.attribute === "attributes.hp") {
data.value += parseInt(getProperty(this.actor.data, "data.attributes.hp.temp") || 0); data.value += parseInt(getProperty(this.actor.data, "data.attributes.hp.temp") || 0);
data.max += parseInt(getProperty(this.actor.data, "data.attributes.hp.tempmax") || 0); data.max += parseInt(getProperty(this.actor.data, "data.attributes.hp.tempmax") || 0);
} }

View file

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

View file

@ -1,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.owner ) return; if (actor && actor.owner) return;
else if ( game.user.isGM || (data.author.id === game.user.id)) return; else if (game.user.isGM || data.author.id === game.user.id) return;
// Otherwise conceal action buttons except for saving throw // Otherwise conceal action buttons except for saving throw
const buttons = chatCard.find("button[data-action]"); const buttons = chatCard.find("button[data-action]");
buttons.each((i, btn) => { buttons.each((i, btn) => {
if ( btn.dataset.action === "save" ) return; if (btn.dataset.action === "save") return;
btn.style.display = "none" btn.style.display = "none";
}); });
} }
}; };
@ -63,7 +62,7 @@ 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;
@ -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(
const a = t.actor; canvas.tokens.controlled.map(t => {
return a.applyDamage(roll.total, multiplier); const a = t.actor;
})); 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(combatant) { export const _getInitiativeFormula = function (combatant) {
const actor = combatant.actor; const actor = combatant.actor;
if ( !actor ) return "1d20"; if (!actor) return "1d20";
const init = actor.data.data.attributes.init; const init = actor.data.data.attributes.init;
let nd = 1; let nd = 1;
@ -19,10 +18,10 @@ export const _getInitiativeFormula = function(combatant) {
mods += "kh"; mods += "kh";
} }
const parts = [`${nd}d20${mods}`, init.mod, (init.prof !== 0) ? init.prof : null, (init.bonus !== 0) ? init.bonus : null]; const parts = [`${nd}d20${mods}`, init.mod, init.prof !== 0 ? init.prof : null, init.bonus !== 0 ? init.bonus : null];
// Optionally apply Dexterity tiebreaker // Optionally apply Dexterity tiebreaker
const tiebreaker = game.settings.get("sw5e", "initiativeDexTiebreaker"); const tiebreaker = game.settings.get("sw5e", "initiativeDexTiebreaker");
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

@ -18,30 +18,36 @@ export function simplifyRollFormula(formula, data, {constantFirst = false} = {})
const rollableTerms = []; // Terms that are non-constant, and their associated operators const rollableTerms = []; // Terms that are non-constant, and their associated operators
const constantTerms = []; // Terms that are constant, and their associated operators const constantTerms = []; // Terms that are constant, and their associated operators
let operators = []; // Temporary storage for operators before they are moved to one of the above let operators = []; // Temporary storage for operators before they are moved to one of the above
for (let term of terms) { // For each term for (let term of terms) {
if (["+", "-"].includes(term)) operators.push(term); // If the term is an addition/subtraction operator, push the term into the operators array // For each term
else { // Otherwise the term is not an operator if (["+", "-"].includes(term)) operators.push(term);
if (term instanceof DiceTerm) { // If the term is something rollable // If the term is an addition/subtraction operator, push the term into the operators array
rollableTerms.push(...operators); // Place all the operators into the rollableTerms array else {
rollableTerms.push(term); // Then place this rollable term into it as well // Otherwise the term is not an operator
} // if (term instanceof DiceTerm) {
else { // Otherwise, this must be a constant // If the term is something rollable
constantTerms.push(...operators); // Place the operators into the constantTerms array rollableTerms.push(...operators); // Place all the operators into the rollableTerms array
constantTerms.push(term); // Then also add this constant term to that array. rollableTerms.push(term); // Then place this rollable term into it as well
} // } //
operators = []; // Finally, the operators have now all been assigend to one of the arrays, so empty this before the next iteration. else {
// Otherwise, this must be a constant
constantTerms.push(...operators); // Place the operators into the constantTerms array
constantTerms.push(term); // Then also add this constant term to that array.
} //
operators = []; // Finally, the operators have now all been assigend to one of the arrays, so empty this before the next iteration.
} }
} }
const constantFormula = Roll.cleanFormula(constantTerms); // Cleans up the constant terms and produces a new formula string const constantFormula = Roll.cleanFormula(constantTerms); // Cleans up the constant terms and produces a new formula string
const rollableFormula = Roll.cleanFormula(rollableTerms); // Cleans up the non-constant terms and produces a new formula string const rollableFormula = Roll.cleanFormula(rollableTerms); // Cleans up the non-constant terms and produces a new formula string
const constantPart = roll._safeEval(constantFormula); // Mathematically evaluate the constant formula to produce a single constant term const constantPart = roll._safeEval(constantFormula); // Mathematically evaluate the constant formula to produce a single constant term
const parts = constantFirst ? // Order the rollable and constant terms, either constant first or second depending on the optional argumen const parts = constantFirst // Order the rollable and constant terms, either constant first or second depending on the optional argumen
[constantPart, rollableFormula] : [rollableFormula, constantPart]; ? [constantPart, rollableFormula]
: [rollableFormula, constantPart];
// Join the parts with a + sign, pass them to `Roll` once again to clean up the formula // Join the parts with a + sign, pass them to `Roll` once again to clean up the formula
return new Roll(parts.filterJoin(" + ")).formula; return new Roll(parts.filterJoin(" + ")).formula;
@ -55,11 +61,11 @@ export function simplifyRollFormula(formula, data, {constantFirst = false} = {})
* @return {Boolean} True when unsupported, false if supported * @return {Boolean} True when unsupported, false if supported
*/ */
function _isUnsupportedTerm(term) { function _isUnsupportedTerm(term) {
const diceTerm = term instanceof DiceTerm; const diceTerm = term instanceof DiceTerm;
const operator = ["+", "-"].includes(term); const operator = ["+", "-"].includes(term);
const number = !isNaN(Number(term)); const number = !isNaN(Number(term));
return !(diceTerm || operator || number); return !(diceTerm || operator || number);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -94,12 +100,28 @@ function _isUnsupportedTerm(term) {
* *
* @return {Promise} A Promise which resolves once the roll workflow has completed * @return {Promise} A Promise which resolves once the roll workflow has completed
*/ */
export async function d20Roll({parts=[], data={}, event={}, rollMode=null, template=null, title=null, speaker=null, export async function d20Roll({
flavor=null, fastForward=null, dialogOptions, parts = [],
advantage=null, disadvantage=null, critical=20, fumble=1, targetValue=null, data = {},
elvenAccuracy=false, halflingLucky=false, reliableTalent=false, event = {},
chatMessage=true, messageData={}}={}) { rollMode = null,
template = null,
title = null,
speaker = null,
flavor = null,
fastForward = null,
dialogOptions,
advantage = null,
disadvantage = null,
critical = 20,
fumble = 1,
targetValue = null,
elvenAccuracy = false,
halflingLucky = false,
reliableTalent = false,
chatMessage = true,
messageData = {}
} = {}) {
// Prepare Message Data // Prepare Message Data
messageData.flavor = flavor || title; messageData.flavor = flavor || title;
messageData.speaker = speaker || ChatMessage.getSpeaker(); messageData.speaker = speaker || ChatMessage.getSpeaker();
@ -110,13 +132,12 @@ export async function d20Roll({parts=[], data={}, event={}, rollMode=null, templ
let adv = 0; let adv = 0;
fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)); fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey));
if (fastForward) { if (fastForward) {
if ( advantage ?? event.altKey ) adv = 1; if (advantage ?? event.altKey) adv = 1;
else if ( disadvantage ?? (event.ctrlKey || event.metaKey) ) adv = -1; else if (disadvantage ?? (event.ctrlKey || event.metaKey)) adv = -1;
} }
// Define the inner roll function // Define the inner roll function
const _roll = (parts, adv, form) => { const _roll = (parts, adv, form) => {
// Determine the d20 roll and modifiers // Determine the d20 roll and modifiers
let nd = 1; let nd = 1;
let mods = halflingLucky ? "r1=1" : ""; let mods = halflingLucky ? "r1=1" : "";
@ -125,7 +146,7 @@ export async function d20Roll({parts=[], data={}, event={}, rollMode=null, templ
if (adv === 1) { if (adv === 1) {
nd = elvenAccuracy ? 3 : 2; nd = elvenAccuracy ? 3 : 2;
messageData.flavor += ` (${game.i18n.localize("SW5E.Advantage")})`; messageData.flavor += ` (${game.i18n.localize("SW5E.Advantage")})`;
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].advantage = true; if ("flags.sw5e.roll" in messageData) messageData["flags.sw5e.roll"].advantage = true;
mods += "kh"; mods += "kh";
} }
@ -133,7 +154,7 @@ export async function d20Roll({parts=[], data={}, event={}, rollMode=null, templ
else if (adv === -1) { else if (adv === -1) {
nd = 2; nd = 2;
messageData.flavor += ` (${game.i18n.localize("SW5E.Disadvantage")})`; messageData.flavor += ` (${game.i18n.localize("SW5E.Disadvantage")})`;
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].disadvantage = true; if ("flags.sw5e.roll" in messageData) messageData["flags.sw5e.roll"].disadvantage = true;
mods += "kl"; mods += "kl";
} }
@ -143,8 +164,8 @@ export async function d20Roll({parts=[], data={}, event={}, rollMode=null, templ
parts.unshift(formula); parts.unshift(formula);
// Optionally include a situational bonus // Optionally include a situational bonus
if ( form ) { if (form) {
data['bonus'] = form.bonus.value; data["bonus"] = form.bonus.value;
messageOptions.rollMode = form.rollMode.value; messageOptions.rollMode = form.rollMode.value;
} }
if (!data["bonus"]) parts.pop(); if (!data["bonus"]) parts.pop();
@ -175,8 +196,8 @@ export async function d20Roll({parts=[], data={}, event={}, rollMode=null, templ
if (d.faces === 20) { if (d.faces === 20) {
d.options.critical = critical; d.options.critical = critical;
d.options.fumble = fumble; d.options.fumble = fumble;
if ( adv === 1 ) d.options.advantage = true; if (adv === 1) d.options.advantage = true;
else if ( adv === -1 ) d.options.disadvantage = true; else if (adv === -1) d.options.disadvantage = true;
if (targetValue) d.options.target = targetValue; if (targetValue) d.options.target = targetValue;
} }
} }
@ -189,11 +210,20 @@ export async function d20Roll({parts=[], data={}, event={}, rollMode=null, templ
}; };
// Create the Roll instance // Create the Roll instance
const roll = fastForward ? _roll(parts, adv) : const roll = fastForward
await _d20RollDialog({template, title, parts, data, rollMode: messageOptions.rollMode, dialogOptions, roll: _roll}); ? _roll(parts, adv)
: await _d20RollDialog({
template,
title,
parts,
data,
rollMode: messageOptions.rollMode,
dialogOptions,
roll: _roll
});
// Create a Chat Message // Create a Chat Message
if ( roll && chatMessage ) roll.toMessage(messageData, messageOptions); if (roll && chatMessage) roll.toMessage(messageData, messageOptions);
return roll; return roll;
} }
@ -204,8 +234,7 @@ export async function d20Roll({parts=[], data={}, event={}, rollMode=null, templ
* @return {Promise<Roll>} * @return {Promise<Roll>}
* @private * @private
*/ */
async function _d20RollDialog({template, title, parts, data, rollMode, dialogOptions, roll}={}) { async function _d20RollDialog({template, title, parts, data, rollMode, dialogOptions, roll} = {}) {
// Render modal dialog // Render modal dialog
template = template || "systems/sw5e/templates/chat/roll-dialog.html"; template = template || "systems/sw5e/templates/chat/roll-dialog.html";
let dialogData = { let dialogData = {
@ -219,26 +248,29 @@ async function _d20RollDialog({template, title, parts, data, rollMode, dialogOpt
// Create the Dialog window // Create the Dialog window
return new Promise(resolve => { return new Promise(resolve => {
new Dialog({ new Dialog(
title: title, {
content: html, title: title,
buttons: { content: html,
advantage: { buttons: {
label: game.i18n.localize("SW5E.Advantage"), advantage: {
callback: html => resolve(roll(parts, 1, html[0].querySelector("form"))) label: game.i18n.localize("SW5E.Advantage"),
callback: html => resolve(roll(parts, 1, html[0].querySelector("form")))
},
normal: {
label: game.i18n.localize("SW5E.Normal"),
callback: html => resolve(roll(parts, 0, html[0].querySelector("form")))
},
disadvantage: {
label: game.i18n.localize("SW5E.Disadvantage"),
callback: html => resolve(roll(parts, -1, html[0].querySelector("form")))
}
}, },
normal: { default: "normal",
label: game.i18n.localize("SW5E.Normal"), close: () => resolve(null)
callback: html => resolve(roll(parts, 0, html[0].querySelector("form")))
},
disadvantage: {
label: game.i18n.localize("SW5E.Disadvantage"),
callback: html => resolve(roll(parts, -1, html[0].querySelector("form")))
}
}, },
default: "normal", dialogOptions
close: () => resolve(null) ).render(true);
}, dialogOptions).render(true);
}); });
} }
@ -271,10 +303,25 @@ async function _d20RollDialog({template, title, parts, data, rollMode, dialogOpt
* *
* @return {Promise} A Promise which resolves once the roll workflow has completed * @return {Promise} A Promise which resolves once the roll workflow has completed
*/ */
export async function damageRoll({parts, actor, data, event={}, rollMode=null, template, title, speaker, flavor, export async function damageRoll({
allowCritical=true, critical=false, criticalBonusDice=0, criticalMultiplier=2, fastForward=null, parts,
dialogOptions={}, chatMessage=true, messageData={}}={}) { actor,
data,
event = {},
rollMode = null,
template,
title,
speaker,
flavor,
allowCritical = true,
critical = false,
criticalBonusDice = 0,
criticalMultiplier = 2,
fastForward = null,
dialogOptions = {},
chatMessage = true,
messageData = {}
} = {}) {
// Prepare Message Data // Prepare Message Data
messageData.flavor = flavor || title; messageData.flavor = flavor || title;
messageData.speaker = speaker || ChatMessage.getSpeaker(); messageData.speaker = speaker || ChatMessage.getSpeaker();
@ -282,11 +329,10 @@ export async function damageRoll({parts, actor, data, event={}, rollMode=null, t
parts = parts.concat(["@bonus"]); parts = parts.concat(["@bonus"]);
// Define inner roll function // Define inner roll function
const _roll = function(parts, crit, form) { const _roll = function (parts, crit, form) {
// Optionally include a situational bonus // Optionally include a situational bonus
if ( form ) { if (form) {
data['bonus'] = form.bonus.value; data["bonus"] = form.bonus.value;
messageOptions.rollMode = form.rollMode.value; messageOptions.rollMode = form.rollMode.value;
} }
if (!data["bonus"]) parts.pop(); if (!data["bonus"]) parts.pop();
@ -295,22 +341,23 @@ export async function damageRoll({parts, actor, data, event={}, rollMode=null, t
let roll = new Roll(parts.join("+"), data); let roll = new Roll(parts.join("+"), data);
// Modify the damage formula for critical hits // Modify the damage formula for critical hits
if ( crit === true ) { if (crit === true) {
roll.alter(criticalMultiplier, 0); // Multiply all dice roll.alter(criticalMultiplier, 0); // Multiply all dice
if ( roll.terms[0] instanceof Die ) { // Add bonus dice for only the main dice term if (roll.terms[0] instanceof Die) {
// Add bonus dice for only the main dice term
roll.terms[0].alter(1, criticalBonusDice); roll.terms[0].alter(1, criticalBonusDice);
roll._formula = roll.formula; roll._formula = roll.formula;
} }
messageData.flavor += ` (${game.i18n.localize("SW5E.Critical")})`; messageData.flavor += ` (${game.i18n.localize("SW5E.Critical")})`;
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].critical = true; if ("flags.sw5e.roll" in messageData) messageData["flags.sw5e.roll"].critical = true;
} }
// Execute the roll // Execute the roll
try { try {
roll.evaluate() roll.evaluate();
if ( crit ) roll.dice.forEach(d => d.options.critical = true); // TODO workaround core bug which wipes Roll#options on roll if (crit) roll.dice.forEach(d => (d.options.critical = true)); // TODO workaround core bug which wipes Roll#options on roll
return roll; return roll;
} catch(err) { } catch (err) {
console.error(err); console.error(err);
ui.notifications.error(`Dice roll evaluation failed: ${err.message}`); ui.notifications.error(`Dice roll evaluation failed: ${err.message}`);
return null; return null;
@ -318,14 +365,22 @@ export async function damageRoll({parts, actor, data, event={}, rollMode=null, t
}; };
// Create the Roll instance // Create the Roll instance
const roll = fastForward ? _roll(parts, critical) : await _damageRollDialog({ const roll = fastForward
template, title, parts, data, allowCritical, rollMode: messageOptions.rollMode, dialogOptions, roll: _roll ? _roll(parts, critical)
}); : await _damageRollDialog({
template,
title,
parts,
data,
allowCritical,
rollMode: messageOptions.rollMode,
dialogOptions,
roll: _roll
});
// Create a Chat Message // Create a Chat Message
if ( roll && chatMessage ) roll.toMessage(messageData, messageOptions); if (roll && chatMessage) roll.toMessage(messageData, messageOptions);
return roll; return roll;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -335,8 +390,7 @@ export async function damageRoll({parts, actor, data, event={}, rollMode=null, t
* @return {Promise<Roll>} * @return {Promise<Roll>}
* @private * @private
*/ */
async function _damageRollDialog({template, title, parts, data, allowCritical, rollMode, dialogOptions, roll}={}) { async function _damageRollDialog({template, title, parts, data, allowCritical, rollMode, dialogOptions, roll} = {}) {
// Render modal dialog // Render modal dialog
template = template || "systems/sw5e/templates/chat/roll-dialog.html"; template = template || "systems/sw5e/templates/chat/roll-dialog.html";
let dialogData = { let dialogData = {
@ -349,22 +403,25 @@ async function _damageRollDialog({template, title, parts, data, allowCritical, r
// Create the Dialog window // Create the Dialog window
return new Promise(resolve => { return new Promise(resolve => {
new Dialog({ new Dialog(
title: title, {
content: html, title: title,
buttons: { content: html,
critical: { buttons: {
condition: allowCritical, critical: {
label: game.i18n.localize("SW5E.CriticalHit"), condition: allowCritical,
callback: html => resolve(roll(parts, true, html[0].querySelector("form"))) label: game.i18n.localize("SW5E.CriticalHit"),
}, callback: html => resolve(roll(parts, true, html[0].querySelector("form")))
normal: { },
label: game.i18n.localize(allowCritical ? "SW5E.Normal" : "SW5E.Roll"), normal: {
callback: html => resolve(roll(parts, false, html[0].querySelector("form"))) label: game.i18n.localize(allowCritical ? "SW5E.Normal" : "SW5E.Roll"),
callback: html => resolve(roll(parts, false, html[0].querySelector("form")))
}
}, },
default: "normal",
close: () => resolve(null)
}, },
default: "normal", dialogOptions
close: () => resolve(null) ).render(true);
}, dialogOptions).render(true);
}); });
} }

74
module/effects.js vendored
View file

@ -8,15 +8,18 @@ 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 ActiveEffect.create({ return ActiveEffect.create(
label: "New Effect", {
icon: "icons/svg/aura.svg", label: "New Effect",
origin: owner.uuid, icon: "icons/svg/aura.svg",
"duration.rounds": li.dataset.effectType === "temporary" ? 1 : undefined, origin: owner.uuid,
disabled: li.dataset.effectType === "inactive" "duration.rounds": li.dataset.effectType === "temporary" ? 1 : undefined,
}, owner).create(); disabled: li.dataset.effectType === "inactive"
},
owner
).create();
case "edit": case "edit":
return effect.sheet.render(true); return effect.sheet.render(true);
case "delete": case "delete":
@ -32,32 +35,31 @@ 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: { type: "temporary",
type: "temporary", label: "SW5E.EffectsCategoryTemporary",
label: "SW5E.EffectsCategoryTemporary", effects: []
effects: [] },
}, passive: {
passive: { type: "passive",
type: "passive", label: "SW5E.EffectsCategoryPassive",
label: "SW5E.EffectsCategoryPassive", effects: []
effects: [] },
}, inactive: {
inactive: { type: "inactive",
type: "inactive", label: "SW5E.EffectsCategoryInactive",
label: "SW5E.EffectsCategoryInactive", effects: []
effects: []
}
};
// Iterate over active effects, classifying them into categories
for ( let e of effects ) {
e._getSourceName(); // Trigger a lookup for the source name
if ( e.data.disabled ) categories.inactive.effects.push(e);
else if ( e.isTemporary ) categories.temporary.effects.push(e);
else categories.passive.effects.push(e);
} }
return categories; };
}
// Iterate over active effects, classifying them into categories
for (let e of effects) {
e._getSourceName(); // Trigger a lookup for the source name
if (e.data.disabled) categories.inactive.effects.push(e);
else if (e.isTemporary) categories.temporary.effects.push(e);
else categories.passive.effects.push(e);
}
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"}]
}); });
} }
@ -96,7 +96,7 @@ export default class ItemSheet5e extends ItemSheet {
} }
return ammo; return ammo;
}, },
{ [item._id]: `${item.name} (${item.data.quantity})` } {[item._id]: `${item.name} (${item.data.quantity})`}
); );
} }
@ -127,8 +127,8 @@ 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;
} }
@ -171,8 +171,8 @@ export default class ItemSheet5e extends ItemSheet {
if (item.type === "weapon") { if (item.type === "weapon") {
props.push( props.push(
...Object.entries(item.data.properties) ...Object.entries(item.data.properties)
.filter((e) => e[1] === true) .filter(e => e[1] === true)
.map((e) => CONFIG.SW5E.weaponProperties[e[0]]) .map(e => CONFIG.SW5E.weaponProperties[e[0]])
); );
} else if (item.type === "power") { } else if (item.type === "power") {
props.push( props.push(
@ -211,7 +211,7 @@ export default class ItemSheet5e extends ItemSheet {
if (item.type !== "weapon" && item.data.activation && !isObjectEmpty(item.data.activation)) { if (item.type !== "weapon" && item.data.activation && !isObjectEmpty(item.data.activation)) {
props.push(labels.activation, labels.range, labels.target, labels.duration); props.push(labels.activation, labels.range, labels.target, labels.duration);
} }
return props.filter((p) => !!p); return props.filter(p => !!p);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -249,14 +249,14 @@ export default class ItemSheet5e extends ItemSheet {
/** @override */ /** @override */
_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);
// Handle Damage array // Handle Damage array
const damage = data.data?.damage; const damage = data.data?.damage;
if (damage) damage.parts = Object.values(damage?.parts || {}).map((d) => [d[0] || "", d[1] || ""]); if (damage) damage.parts = Object.values(damage?.parts || {}).map(d => [d[0] || "", d[1] || ""]);
// Return the flattened submission data // Return the flattened submission data
return flattenObject(data); return flattenObject(data);
@ -270,7 +270,7 @@ export default class ItemSheet5e extends ItemSheet {
if (this.isEditable) { if (this.isEditable) {
html.find(".damage-control").click(this._onDamageControl.bind(this)); html.find(".damage-control").click(this._onDamageControl.bind(this));
html.find(".trait-selector.class-skills").click(this._onConfigureClassSkills.bind(this)); html.find(".trait-selector.class-skills").click(this._onConfigureClassSkills.bind(this));
html.find(".effect-control").click((ev) => { html.find(".effect-control").click(ev => {
if (this.item.isOwned) if (this.item.isOwned)
return ui.notifications.warn( return ui.notifications.warn(
"Managing Active Effects within an Owned Item is not currently supported and will be added in a subsequent update." "Managing Active Effects within an Owned Item is not currently supported and will be added in a subsequent update."
@ -296,7 +296,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
@ -305,7 +305,7 @@ export default class ItemSheet5e extends ItemSheet {
const li = a.closest(".damage-part"); const li = a.closest(".damage-part");
const damage = duplicate(this.item.data.data.damage); const damage = duplicate(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});
} }
} }

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,56 +2,59 @@
* 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.entities ) { for await (let a of game.actors.entities) {
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 ( !isObjectEmpty(updateData) ) { if (!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.entities ) { for (let i of game.items.entities) {
try { try {
const updateData = migrateItemData(i.data); const updateData = migrateItemData(i.data);
if ( !isObjectEmpty(updateData) ) { if (!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.entities ) { for (let s of game.scenes.entities) {
try { try {
const updateData = await migrateSceneData(s.data); const updateData = await migrateSceneData(s.data);
if ( !isObjectEmpty(updateData) ) { if (!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});
} }
} 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);
} }
@ -67,9 +70,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;
@ -80,7 +83,7 @@ export const migrateCompendium = async function(pack) {
const content = await pack.getContent(); const content = await pack.getContent();
// Iterate over compendium entries - applying fine-tuned migration functions // Iterate over compendium entries - applying fine-tuned migration functions
for await ( let ent of content ) { for await (let ent of content) {
let updateData = {}; let updateData = {};
try { try {
switch (entity) { switch (entity) {
@ -94,16 +97,14 @@ export const migrateCompendium = async function(pack) {
updateData = await migrateSceneData(ent.data); updateData = await migrateSceneData(ent.data);
break; break;
} }
if ( isObjectEmpty(updateData) ) continue; if (isObjectEmpty(updateData)) continue;
// Save the entry, if data was changed // Save the entry, if data was changed
updateData["_id"] = ent._id; updateData["_id"] = ent._id;
await pack.updateEntity(updateData); await pack.updateEntity(updateData);
console.log(`Migrated ${entity} entity ${ent.name} in Compendium ${pack.collection}`); console.log(`Migrated ${entity} entity ${ent.name} in Compendium ${pack.collection}`);
} } catch (err) {
// Handle migration failures
// Handle migration failures
catch(err) {
err.message = `Failed sw5e system migration for entity ${ent.name} in pack ${pack.collection}: ${err.message}`; err.message = `Failed sw5e system migration for entity ${ent.name} in pack ${pack.collection}: ${err.message}`;
console.error(err); console.error(err);
} }
@ -124,7 +125,7 @@ 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
@ -132,7 +133,7 @@ export const migrateActorData = async function(actor) {
_migrateActorSenses(actor, updateData); _migrateActorSenses(actor, updateData);
// Migrate Owned Items // Migrate Owned Items
if ( !!actor.items ) { if (!!actor.items) {
let hasItemUpdates = false; let hasItemUpdates = false;
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;
@ -141,21 +142,21 @@ export const migrateActorData = async function(actor) {
let itemUpdate = await migrateActorItemData(i, actor); let itemUpdate = await migrateActorItemData(i, 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(i.data, "preparation.prepared") === false) itemUpdate["data.preparation.prepared"] = true; if (getProperty(i.data, "preparation.prepared") === false) itemUpdate["data.preparation.prepared"] = true;
if (getProperty(i.data, "equipped") === false) itemUpdate["data.equipped"] = true; if (getProperty(i.data, "equipped") === false) itemUpdate["data.equipped"] = true;
if (getProperty(i.data, "proficient") === false) itemUpdate["data.proficient"] = true; if (getProperty(i.data, "proficient") === false) itemUpdate["data.proficient"] = true;
} }
// Update the Owned Item // Update the Owned Item
if ( !isObjectEmpty(itemUpdate) ) { if (!isObjectEmpty(itemUpdate)) {
hasItemUpdates = true; hasItemUpdates = true;
console.log(`Migrating Actor ${actor.name}'s ${i.name}`); console.log(`Migrating Actor ${actor.name}'s ${i.name}`);
return [...results, mergeObject(i, itemUpdate, {enforceTypes: false, inplace: false})]; return [...results, mergeObject(i, itemUpdate, {enforceTypes: false, inplace: false})];
} else return [...results, i]; } else return [...results, i];
}, []); }, []);
if ( hasItemUpdates ) updateData.items = items; if (hasItemUpdates) updateData.items = items;
} }
// Update NPC data with new datamodel information // Update NPC data with new datamodel information
@ -165,20 +166,18 @@ export const migrateActorData = async function(actor) {
// migrate powers last since it relies on item classes being migrated first. // migrate powers last since it relies on item classes being migrated first.
_migrateActorPowers(actor, updateData); _migrateActorPowers(actor, updateData);
return updateData; return updateData;
}; };
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**
* 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);
@ -188,7 +187,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);
} }
@ -196,14 +195,13 @@ function cleanActorData(actorData) {
return actorData; return actorData;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**
* Migrate a single Item entity to incorporate latest data model changes * Migrate a single Item entity to incorporate latest data model changes
* @param item * @param item
*/ */
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);
@ -217,7 +215,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);
@ -233,24 +231,26 @@ 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 = duplicate(scene.tokens); const tokens = duplicate(scene.tokens);
return { return {
tokens: await Promise.all(tokens.map(async (t) => { tokens: await Promise.all(
if (!t.actorId || t.actorLink || !t.actorData.data) { tokens.map(async t => {
t.actorData = {}; if (!t.actorId || t.actorLink || !t.actorData.data) {
t.actorData = {};
return t;
}
const token = new Token(t);
if (!token.actor) {
t.actorId = null;
t.actorData = {};
} else if (!t.actorLink) {
const updateData = await migrateActorData(token.data.actorData);
t.actorData = mergeObject(token.data.actorData, updateData);
}
return t; return t;
} })
const token = new Token(t); )
if ( !token.actor ) {
t.actorId = null;
t.actorData = {};
} else if ( !t.actorLink ) {
const updateData = await migrateActorData(token.data.actorData);
t.actorData = mergeObject(token.data.actorData, updateData);
}
return t;
}))
}; };
}; };
@ -266,7 +266,6 @@ export const migrateActorItemData = async function(item, actor) {
* @return {Object} The updated Actor * @return {Object} The updated Actor
*/ */
function _updateNPCData(actor) { function _updateNPCData(actor) {
let actorData = actor.data; let actorData = actor.data;
const updateData = {}; const updateData = {};
// check for flag.core, if not there is no compendium monster so exit // check for flag.core, if not there is no compendium monster so exit
@ -274,29 +273,38 @@ 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
const monsterData = monster.data.data; .get("sw5e.monsters")
// copy movement[], senses[], powercasting, force[], tech[], powerForceLevel, powerTechLevel .getEntity(core_id)
updateData["data.attributes.movement"] = monsterData.attributes.movement; .then(monster => {
updateData["data.attributes.senses"] = monsterData.attributes.senses; const monsterData = monster.data.data;
updateData["data.attributes.powercasting"] = monsterData.attributes.powercasting; // copy movement[], senses[], powercasting, force[], tech[], powerForceLevel, powerTechLevel
updateData["data.attributes.force"] = monsterData.attributes.force; updateData["data.attributes.movement"] = monsterData.attributes.movement;
updateData["data.attributes.tech"] = monsterData.attributes.tech; updateData["data.attributes.senses"] = monsterData.attributes.senses;
updateData["data.details.powerForceLevel"] = monsterData.details.powerForceLevel; updateData["data.attributes.powercasting"] = monsterData.attributes.powercasting;
updateData["data.details.powerTechLevel"] = monsterData.details.powerTechLevel; updateData["data.attributes.force"] = monsterData.attributes.force;
// push missing powers onto actor updateData["data.attributes.tech"] = monsterData.attributes.tech;
let newPowers = []; updateData["data.details.powerForceLevel"] = monsterData.details.powerForceLevel;
for ( let i of monster.items ) { updateData["data.details.powerTechLevel"] = monsterData.details.powerTechLevel;
// push missing powers onto actor
let newPowers = [];
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));
@ -304,26 +312,24 @@ function _updateNPCData(actor) {
newPowers.push(newPower); newPowers.push(newPower);
} }
} }
} }
// get actor to create new powers // get actor to create new powers
const liveActor = game.actors.get(actor._id); const liveActor = game.actors.get(actor._id);
// create the powers on the actor // create the powers on the actor
liveActor.createEmbeddedEntity("OwnedItem", newPowers); liveActor.createEmbeddedEntity("OwnedItem", newPowers);
// set flag to check to see if migration has been done so we don't do it again. // set flag to check to see if migration has been done so we don't do it again.
liveActor.setFlag("sw5e", "dataVersion", "1.2.4"); liveActor.setFlag("sw5e", "dataVersion", "1.2.4");
}) });
} }
//merge object //merge object
actorData = mergeObject(actorData, updateData); actorData = mergeObject(actorData, updateData);
// Return the scrubbed data // Return the scrubbed data
return actor; return actor;
} }
/** /**
* Migrate the actor speed string to movement object * Migrate the actor speed string to movement object
* @private * @private
@ -332,21 +338,20 @@ 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;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -360,7 +365,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;
@ -381,14 +386,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;
@ -397,7 +402,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"] = "";
@ -407,7 +412,7 @@ function _migrateActorPowers(actorData, updateData) {
// Remove the Power DC Bonus // Remove the Power DC Bonus
updateData["data.bonuses.power.-=dc"] = null; updateData["data.bonuses.power.-=dc"] = null;
return updateData return updateData;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -418,7 +423,7 @@ 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 || "";
// 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"
@ -426,19 +431,19 @@ function _migrateActorSenses(actor, updateData) {
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;
} }
@ -453,9 +458,9 @@ function _migrateActorSenses(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"] = "consular"; updateData["data.powercasting"] = "consular";
break; break;
case "Engineer": case "Engineer":
@ -494,32 +499,35 @@ 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;
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 power type is not force or tech exit out //if power type is not force or tech exit out
let powerType = "none"; let powerType = "none";
if (coreSource === "Compendium.sw5e.forcepowers") powerType = "sw5e.forcepowers"; if (coreSource === "Compendium.sw5e.forcepowers") powerType = "sw5e.forcepowers";
if (coreSource === "Compendium.sw5e.techpowers") powerType = "sw5e.techpowers"; if (coreSource === "Compendium.sw5e.techpowers") powerType = "sw5e.techpowers";
if (powerType === "none") return updateData; if (powerType === "none") return updateData;
const corePower = duplicate(await game.packs.get(powerType).getEntity(core_id)); const corePower = duplicate(await game.packs.get(powerType).getEntity(core_id));
console.log(`Updating Actor ${actor.name}'s ${item.name} from compendium`); console.log(`Updating Actor ${actor.name}'s ${item.name} from compendium`);
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 => {
//}) //})
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -529,7 +537,7 @@ async function _migrateItemPower(item, actor, updateData) {
* @private * @private
*/ */
function _migrateItemAttunement(item, updateData) { function _migrateItemAttunement(item, updateData) {
if ( item.data.attuned === undefined ) return; if (item.data.attuned === undefined) return;
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;
@ -543,19 +551,19 @@ function _migrateItemAttunement(item, updateData) {
* @private * @private
*/ */
export async function purgeFlags(pack) { export async function purgeFlags(pack) {
const cleanFlags = (flags) => { const cleanFlags = flags => {
const flags5e = flags.sw5e || null; const flags5e = flags.sw5e || null;
return flags5e ? {sw5e: flags5e} : {}; return flags5e ? {sw5e: flags5e} : {};
}; };
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}`);
@ -565,20 +573,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;
@ -65,7 +64,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);
@ -85,7 +84,7 @@ export default class AbilityTemplate extends MeasuredTemplate {
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.x = snapped.x; this.data.x = snapped.x;
@ -120,11 +119,11 @@ 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.direction += (snap * Math.sign(event.deltaY)); this.data.direction += snap * Math.sign(event.deltaY);
this.refresh(); this.refresh();
}; };

View file

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

View file

@ -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",
@ -14,18 +13,18 @@ export const preloadHandlebarsTemplates = async function() {
"systems/sw5e/templates/actors/oldActor/parts/actor-inventory.html", "systems/sw5e/templates/actors/oldActor/parts/actor-inventory.html",
"systems/sw5e/templates/actors/oldActor/parts/actor-features.html", "systems/sw5e/templates/actors/oldActor/parts/actor-features.html",
"systems/sw5e/templates/actors/oldActor/parts/actor-powerbook.html", "systems/sw5e/templates/actors/oldActor/parts/actor-powerbook.html",
"systems/sw5e/templates/actors/oldActor/parts/actor-notes.html", "systems/sw5e/templates/actors/oldActor/parts/actor-notes.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-biography.html", "systems/sw5e/templates/actors/newActor/parts/swalt-biography.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-core.html", "systems/sw5e/templates/actors/newActor/parts/swalt-core.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-active-effects.html", "systems/sw5e/templates/actors/newActor/parts/swalt-active-effects.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-features.html", "systems/sw5e/templates/actors/newActor/parts/swalt-features.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-inventory.html", "systems/sw5e/templates/actors/newActor/parts/swalt-inventory.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-force-powerbook.html", "systems/sw5e/templates/actors/newActor/parts/swalt-force-powerbook.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-tech-powerbook.html", "systems/sw5e/templates/actors/newActor/parts/swalt-tech-powerbook.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-resources.html", "systems/sw5e/templates/actors/newActor/parts/swalt-resources.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-traits.html", "systems/sw5e/templates/actors/newActor/parts/swalt-traits.html",
// Item Sheet Partials // Item Sheet Partials
"systems/sw5e/templates/items/parts/item-action.html", "systems/sw5e/templates/items/parts/item-action.html",
"systems/sw5e/templates/items/parts/item-activation.html", "systems/sw5e/templates/items/parts/item-activation.html",

161
sw5e.js
View file

@ -8,11 +8,11 @@
*/ */
// 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, getBarAttribute } from "./module/canvas.js"; import {measureDistances, getBarAttribute} from "./module/canvas.js";
// Import Entities // Import Entities
import Actor5e from "./module/actor/entity.js"; import Actor5e from "./module/actor/entity.js";
@ -44,7 +44,7 @@ import * as migrations from "./module/migration.js";
/* Foundry VTT Initialization */ /* Foundry VTT Initialization */
/* -------------------------------------------- */ /* -------------------------------------------- */
Hooks.once("init", function() { 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
@ -69,7 +69,7 @@ Hooks.once("init", function() {
dice: dice, dice: dice,
entities: { entities: {
Actor5e, Actor5e,
Item5e, Item5e
}, },
macros: macros, macros: macros,
migrations: migrations, migrations: migrations,
@ -81,11 +81,7 @@ Hooks.once("init", function() {
CONFIG.Actor.entityClass = Actor5e; CONFIG.Actor.entityClass = Actor5e;
CONFIG.Item.entityClass = Item5e; CONFIG.Item.entityClass = Item5e;
CONFIG.time.roundTime = 6; CONFIG.time.roundTime = 6;
CONFIG.fontFamilies = [ CONFIG.fontFamilies = ["Engli-Besh", "Open Sans", "Russo One"];
"Engli-Besh",
"Open Sans",
"Russo One"
];
// 5e cone RAW should be 53.13 degrees // 5e cone RAW should be 53.13 degrees
CONFIG.MeasuredTemplate.defaults.angle = 53.13; CONFIG.MeasuredTemplate.defaults.angle = 53.13;
@ -107,12 +103,12 @@ Hooks.once("init", function() {
types: ["character"], types: ["character"],
makeDefault: true, makeDefault: true,
label: "SW5E.SheetClassCharacter" label: "SW5E.SheetClassCharacter"
}); });
Actors.registerSheet("sw5e", ActorSheet5eCharacter, { Actors.registerSheet("sw5e", ActorSheet5eCharacter, {
types: ["character"], types: ["character"],
makeDefault: false, makeDefault: false,
label: "SW5E.SheetClassCharacterOld" label: "SW5E.SheetClassCharacterOld"
}); });
Actors.registerSheet("sw5e", ActorSheet5eNPCNew, { Actors.registerSheet("sw5e", ActorSheet5eNPCNew, {
types: ["npc"], types: ["npc"],
makeDefault: true, makeDefault: true,
@ -123,14 +119,31 @@ Hooks.once("init", function() {
makeDefault: false, makeDefault: false,
label: "SW5E.SheetClassNPCOld" label: "SW5E.SheetClassNPCOld"
}); });
Actors.registerSheet('sw5e', ActorSheet5eVehicle, { Actors.registerSheet("sw5e", ActorSheet5eVehicle, {
types: ['vehicle'], types: ["vehicle"],
makeDefault: true, makeDefault: true,
label: "SW5E.SheetClassVehicle" label: "SW5E.SheetClassVehicle"
}); });
Items.unregisterSheet("core", ItemSheet); Items.unregisterSheet("core", ItemSheet);
Items.registerSheet("sw5e", ItemSheet5e, { Items.registerSheet("sw5e", ItemSheet5e, {
types: ['weapon', 'equipment', 'consumable', 'tool', 'loot', 'class', 'power', 'feat', 'species', 'backpack', 'archetype', 'classfeature', 'background', 'fightingmastery', 'fightingstyle', 'lightsaberform'], types: [
"weapon",
"equipment",
"consumable",
"tool",
"loot",
"class",
"power",
"feat",
"species",
"backpack",
"archetype",
"classfeature",
"background",
"fightingmastery",
"fightingstyle",
"lightsaberform"
],
makeDefault: true, makeDefault: true,
label: "SW5E.SheetClassItem" label: "SW5E.SheetClassItem"
}); });
@ -139,7 +152,6 @@ Hooks.once("init", function() {
preloadHandlebarsTemplates(); preloadHandlebarsTemplates();
}); });
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Foundry VTT Setup */ /* Foundry VTT Setup */
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -147,30 +159,70 @@ Hooks.once("init", function() {
/** /**
* This function runs after game data has been requested and loaded from the servers, so entities exist * This function runs after game data has been requested and loaded from the servers, so entities exist
*/ */
Hooks.once("setup", function() { Hooks.once("setup", function () {
// Localize CONFIG objects once up-front // Localize CONFIG objects once up-front
const toLocalize = [ const toLocalize = [
"abilities", "abilityAbbreviations", "abilityActivationTypes", "abilityConsumptionTypes", "actorSizes", "alignments", "abilities",
"armorProficiencies", "armorPropertiesTypes", "conditionTypes", "consumableTypes", "cover", "currencies", "damageResistanceTypes", "abilityAbbreviations",
"damageTypes", "distanceUnits", "equipmentTypes", "healingTypes", "itemActionTypes", "languages", "abilityActivationTypes",
"limitedUsePeriods", "movementTypes", "movementUnits", "polymorphSettings", "proficiencyLevels", "senses", "skills", "abilityConsumptionTypes",
"powerComponents", "powerLevels", "powerPreparationModes", "powerScalingModes", "powerSchools", "targetTypes", "actorSizes",
"timePeriods", "toolProficiencies", "weaponProficiencies", "weaponProperties", "weaponTypes" "alignments",
"armorProficiencies",
"armorPropertiesTypes",
"conditionTypes",
"consumableTypes",
"cover",
"currencies",
"damageResistanceTypes",
"damageTypes",
"distanceUnits",
"equipmentTypes",
"healingTypes",
"itemActionTypes",
"languages",
"limitedUsePeriods",
"movementTypes",
"movementUnits",
"polymorphSettings",
"proficiencyLevels",
"senses",
"skills",
"powerComponents",
"powerLevels",
"powerPreparationModes",
"powerScalingModes",
"powerSchools",
"targetTypes",
"timePeriods",
"toolProficiencies",
"weaponProficiencies",
"weaponProperties",
"weaponTypes"
]; ];
// Exclude some from sorting where the default order matters // Exclude some from sorting where the default order matters
const noSort = [ const noSort = [
"abilities", "alignments", "currencies", "distanceUnits", "movementUnits", "itemActionTypes", "proficiencyLevels", "abilities",
"limitedUsePeriods", "powerComponents", "powerLevels", "powerPreparationModes", "weaponTypes" "alignments",
"currencies",
"distanceUnits",
"movementUnits",
"itemActionTypes",
"proficiencyLevels",
"limitedUsePeriods",
"powerComponents",
"powerLevels",
"powerPreparationModes",
"weaponTypes"
]; ];
// Localize and sort CONFIG objects // Localize and sort CONFIG objects
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;
@ -179,7 +231,7 @@ Hooks.once("setup", function() {
// add DND5E translation for module compatability // add DND5E translation for module compatability
game.i18n.translations.DND5E = game.i18n.translations.SW5E; game.i18n.translations.DND5E = game.i18n.translations.SW5E;
// console.log(game.settings.get("sw5e", "colorTheme")); // console.log(game.settings.get("sw5e", "colorTheme"));
let theme = game.settings.get("sw5e", "colorTheme") + '-theme'; let theme = game.settings.get("sw5e", "colorTheme") + "-theme";
document.body.classList.add(theme); document.body.classList.add(theme);
}); });
@ -188,23 +240,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.2.4.R1-A5"; const NEEDS_MIGRATION_VERSION = "1.2.4.R1-A5";
// Check for R1 SW5E versions // Check for R1 SW5E versions
const SW5E_NEEDS_MIGRATION_VERSION = "R1-A5"; const SW5E_NEEDS_MIGRATION_VERSION = "R1-A5";
const COMPATIBLE_MIGRATION_VERSION = 0.80; const COMPATIBLE_MIGRATION_VERSION = 0.8;
const needsMigration = currentVersion && (isNewerVersion(SW5E_NEEDS_MIGRATION_VERSION, currentVersion) || isNewerVersion(NEEDS_MIGRATION_VERSION, currentVersion)); const needsMigration =
if ( !needsMigration ) return; currentVersion &&
(isNewerVersion(SW5E_NEEDS_MIGRATION_VERSION, currentVersion) ||
isNewerVersion(NEEDS_MIGRATION_VERSION, currentVersion));
if (!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});
} }
@ -215,8 +269,7 @@ 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;
@ -225,13 +278,11 @@ Hooks.on("canvasInit", function() {
Token.prototype.getBarAttribute = getBarAttribute; Token.prototype.getBarAttribute = getBarAttribute;
}); });
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Other Hooks */ /* Other Hooks */
/* -------------------------------------------- */ /* -------------------------------------------- */
Hooks.on("renderChatMessage", (app, html, data) => { Hooks.on("renderChatMessage", (app, html, data) => {
// Display action buttons // Display action buttons
chat.displayChatActionButtons(app, html, data); chat.displayChatActionButtons(app, html, data);
@ -244,38 +295,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");
}); });
// TODO I should remove this // TODO I should remove this
Handlebars.registerHelper('getProperty', function (data, property) { Handlebars.registerHelper("getProperty", function (data, property) {
return getProperty(data, property); return getProperty(data, property);
}); });
function setFolderBackground(html) { function setFolderBackground(html) {
html.find("header.folder-header").each(function() { html.find("header.folder-header").each(function () {
let bgColor = $(this).css("background-color"); let bgColor = $(this).css("background-color");
if(bgColor == undefined) if (bgColor == undefined) bgColor = "rgb(255,255,255)";
bgColor = "rgb(255,255,255)"; $(this).closest("li").css("background-color", bgColor);
$(this).closest('li').css("background-color", bgColor); });
}) }
}