forked from GitHub-Mirrors/foundry-sw5e

System main update to be inline with dnd5e 1.1.1 Added active effects to as many sheets as I thought applicable. Please check loot, I made an attempt but it may be broken All .less .css and actor .html updates were made to the old actors. New actors may be broken with this update removed templates\actors\oldActor\parts\actor-effects.html for newer templates\actors\parts\active-effects.html removed module\apps\cast-dialog, templates\apps\cast-cast.html, and templates\items\cast.html. I do not think they are used, I think they were deprecated when powers were treated as items, if not we can add them back in. **NOTE** REQUIRES Foundry 0.7.6
385 lines
12 KiB
JavaScript
385 lines
12 KiB
JavaScript
import ActorSheet5e from "../base.js";
|
|
|
|
/**
|
|
* An Actor sheet for Vehicle type actors.
|
|
* Extends the base ActorSheet5e class.
|
|
* @type {ActorSheet5e}
|
|
*/
|
|
export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|
/**
|
|
* Define default rendering options for the Vehicle sheet.
|
|
* @returns {Object}
|
|
*/
|
|
static get defaultOptions() {
|
|
return mergeObject(super.defaultOptions, {
|
|
classes: ["sw5e", "sheet", "actor", "vehicle"],
|
|
width: 605,
|
|
height: 680
|
|
});
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Creates a new cargo entry for a vehicle Actor.
|
|
*/
|
|
static get newCargo() {
|
|
return {
|
|
name: '',
|
|
quantity: 1
|
|
};
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Compute the total weight of the vehicle's cargo.
|
|
* @param {Number} totalWeight The cumulative item weight from inventory items
|
|
* @param {Object} actorData The data object for the Actor being rendered
|
|
* @returns {{max: number, value: number, pct: number}}
|
|
* @private
|
|
*/
|
|
_computeEncumbrance(totalWeight, actorData) {
|
|
|
|
// Compute currency weight
|
|
const totalCoins = Object.values(actorData.data.currency).reduce((acc, denom) => acc + denom, 0);
|
|
totalWeight += totalCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
|
|
|
|
// Vehicle weights are an order of magnitude greater.
|
|
totalWeight /= CONFIG.SW5E.encumbrance.vehicleWeightMultiplier;
|
|
|
|
// Compute overall encumbrance
|
|
const max = actorData.data.attributes.capacity.cargo;
|
|
const pct = Math.clamped((totalWeight * 100) / max, 0, 100);
|
|
return {value: totalWeight.toNearest(0.1), max, pct};
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Prepare items that are mounted to a vehicle and require one or more crew
|
|
* to operate.
|
|
* @private
|
|
*/
|
|
_prepareCrewedItem(item) {
|
|
|
|
// Determine crewed status
|
|
const isCrewed = item.data.crewed;
|
|
item.toggleClass = isCrewed ? 'active' : '';
|
|
item.toggleTitle = game.i18n.localize(`SW5E.${isCrewed ? 'Crewed' : 'Uncrewed'}`);
|
|
|
|
// Handle crew actions
|
|
if (item.type === 'feat' && item.data.activation.type === 'crew') {
|
|
item.crew = item.data.activation.cost;
|
|
item.cover = game.i18n.localize(`SW5E.${item.data.cover ? 'CoverTotal' : 'None'}`);
|
|
if (item.data.cover === .5) item.cover = '½';
|
|
else if (item.data.cover === .75) item.cover = '¾';
|
|
else if (item.data.cover === null) item.cover = '—';
|
|
if (item.crew < 1 || item.crew === null) item.crew = '—';
|
|
}
|
|
|
|
// Prepare vehicle weapons
|
|
if (item.type === 'equipment' || item.type === 'weapon') {
|
|
item.threshold = item.data.hp.dt ? item.data.hp.dt : '—';
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/** @override */
|
|
_getMovementSpeed(actorData) {
|
|
return {primary: "", special: ""};
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Organize Owned Items for rendering the Vehicle sheet.
|
|
* @private
|
|
*/
|
|
_prepareItems(data) {
|
|
const cargoColumns = [{
|
|
label: game.i18n.localize('SW5E.Quantity'),
|
|
css: 'item-qty',
|
|
property: 'quantity',
|
|
editable: 'Number'
|
|
}];
|
|
|
|
const equipmentColumns = [{
|
|
label: game.i18n.localize('SW5E.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.HP'),
|
|
css: 'item-hp',
|
|
property: 'data.hp.value',
|
|
editable: 'Number'
|
|
}, {
|
|
label: game.i18n.localize('SW5E.Threshold'),
|
|
css: 'item-threshold',
|
|
property: 'threshold'
|
|
}];
|
|
|
|
const features = {
|
|
actions: {
|
|
label: game.i18n.localize('SW5E.ActionPl'),
|
|
items: [],
|
|
crewable: true,
|
|
dataset: {type: 'feat', 'activation.type': 'crew'},
|
|
columns: [{
|
|
label: game.i18n.localize('SW5E.VehicleCrew'),
|
|
css: 'item-crew',
|
|
property: 'crew'
|
|
}, {
|
|
label: game.i18n.localize('SW5E.Cover'),
|
|
css: 'item-cover',
|
|
property: 'cover'
|
|
}]
|
|
},
|
|
equipment: {
|
|
label: game.i18n.localize('SW5E.ItemTypeEquipment'),
|
|
items: [],
|
|
crewable: true,
|
|
dataset: {type: 'equipment', 'armor.type': 'vehicle'},
|
|
columns: equipmentColumns
|
|
},
|
|
passive: {
|
|
label: game.i18n.localize('SW5E.Features'),
|
|
items: [],
|
|
dataset: {type: 'feat'}
|
|
},
|
|
reactions: {
|
|
label: game.i18n.localize('SW5E.ReactionPl'),
|
|
items: [],
|
|
dataset: {type: 'feat', 'activation.type': 'reaction'}
|
|
},
|
|
weapons: {
|
|
label: game.i18n.localize('SW5E.ItemTypeWeaponPl'),
|
|
items: [],
|
|
crewable: true,
|
|
dataset: {type: 'weapon', 'weapon-type': 'siege'},
|
|
columns: equipmentColumns
|
|
}
|
|
};
|
|
|
|
const cargo = {
|
|
crew: {
|
|
label: game.i18n.localize('SW5E.VehicleCrew'),
|
|
items: data.data.cargo.crew,
|
|
css: 'cargo-row crew',
|
|
editableName: true,
|
|
dataset: {type: 'crew'},
|
|
columns: cargoColumns
|
|
},
|
|
passengers: {
|
|
label: game.i18n.localize('SW5E.VehiclePassengers'),
|
|
items: data.data.cargo.passengers,
|
|
css: 'cargo-row passengers',
|
|
editableName: true,
|
|
dataset: {type: 'passengers'},
|
|
columns: cargoColumns
|
|
},
|
|
cargo: {
|
|
label: game.i18n.localize('SW5E.VehicleCargo'),
|
|
items: [],
|
|
dataset: {type: 'loot'},
|
|
columns: [{
|
|
label: game.i18n.localize('SW5E.Quantity'),
|
|
css: 'item-qty',
|
|
property: 'data.quantity',
|
|
editable: 'Number'
|
|
}, {
|
|
label: game.i18n.localize('SW5E.Price'),
|
|
css: 'item-price',
|
|
property: 'data.price',
|
|
editable: 'Number'
|
|
}, {
|
|
label: game.i18n.localize('SW5E.Weight'),
|
|
css: 'item-weight',
|
|
property: 'data.weight',
|
|
editable: 'Number'
|
|
}]
|
|
}
|
|
};
|
|
|
|
let totalWeight = 0;
|
|
for (const item of data.items) {
|
|
this._prepareCrewedItem(item);
|
|
if (item.type === 'weapon') features.weapons.items.push(item);
|
|
else if (item.type === 'equipment') features.equipment.items.push(item);
|
|
else if (item.type === 'loot') {
|
|
totalWeight += (item.data.weight || 0) * item.data.quantity;
|
|
cargo.cargo.items.push(item);
|
|
}
|
|
else if (item.type === 'feat') {
|
|
if (!item.data.activation.type || item.data.activation.type === 'none') {
|
|
features.passive.items.push(item);
|
|
}
|
|
else if (item.data.activation.type === 'reaction') features.reactions.items.push(item);
|
|
else features.actions.items.push(item);
|
|
}
|
|
}
|
|
|
|
data.features = Object.values(features);
|
|
data.cargo = Object.values(cargo);
|
|
data.data.attributes.encumbrance = this._computeEncumbrance(totalWeight, data);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* Event Listeners and Handlers */
|
|
/* -------------------------------------------- */
|
|
|
|
/** @override */
|
|
activateListeners(html) {
|
|
super.activateListeners(html);
|
|
if (!this.options.editable) return;
|
|
|
|
html.find('.item-toggle').click(this._onToggleItem.bind(this));
|
|
html.find('.item-hp input')
|
|
.click(evt => evt.target.select())
|
|
.change(this._onHPChange.bind(this));
|
|
|
|
html.find('.item:not(.cargo-row) input[data-property]')
|
|
.click(evt => evt.target.select())
|
|
.change(this._onEditInSheet.bind(this));
|
|
|
|
html.find('.cargo-row input')
|
|
.click(evt => evt.target.select())
|
|
.change(this._onCargoRowChange.bind(this));
|
|
|
|
if (this.actor.data.data.attributes.actions.stations) {
|
|
html.find('.counter.actions, .counter.action-thresholds').hide();
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Handle saving a cargo row (i.e. crew or passenger) in-sheet.
|
|
* @param event {Event}
|
|
* @returns {Promise<Actor>|null}
|
|
* @private
|
|
*/
|
|
_onCargoRowChange(event) {
|
|
event.preventDefault();
|
|
const target = event.currentTarget;
|
|
const row = target.closest('.item');
|
|
const idx = Number(row.dataset.itemId);
|
|
const property = row.classList.contains('crew') ? 'crew' : 'passengers';
|
|
|
|
// Get the cargo entry
|
|
const cargo = duplicate(this.actor.data.data.cargo[property]);
|
|
const entry = cargo[idx];
|
|
if (!entry) return null;
|
|
|
|
// Update the cargo value
|
|
const key = target.dataset.property || 'name';
|
|
const type = target.dataset.dtype;
|
|
let value = target.value;
|
|
if (type === 'Number') value = Number(value);
|
|
entry[key] = value;
|
|
|
|
// Perform the Actor update
|
|
return this.actor.update({[`data.cargo.${property}`]: cargo});
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Handle editing certain values like quantity, price, and weight in-sheet.
|
|
* @param event {Event}
|
|
* @returns {Promise<Item>}
|
|
* @private
|
|
*/
|
|
_onEditInSheet(event) {
|
|
event.preventDefault();
|
|
const itemID = event.currentTarget.closest('.item').dataset.itemId;
|
|
const item = this.actor.items.get(itemID);
|
|
const property = event.currentTarget.dataset.property;
|
|
const type = event.currentTarget.dataset.dtype;
|
|
let value = event.currentTarget.value;
|
|
switch (type) {
|
|
case 'Number': value = parseInt(value); break;
|
|
case 'Boolean': value = value === 'true'; break;
|
|
}
|
|
return item.update({[`${property}`]: value});
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Handle creating a new crew or passenger row.
|
|
* @param event {Event}
|
|
* @returns {Promise<Actor|Item>}
|
|
* @private
|
|
*/
|
|
_onItemCreate(event) {
|
|
event.preventDefault();
|
|
const target = event.currentTarget;
|
|
const type = target.dataset.type;
|
|
if (type === 'crew' || type === 'passengers') {
|
|
const cargo = duplicate(this.actor.data.data.cargo[type]);
|
|
cargo.push(this.constructor.newCargo);
|
|
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
|
}
|
|
return super._onItemCreate(event);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Handle deleting a crew or passenger row.
|
|
* @param event {Event}
|
|
* @returns {Promise<Actor|Item>}
|
|
* @private
|
|
*/
|
|
_onItemDelete(event) {
|
|
event.preventDefault();
|
|
const row = event.currentTarget.closest('.item');
|
|
if (row.classList.contains('cargo-row')) {
|
|
const idx = Number(row.dataset.itemId);
|
|
const type = row.classList.contains('crew') ? 'crew' : 'passengers';
|
|
const cargo = duplicate(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx);
|
|
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
|
}
|
|
|
|
return super._onItemDelete(event);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Special handling for editing HP to clamp it within appropriate range.
|
|
* @param event {Event}
|
|
* @returns {Promise<Item>}
|
|
* @private
|
|
*/
|
|
_onHPChange(event) {
|
|
event.preventDefault();
|
|
const itemID = event.currentTarget.closest('.item').dataset.itemId;
|
|
const item = this.actor.items.get(itemID);
|
|
const hp = Math.clamped(0, parseInt(event.currentTarget.value), item.data.data.hp.max);
|
|
event.currentTarget.value = hp;
|
|
return item.update({'data.hp.value': hp});
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Handle toggling an item's crewed status.
|
|
* @param event {Event}
|
|
* @returns {Promise<Item>}
|
|
* @private
|
|
*/
|
|
_onToggleItem(event) {
|
|
event.preventDefault();
|
|
const itemID = event.currentTarget.closest('.item').dataset.itemId;
|
|
const item = this.actor.items.get(itemID);
|
|
const crewed = !!item.data.data.crewed;
|
|
return item.update({'data.crewed': !crewed});
|
|
}
|
|
};
|