2020-06-24 14:22:36 -04:00
|
|
|
import { ActorSheet5e } from "../sheets/base.js";
|
|
|
|
|
|
|
|
/**
|
2020-09-01 12:13:00 -04:00
|
|
|
* An Actor sheet for NPC type characters in the SW5E system.
|
2020-06-24 14:22:36 -04:00
|
|
|
* Extends the base ActorSheet5e class.
|
|
|
|
* @type {ActorSheet5e}
|
|
|
|
*/
|
|
|
|
export class ActorSheet5eNPC extends ActorSheet5e {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Define default rendering options for the NPC sheet
|
|
|
|
* @return {Object}
|
|
|
|
*/
|
|
|
|
static get defaultOptions() {
|
|
|
|
return mergeObject(super.defaultOptions, {
|
|
|
|
classes: ["sw5e", "sheet", "actor", "npc"],
|
|
|
|
width: 600,
|
|
|
|
height: 658
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/* Rendering */
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the correct HTML template path to use for rendering this particular sheet
|
|
|
|
* @type {String}
|
|
|
|
*/
|
|
|
|
get template() {
|
|
|
|
if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/limited-sheet.html";
|
|
|
|
return "systems/sw5e/templates/actors/npc-sheet.html";
|
|
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Organize Owned Items for rendering the NPC sheet
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_prepareItems(data) {
|
|
|
|
|
|
|
|
// Categorize Items as Features and Powers
|
|
|
|
const features = {
|
|
|
|
weapons: { label: "Attacks", items: [] , hasActions: true, dataset: {type: "weapon", "weapon-type": "natural"} },
|
|
|
|
actions: { label: "Actions", items: [] , hasActions: true, dataset: {type: "feat", "activation.type": "action"} },
|
|
|
|
passive: { label: "Features", items: [], dataset: {type: "feat"} },
|
|
|
|
equipment: { label: "Inventory", items: [], dataset: {type: "loot"}}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Start by classifying items into groups for rendering
|
|
|
|
let [powers, other] = data.items.reduce((arr, item) => {
|
|
|
|
item.img = item.img || DEFAULT_TOKEN;
|
|
|
|
item.isStack = item.data.quantity ? item.data.quantity > 1 : false;
|
|
|
|
item.hasUses = item.data.uses && (item.data.uses.max > 0);
|
|
|
|
item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false);
|
|
|
|
item.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0));
|
|
|
|
item.hasTarget = !!item.data.target && !(["none",""].includes(item.data.target.type));
|
|
|
|
if ( item.type === "power" ) arr[0].push(item);
|
|
|
|
else arr[1].push(item);
|
|
|
|
return arr;
|
|
|
|
}, [[], []]);
|
|
|
|
|
|
|
|
// Apply item filters
|
|
|
|
powers = this._filterItems(powers, this._filters.powerbook);
|
|
|
|
other = this._filterItems(other, this._filters.features);
|
|
|
|
|
|
|
|
// Organize Powerbook
|
|
|
|
const powerbook = this._preparePowerbook(data, powers);
|
|
|
|
|
|
|
|
// Organize Features
|
|
|
|
for ( let item of other ) {
|
|
|
|
if ( item.type === "weapon" ) features.weapons.items.push(item);
|
|
|
|
else if ( item.type === "feat" ) {
|
|
|
|
if ( item.data.activation.type ) features.actions.items.push(item);
|
|
|
|
else features.passive.items.push(item);
|
|
|
|
}
|
|
|
|
else features.equipment.items.push(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assign and return
|
|
|
|
data.features = Object.values(features);
|
|
|
|
data.powerbook = powerbook;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add some extra data when rendering the sheet to reduce the amount of logic required within the template.
|
|
|
|
*/
|
|
|
|
getData() {
|
|
|
|
const data = super.getData();
|
|
|
|
|
|
|
|
// Challenge Rating
|
|
|
|
const cr = parseFloat(data.data.details.cr || 0);
|
|
|
|
const crLabels = {0: "0", 0.125: "1/8", 0.25: "1/4", 0.5: "1/2"};
|
|
|
|
data.labels["cr"] = cr >= 1 ? String(cr) : crLabels[cr] || 1;
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/* Object Updates */
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This method is called upon form submission after form data is validated
|
|
|
|
* @param event {Event} The initial triggering submission event
|
|
|
|
* @param formData {Object} The object of validated form data with which to update the object
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_updateObject(event, formData) {
|
|
|
|
|
|
|
|
// Format NPC Challenge Rating
|
|
|
|
const crs = {"1/8": 0.125, "1/4": 0.25, "1/2": 0.5};
|
|
|
|
let crv = "data.details.cr";
|
|
|
|
let cr = formData[crv];
|
|
|
|
cr = crs[cr] || parseFloat(cr);
|
|
|
|
if ( cr ) formData[crv] = cr < 1 ? cr : parseInt(cr);
|
|
|
|
|
|
|
|
// Parent ActorSheet update steps
|
|
|
|
super._updateObject(event, formData);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/* Event Listeners and Handlers */
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Activate event listeners using the prepared sheet HTML
|
|
|
|
* @param html {HTML} The prepared HTML object ready to be rendered into the DOM
|
|
|
|
*/
|
|
|
|
activateListeners(html) {
|
|
|
|
super.activateListeners(html);
|
|
|
|
|
|
|
|
// Rollable Health Formula
|
|
|
|
html.find(".health .rollable").click(this._onRollHealthFormula.bind(this));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle rolling NPC health values using the provided formula
|
|
|
|
* @param {Event} event The original click event
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_onRollHealthFormula(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
const formula = this.actor.data.data.attributes.hp.formula;
|
|
|
|
if ( !formula ) return;
|
|
|
|
const hp = new Roll(formula).roll().total;
|
|
|
|
AudioHelper.play({src: CONFIG.sounds.dice});
|
|
|
|
this.actor.update({"data.attributes.hp.value": hp, "data.attributes.hp.max": hp});
|
|
|
|
}
|
|
|
|
}
|