forked from GitHub-Mirrors/foundry-sw5e
Updated to DND5e 1.3.2
Things unfinished: - Migration - The update adds new sections to the class sheet to allow some light customisation, this hasn't been included, but could be extended for the sake of dynamic classes with automatic class features and more - The French - The packs have not yet been updated, meaning due to the addition of a progression field to the class item, classes now don't set force or tech points - I updated the function calls in starships, but I didn't update it very thoroughly, it'll need checking - I only did a little testing - There has since been updates to DND5e that hasn't made it to release that patch bugs, those should be implemented Things changed from base 5e: - Short rests and long rests were merged into one function, this needed some rewrites to account for force and tech points, and for printing the correct message Extra Comments: - Unfinished code exists for automatic spell scrolls, this could be extended for single use force or tech powers - Weapon proficiencies probably need revising - Elven accuracy, halfling lucky, and reliable talent are present in the roll logic, this probably needs revising for sw5e - SW5e has a variant rule that permits force powers of any alignment to use either charisma or wisdom, that could be implemented - SW5e's version of gritty realism, [Longer Rests](https://sw5e.com/rules/variantRules/Longer%20Rests) differs from base dnd, this could be implemented - Extra ideas I've had while looking through the code can be found in Todos next to the ideas relevant context
This commit is contained in:
parent
aa07380c57
commit
2a7e1c419e
72 changed files with 3107 additions and 1359 deletions
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,10 @@
|
|||
import Item5e from "../../../item/entity.js";
|
||||
import TraitSelector from "../../../apps/trait-selector.js";
|
||||
import ActorSheetFlags from "../../../apps/actor-flags.js";
|
||||
import ActorHitDiceConfig from "../../../apps/hit-dice-config.js";
|
||||
import ActorMovementConfig from "../../../apps/movement-config.js";
|
||||
import ActorSensesConfig from "../../../apps/senses-config.js";
|
||||
import ActorTypeConfig from "../../../apps/actor-type.js";
|
||||
import {SW5E} from '../../../config.js';
|
||||
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js";
|
||||
|
||||
|
@ -46,6 +48,14 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A set of item types that should be prevented from being dropped on this type of actor sheet.
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
static unsupportedItemTypes = new Set();
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
get template() {
|
||||
if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/newActor/expanded-limited-sheet.html";
|
||||
|
@ -55,44 +65,51 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
getData(options) {
|
||||
|
||||
// Basic data
|
||||
let isOwner = this.entity.owner;
|
||||
let isOwner = this.actor.isOwner;
|
||||
const data = {
|
||||
owner: isOwner,
|
||||
limited: this.entity.limited,
|
||||
limited: this.actor.limited,
|
||||
options: this.options,
|
||||
editable: this.isEditable,
|
||||
cssClass: isOwner ? "editable" : "locked",
|
||||
isCharacter: this.entity.data.type === "character",
|
||||
isNPC: this.entity.data.type === "npc",
|
||||
isStarship: this.entity.data.type === "starship",
|
||||
isVehicle: this.entity.data.type === 'vehicle',
|
||||
isCharacter: this.actor.data.type === "character",
|
||||
isNPC: this.actor.data.type === "npc",
|
||||
isStarship: this.actor.data.type === "starship",
|
||||
isVehicle: this.actor.data.type === 'vehicle',
|
||||
config: CONFIG.SW5E,
|
||||
rollData: this.actor.getRollData.bind(this.actor)
|
||||
};
|
||||
|
||||
// The Actor and its Items
|
||||
data.actor = duplicate(this.actor.data);
|
||||
data.items = this.actor.items.map(i => {
|
||||
i.data.labels = i.labels;
|
||||
return i.data;
|
||||
});
|
||||
// The Actor's data
|
||||
const actorData = this.actor.data.toObject(false);
|
||||
data.actor = actorData;
|
||||
data.data = actorData.data;
|
||||
|
||||
// Owned Items
|
||||
data.items = actorData.items;
|
||||
for ( let i of data.items ) {
|
||||
const item = this.actor.items.get(i._id);
|
||||
i.labels = item.labels;
|
||||
}
|
||||
data.items.sort((a, b) => (a.sort || 0) - (b.sort || 0));
|
||||
data.data = data.actor.data;
|
||||
|
||||
// Labels and filters
|
||||
data.labels = this.actor.labels || {};
|
||||
data.filters = this._filters;
|
||||
|
||||
// Ability Scores
|
||||
for ( let [a, abl] of Object.entries(data.actor.data.abilities)) {
|
||||
for ( let [a, abl] of Object.entries(actorData.data.abilities)) {
|
||||
abl.icon = this._getProficiencyIcon(abl.proficient);
|
||||
abl.hover = CONFIG.SW5E.proficiencyLevels[abl.proficient];
|
||||
abl.label = CONFIG.SW5E.abilities[a];
|
||||
}
|
||||
|
||||
// Skills
|
||||
if (data.actor.data.skills) {
|
||||
for ( let [s, skl] of Object.entries(data.actor.data.skills)) {
|
||||
if (actorData.data.skills) {
|
||||
for ( let [s, skl] of Object.entries(actorData.data.skills)) {
|
||||
skl.ability = CONFIG.SW5E.abilityAbbreviations[skl.ability];
|
||||
skl.icon = this._getProficiencyIcon(skl.value);
|
||||
skl.hover = CONFIG.SW5E.proficiencyLevels[skl.value];
|
||||
|
@ -105,19 +122,19 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
}
|
||||
|
||||
// Movement speeds
|
||||
data.movement = this._getMovementSpeed(data.actor);
|
||||
data.movement = this._getMovementSpeed(actorData);
|
||||
|
||||
// Senses
|
||||
data.senses = this._getSenses(data.actor);
|
||||
data.senses = this._getSenses(actorData);
|
||||
|
||||
// Update traits
|
||||
this._prepareTraits(data.actor.data.traits);
|
||||
this._prepareTraits(actorData.data.traits);
|
||||
|
||||
// Prepare owned items
|
||||
this._prepareItems(data);
|
||||
|
||||
// Prepare active effects
|
||||
data.effects = prepareActiveEffectCategories(this.entity.effects);
|
||||
data.effects = prepareActiveEffectCategories(this.actor.effects);
|
||||
|
||||
// Return data to the sheet
|
||||
return data
|
||||
|
@ -223,12 +240,13 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/**
|
||||
* Insert a power into the powerbook object when rendering the character sheet
|
||||
* @param {Object} data The Actor data being prepared
|
||||
* @param {Array} powers The power data being prepared
|
||||
* @param {Object} data The Actor data being prepared
|
||||
* @param {Array} powers The power data being prepared
|
||||
* @param {string} school The school of the powerbook being prepared
|
||||
* @private
|
||||
*/
|
||||
_preparePowerbook(data, powers, school) {
|
||||
const owner = this.actor.owner;
|
||||
const owner = this.actor.isOwner;
|
||||
const levels = data.data.powers;
|
||||
const powerbook = {};
|
||||
|
||||
|
@ -413,18 +431,18 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
html.find('.item-create').click(this._onItemCreate.bind(this));
|
||||
html.find('.item-edit').click(this._onItemEdit.bind(this));
|
||||
html.find('.item-delete').click(this._onItemDelete.bind(this));
|
||||
html.find('.item-collapse').click(this._onItemCollapse.bind(this));
|
||||
html.find('.item-collapse').click(this._onItemCollapse.bind(this));
|
||||
html.find('.item-uses input').click(ev => ev.target.select()).change(this._onUsesChange.bind(this));
|
||||
html.find('.slot-max-override').click(this._onPowerSlotOverride.bind(this));
|
||||
html.find('.increment-class-level').click(this._onIncrementClassLevel.bind(this));
|
||||
html.find('.decrement-class-level').click(this._onDecrementClassLevel.bind(this));
|
||||
html.find('.increment-class-level').click(this._onIncrementClassLevel.bind(this));
|
||||
html.find('.decrement-class-level').click(this._onDecrementClassLevel.bind(this));
|
||||
|
||||
// Active Effect management
|
||||
html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.entity));
|
||||
html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.actor));
|
||||
}
|
||||
|
||||
// Owner Only Listeners
|
||||
if ( this.actor.owner ) {
|
||||
if ( this.actor.isOwner ) {
|
||||
|
||||
// Ability Checks
|
||||
html.find('.ability-name').click(this._onRollAbilityTest.bind(this));
|
||||
|
@ -491,17 +509,25 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onConfigMenu(event) {
|
||||
event.preventDefault();
|
||||
const button = event.currentTarget;
|
||||
let app;
|
||||
switch ( button.dataset.action ) {
|
||||
case "hit-dice":
|
||||
app = new ActorHitDiceConfig(this.object);
|
||||
break;
|
||||
case "movement":
|
||||
new ActorMovementConfig(this.object).render(true);
|
||||
app = new ActorMovementConfig(this.object);
|
||||
break;
|
||||
case "flags":
|
||||
new ActorSheetFlags(this.object).render(true);
|
||||
app = new ActorSheetFlags(this.object);
|
||||
break;
|
||||
case "senses":
|
||||
new ActorSensesConfig(this.object).render(true);
|
||||
app = new ActorSensesConfig(this.object);
|
||||
break;
|
||||
case "type":
|
||||
new ActorTypeConfig(this.object).render(true);
|
||||
break;
|
||||
}
|
||||
app?.render(true);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -535,7 +561,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/** @override */
|
||||
async _onDropActor(event, data) {
|
||||
const canPolymorph = game.user.isGM || (this.actor.owner && game.settings.get('sw5e', 'allowPolymorphing'));
|
||||
const canPolymorph = game.user.isGM || (this.actor.isOwner && game.settings.get('sw5e', 'allowPolymorphing'));
|
||||
if ( !canPolymorph ) return false;
|
||||
|
||||
// Get the target actor
|
||||
|
@ -610,15 +636,40 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
/** @override */
|
||||
async _onDropItemCreate(itemData) {
|
||||
|
||||
// Check to make sure items of this type are allowed on this actor
|
||||
if ( this.constructor.unsupportedItemTypes.has(itemData.type) ) {
|
||||
return ui.notifications.warn(game.i18n.format("SW5E.ActorWarningInvalidItem", {
|
||||
itemType: game.i18n.localize(CONFIG.Item.typeLabels[itemData.type]),
|
||||
actorType: game.i18n.localize(CONFIG.Actor.typeLabels[this.actor.type])
|
||||
}));
|
||||
}
|
||||
|
||||
// Create a Consumable power scroll on the Inventory tab
|
||||
if ( (itemData.type === "power") && (this._tabs[0].active === "inventory") ) {
|
||||
const scroll = await Item5e.createScrollFromPower(itemData);
|
||||
itemData = scroll.data;
|
||||
}
|
||||
|
||||
// Ignore certain statuses
|
||||
if ( itemData.data ) {
|
||||
["attunement", "equipped", "proficient", "prepared"].forEach(k => delete itemData.data[k]);
|
||||
// Ignore certain statuses
|
||||
["equipped", "proficient", "prepared"].forEach(k => delete itemData.data[k]);
|
||||
|
||||
// Downgrade ATTUNED to REQUIRED
|
||||
itemData.data.attunement = Math.min(itemData.data.attunement, CONFIG.SW5E.attunementTypes.REQUIRED);
|
||||
}
|
||||
|
||||
// Stack identical consumables
|
||||
if ( itemData.type === "consumable" && itemData.flags.core?.sourceId ) {
|
||||
const similarItem = this.actor.items.find(i => {
|
||||
const sourceId = i.getFlag("core", "sourceId");
|
||||
return sourceId && (sourceId === itemData.flags.core?.sourceId) &&
|
||||
(i.type === "consumable");
|
||||
});
|
||||
if ( similarItem ) {
|
||||
return similarItem.update({
|
||||
'data.quantity': similarItem.data.data.quantity + Math.max(itemData.data.quantity, 1)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create the owned item as normal
|
||||
|
@ -659,7 +710,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
async _onUsesChange(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const item = this.actor.items.get(itemId);
|
||||
const uses = Math.clamped(0, parseInt(event.target.value), item.data.data.uses.max);
|
||||
event.target.value = uses;
|
||||
return item.update({ 'data.uses.value': uses });
|
||||
|
@ -674,7 +725,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onItemRoll(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const item = this.actor.items.get(itemId);
|
||||
return item.roll();
|
||||
}
|
||||
|
||||
|
@ -688,7 +739,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onItemRecharge(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const item = this.actor.items.get(itemId);
|
||||
return item.rollRecharge();
|
||||
};
|
||||
|
||||
|
@ -701,8 +752,8 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onItemSummary(event) {
|
||||
event.preventDefault();
|
||||
let li = $(event.currentTarget).parents(".item"),
|
||||
item = this.actor.getOwnedItem(li.data("item-id")),
|
||||
chatData = item.getChatData({secrets: this.actor.owner});
|
||||
item = this.actor.items.get(li.data("item-id")),
|
||||
chatData = item.getChatData({secrets: this.actor.isOwner});
|
||||
|
||||
// Toggle summary
|
||||
if ( li.hasClass("expanded") ) {
|
||||
|
@ -733,10 +784,10 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
const itemData = {
|
||||
name: game.i18n.format("SW5E.ItemNew", {type: type.capitalize()}),
|
||||
type: type,
|
||||
data: duplicate(header.dataset)
|
||||
data: foundry.utils.deepClone(header.dataset)
|
||||
};
|
||||
delete itemData.data["type"];
|
||||
return this.actor.createEmbeddedEntity("OwnedItem", itemData);
|
||||
return this.actor.createEmbeddedDocuments("Item", [itemData]);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -749,8 +800,8 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onItemEdit(event) {
|
||||
event.preventDefault();
|
||||
const li = event.currentTarget.closest(".item");
|
||||
const item = this.actor.getOwnedItem(li.dataset.itemId);
|
||||
item.sheet.render(true);
|
||||
const item = this.actor.items.get(li.dataset.itemId);
|
||||
return item.sheet.render(true);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -763,7 +814,8 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onItemDelete(event) {
|
||||
event.preventDefault();
|
||||
const li = event.currentTarget.closest(".item");
|
||||
this.actor.deleteOwnedItem(li.dataset.itemId);
|
||||
const item = this.actor.items.get(li.dataset.itemId);
|
||||
if ( item ) return item.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -803,12 +855,12 @@ _onItemCollapse(event) {
|
|||
const itemId = li.dataset.itemId;
|
||||
|
||||
const actor = game.actors.get(actorId);
|
||||
const item = actor.getOwnedItem(itemId);
|
||||
const item = actor.items.get(itemId);
|
||||
|
||||
let levels = item.data.data.levels;
|
||||
const update = {_id: item._id, data: {levels: (levels + 1) }};
|
||||
const update = {_id: item.data._id, data: {levels: (levels + 1) }};
|
||||
|
||||
actor.updateOwnedItem(update)
|
||||
actor.updateEmbeddedDocuments("Item", [update]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -827,12 +879,12 @@ _onItemCollapse(event) {
|
|||
const itemId = li.dataset.itemId;
|
||||
|
||||
const actor = game.actors.get(actorId);
|
||||
const item = actor.getOwnedItem(itemId);
|
||||
const item = actor.items.get(itemId);
|
||||
|
||||
let levels = item.data.data.levels;
|
||||
const update = {_id: item._id, data: {levels: (levels - 1) }};
|
||||
const update = {_id: item.data._id, data: {levels: (levels - 1) }};
|
||||
|
||||
actor.updateOwnedItem(update)
|
||||
actor.updateEmbeddedDocuments("Item", [update]);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -845,7 +897,7 @@ _onItemCollapse(event) {
|
|||
_onRollAbilityTest(event) {
|
||||
event.preventDefault();
|
||||
let ability = event.currentTarget.parentElement.dataset.ability;
|
||||
this.actor.rollAbility(ability, {event: event});
|
||||
return this.actor.rollAbility(ability, {event: event});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -858,7 +910,7 @@ _onItemCollapse(event) {
|
|||
_onRollSkillCheck(event) {
|
||||
event.preventDefault();
|
||||
const skill = event.currentTarget.parentElement.dataset.skill;
|
||||
this.actor.rollSkill(skill, {event: event});
|
||||
return this.actor.rollSkill(skill, {event: event});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -871,7 +923,7 @@ _onItemCollapse(event) {
|
|||
_onToggleAbilityProficiency(event) {
|
||||
event.preventDefault();
|
||||
const field = event.currentTarget.previousElementSibling;
|
||||
this.actor.update({[field.name]: 1 - parseInt(field.value)});
|
||||
return this.actor.update({[field.name]: 1 - parseInt(field.value)});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -888,7 +940,7 @@ _onItemCollapse(event) {
|
|||
const filter = li.dataset.filter;
|
||||
if ( set.has(filter) ) set.delete(filter);
|
||||
else set.add(filter);
|
||||
this.render();
|
||||
return this.render();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -904,7 +956,7 @@ _onItemCollapse(event) {
|
|||
const label = a.parentElement.querySelector("label");
|
||||
const choices = CONFIG.SW5E[a.dataset.options];
|
||||
const options = { name: a.dataset.target, title: label.innerText, choices };
|
||||
new TraitSelector(this.actor, options).render(true)
|
||||
return new TraitSelector(this.actor, options).render(true)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -912,15 +964,14 @@ _onItemCollapse(event) {
|
|||
/** @override */
|
||||
_getHeaderButtons() {
|
||||
let buttons = super._getHeaderButtons();
|
||||
|
||||
// Add button to revert polymorph
|
||||
if ( !this.actor.isPolymorphed || this.actor.isToken ) return buttons;
|
||||
buttons.unshift({
|
||||
label: 'SW5E.PolymorphRestoreTransformation',
|
||||
class: "restore-transformation",
|
||||
icon: "fas fa-backward",
|
||||
onclick: ev => this.actor.revertOriginalForm()
|
||||
});
|
||||
if (this.actor.isPolymorphed) {
|
||||
buttons.unshift({
|
||||
label: 'SW5E.PolymorphRestoreTransformation',
|
||||
class: "restore-transformation",
|
||||
icon: "fas fa-backward",
|
||||
onclick: () => this.actor.revertOriginalForm()
|
||||
});
|
||||
}
|
||||
return buttons;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
|||
let [items, forcepowers, techpowers, feats, classes, deployments, deploymentfeatures, ventures, species, archetypes, classfeatures, backgrounds, fightingstyles, fightingmasteries, lightsaberforms] = data.items.reduce((arr, item) => {
|
||||
|
||||
// Item details
|
||||
item.img = item.img || DEFAULT_TOKEN;
|
||||
item.img = item.img || CONST.DEFAULT_TOKEN;
|
||||
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
||||
item.attunement = {
|
||||
[CONFIG.SW5E.attunementTypes.REQUIRED]: {
|
||||
|
@ -111,6 +111,9 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
|||
// Item toggle state
|
||||
this._prepareItemToggleState(item);
|
||||
|
||||
// Primary Class
|
||||
if ( item.type === "class" ) item.isOriginalClass = ( item.data._id === this.actor.data.data.details.originalClass );
|
||||
|
||||
// Classify items into types
|
||||
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);
|
||||
|
@ -168,7 +171,7 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
|||
if ( f.data.activation.type ) features.active.items.push(f);
|
||||
else features.passive.items.push(f);
|
||||
}
|
||||
classes.sort((a, b) => b.levels - a.levels);
|
||||
classes.sort((a, b) => b.data.levels - a.data.levels);
|
||||
features.classes.items = classes;
|
||||
features.classfeatures.items = classfeatures;
|
||||
features.archetype.items = archetypes;
|
||||
|
@ -260,10 +263,10 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
|||
if (["gmroll", "blindroll"].includes(rollMode)) rollWhisper = ChatMessage.getWhisperIDs("GM");
|
||||
if (rollMode === "blindroll") rollBlind = true;
|
||||
ChatMessage.create({
|
||||
user: game.user._id,
|
||||
user: game.user.data._id,
|
||||
content: content,
|
||||
speaker: {
|
||||
actor: this.actor._id,
|
||||
actor: this.actor.data._id,
|
||||
token: this.actor.token,
|
||||
alias: this.actor.name
|
||||
},
|
||||
|
@ -276,7 +279,7 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
|||
html.find('.item-delete').click(event => {
|
||||
let li = $(event.currentTarget).parents('.item');
|
||||
let itemId = li.attr("data-item-id");
|
||||
let item = this.actor.getOwnedItem(itemId);
|
||||
let item = this.actor.items.get(itemId);
|
||||
new Dialog({
|
||||
title: `Deleting ${item.data.name}`,
|
||||
content: `<p>Are you sure you want to delete ${item.data.name}?</p>`,
|
||||
|
@ -327,7 +330,7 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
|||
_onToggleItem(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const item = this.actor.items.get(itemId);
|
||||
const attr = item.data.type === "power" ? "data.preparation.prepared" : "data.equipped";
|
||||
return item.update({[attr]: !getProperty(item.data, attr)});
|
||||
}
|
||||
|
@ -390,7 +393,7 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
|||
// }
|
||||
|
||||
// Default drop handling if levels were not added
|
||||
super._onDropItemCreate(itemData);
|
||||
return super._onDropItemCreate(itemData);
|
||||
}
|
||||
}
|
||||
async function addFavorites(app, html, data) {
|
||||
|
@ -465,11 +468,11 @@ async function addFavorites(app, html, data) {
|
|||
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>`);
|
||||
favBtn.click(ev => {
|
||||
app.actor.getOwnedItem(item._id).update({
|
||||
app.actor.items.get(item.data._id).update({
|
||||
"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.data._id}"]`).find('.item-controls').prepend(favBtn);
|
||||
}
|
||||
|
||||
if (isFav) {
|
||||
|
@ -543,12 +546,12 @@ async function addFavorites(app, html, data) {
|
|||
//favtabHtml.find('.item-toggle').click(event => app._onToggleItem(event));
|
||||
favtabHtml.find('.item-edit').click(ev => {
|
||||
let itemId = $(ev.target).parents('.item')[0].dataset.itemId;
|
||||
app.actor.getOwnedItem(itemId).sheet.render(true);
|
||||
app.actor.items.get(itemId).sheet.render(true);
|
||||
});
|
||||
favtabHtml.find('.item-fav').click(ev => {
|
||||
let itemId = $(ev.target).parents('.item')[0].dataset.itemId;
|
||||
let val = !app.actor.getOwnedItem(itemId).data.flags.favtab.isFavourite
|
||||
app.actor.getOwnedItem(itemId).update({
|
||||
let val = !app.actor.items.get(itemId).data.flags.favtab.isFavourite
|
||||
app.actor.items.get(itemId).update({
|
||||
"flags.favtab.isFavourite": val
|
||||
});
|
||||
});
|
||||
|
@ -564,10 +567,10 @@ async function addFavorites(app, html, data) {
|
|||
let list = null;
|
||||
if (dropData.data.type === 'feat') list = favFeats;
|
||||
else list = favItems;
|
||||
let dragSource = list.find(i => i._id === dropData.data._id);
|
||||
let siblings = list.filter(i => i._id !== dropData.data._id);
|
||||
let dragSource = list.find(i => i.data._id === dropData.data._id);
|
||||
let siblings = list.filter(i => i.data._id !== dropData.data._id);
|
||||
let targetId = ev.target.closest('.item').dataset.itemId;
|
||||
let dragTarget = siblings.find(s => s._id === targetId);
|
||||
let dragTarget = siblings.find(s => s.data._id === targetId);
|
||||
|
||||
if (dragTarget === undefined) return;
|
||||
const sortUpdates = SortingHelpers.performIntegerSort(dragSource, {
|
||||
|
@ -577,7 +580,7 @@ async function addFavorites(app, html, data) {
|
|||
});
|
||||
const updateData = sortUpdates.map(u => {
|
||||
const update = u.update;
|
||||
update._id = u.target._id;
|
||||
update._id = u.target.data._id;
|
||||
return update;
|
||||
});
|
||||
app.actor.updateEmbeddedEntity("OwnedItem", updateData);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import Actor5e from "../../entity.js";
|
||||
import ActorSheet5e from "./base.js";
|
||||
|
||||
/**
|
||||
|
@ -27,6 +28,11 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
static unsupportedItemTypes = new Set(["class"]);
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Organize Owned Items for rendering the NPC sheet
|
||||
* @private
|
||||
|
@ -43,7 +49,7 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
|||
|
||||
// Start by classifying items into groups for rendering
|
||||
let [forcepowers, techpowers, other] = data.items.reduce((arr, item) => {
|
||||
item.img = item.img || DEFAULT_TOKEN;
|
||||
item.img = item.img || CONST.DEFAULT_TOKEN;
|
||||
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
||||
item.hasUses = item.data.uses && (item.data.uses.max > 0);
|
||||
item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false);
|
||||
|
@ -80,17 +86,19 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
|||
data.techPowerbook = techPowerbook;
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
const data = super.getData();
|
||||
getData(options) {
|
||||
const data = super.getData(options);
|
||||
|
||||
// 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;
|
||||
|
||||
// Creature Type
|
||||
data.labels["type"] = this.actor.labels.creatureType;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -99,7 +107,7 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_updateObject(event, formData) {
|
||||
async _updateObject(event, formData) {
|
||||
|
||||
// Format NPC Challenge Rating
|
||||
const crs = {"1/8": 0.125, "1/4": 0.25, "1/2": 0.5};
|
||||
|
@ -109,7 +117,7 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
|||
if ( cr ) formData[crv] = cr < 1 ? cr : parseInt(cr);
|
||||
|
||||
// Parent ActorSheet update steps
|
||||
super._updateObject(event, formData);
|
||||
return super._updateObject(event, formData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
@ -44,7 +44,7 @@ export default class ActorSheet5eStarship extends ActorSheet5e {
|
|||
|
||||
// Start by classifying items into groups for rendering
|
||||
let [forcepowers, techpowers, other] = data.items.reduce((arr, item) => {
|
||||
item.img = item.img || DEFAULT_TOKEN;
|
||||
item.img = item.img || CONST.DEFAULT_TOKEN;
|
||||
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
||||
item.hasUses = item.data.uses && (item.data.uses.max > 0);
|
||||
item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false);
|
||||
|
@ -91,8 +91,8 @@ export default class ActorSheet5eStarship extends ActorSheet5e {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
const data = super.getData();
|
||||
getData(options) {
|
||||
const data = super.getData(options);
|
||||
|
||||
// Add Size info
|
||||
data.isTiny = data.actor.data.traits.size === "tiny";
|
||||
|
@ -114,7 +114,7 @@ export default class ActorSheet5eStarship extends ActorSheet5e {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_updateObject(event, formData) {
|
||||
async _updateObject(event, formData) {
|
||||
|
||||
// Format NPC Challenge Rating
|
||||
const crs = {"1/8": 0.125, "1/4": 0.25, "1/2": 0.5};
|
||||
|
@ -124,7 +124,7 @@ export default class ActorSheet5eStarship extends ActorSheet5e {
|
|||
if ( cr ) formData[crv] = cr < 1 ? cr : parseInt(cr);
|
||||
|
||||
// Parent ActorSheet update steps
|
||||
super._updateObject(event, formData);
|
||||
return super._updateObject(event, formData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -152,5 +152,4 @@ export default class ActorSheet5eStarship extends ActorSheet5e {
|
|||
AudioHelper.play({src: CONFIG.sounds.dice});
|
||||
this.actor.update({"data.attributes.hp.value": hp, "data.attributes.hp.max": hp});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,12 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
static unsupportedItemTypes = new Set(["class"]);
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new cargo entry for a vehicle Actor.
|
||||
*/
|
||||
|
@ -206,24 +212,39 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
}
|
||||
};
|
||||
|
||||
// Classify items owned by the vehicle and compute total cargo weight
|
||||
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') {
|
||||
|
||||
// Handle cargo explicitly
|
||||
const isCargo = item.flags.sw5e?.vehicleCargo === true;
|
||||
if ( isCargo ) {
|
||||
totalWeight += (item.data.weight || 0) * item.data.quantity;
|
||||
cargo.cargo.items.push(item);
|
||||
continue
|
||||
}
|
||||
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);
|
||||
|
||||
// Handle non-cargo item types
|
||||
switch ( item.type ) {
|
||||
case "weapon":
|
||||
features.weapons.items.push(item);
|
||||
break;
|
||||
case "equipment":
|
||||
features.equipment.items.push(item);
|
||||
break;
|
||||
case "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);
|
||||
break;
|
||||
default:
|
||||
totalWeight += (item.data.weight || 0) * item.data.quantity;
|
||||
cargo.cargo.items.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the rendering context data
|
||||
data.features = Object.values(features);
|
||||
data.cargo = Object.values(cargo);
|
||||
data.data.attributes.encumbrance = this._computeEncumbrance(totalWeight, data);
|
||||
|
@ -272,7 +293,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
const property = row.classList.contains('crew') ? 'crew' : 'passengers';
|
||||
|
||||
// Get the cargo entry
|
||||
const cargo = duplicate(this.actor.data.data.cargo[property]);
|
||||
const cargo = foundry.utils.deepClone(this.actor.data.data.cargo[property]);
|
||||
const entry = cargo[idx];
|
||||
if (!entry) return null;
|
||||
|
||||
|
@ -322,7 +343,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
const target = event.currentTarget;
|
||||
const type = target.dataset.type;
|
||||
if (type === 'crew' || type === 'passengers') {
|
||||
const cargo = duplicate(this.actor.data.data.cargo[type]);
|
||||
const cargo = foundry.utils.deepClone(this.actor.data.data.cargo[type]);
|
||||
cargo.push(this.constructor.newCargo);
|
||||
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
||||
}
|
||||
|
@ -343,13 +364,21 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
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);
|
||||
const cargo = foundry.utils.deepClone(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx);
|
||||
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
||||
}
|
||||
|
||||
return super._onItemDelete(event);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _onDropItemCreate(itemData) {
|
||||
const cargoTypes = ["weapon", "equipment", "consumable", "tool", "loot", "backpack"];
|
||||
const isCargo = cargoTypes.includes(itemData.type) && (this._tabs[0].active === "cargo");
|
||||
foundry.utils.setProperty(itemData, "flags.sw5e.vehicleCargo", isCargo);
|
||||
return super._onDropItemCreate(itemData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import Item5e from "../../../item/entity.js";
|
||||
import TraitSelector from "../../../apps/trait-selector.js";
|
||||
import ActorSheetFlags from "../../../apps/actor-flags.js";
|
||||
import ActorHitDiceConfig from "../../../apps/hit-dice-config.js";
|
||||
import ActorMovementConfig from "../../../apps/movement-config.js";
|
||||
import ActorSensesConfig from "../../../apps/senses-config.js";
|
||||
import ActorTypeConfig from "../../../apps/actor-type.js";
|
||||
import {SW5E} from '../../../config.js';
|
||||
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js";
|
||||
|
||||
|
@ -44,6 +46,15 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A set of item types that should be prevented from being dropped on this type of actor sheet.
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
static unsupportedItemTypes = new Set();
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/** @override */
|
||||
get template() {
|
||||
if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/oldActor/limited-sheet.html";
|
||||
|
@ -53,44 +64,51 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
getData(options) {
|
||||
|
||||
// Basic data
|
||||
let isOwner = this.entity.owner;
|
||||
let isOwner = this.actor.isOwner;
|
||||
const data = {
|
||||
owner: isOwner,
|
||||
limited: this.entity.limited,
|
||||
limited: this.actor.limited,
|
||||
options: this.options,
|
||||
editable: this.isEditable,
|
||||
cssClass: isOwner ? "editable" : "locked",
|
||||
isCharacter: this.entity.data.type === "character",
|
||||
isNPC: this.entity.data.type === "npc",
|
||||
isStarship: this.entity.data.type === "starship",
|
||||
isVehicle: this.entity.data.type === 'vehicle',
|
||||
isCharacter: this.actor.type === "character",
|
||||
isNPC: this.actor.type === "npc",
|
||||
isStarship: this.actor.type === "starship",
|
||||
isVehicle: this.actor.type === 'vehicle',
|
||||
config: CONFIG.SW5E,
|
||||
rollData: this.actor.getRollData.bind(this.actor)
|
||||
};
|
||||
|
||||
// The Actor and its Items
|
||||
data.actor = duplicate(this.actor.data);
|
||||
data.items = this.actor.items.map(i => {
|
||||
i.data.labels = i.labels;
|
||||
return i.data;
|
||||
});
|
||||
// The Actor's data
|
||||
const actorData = this.actor.data.toObject(false);
|
||||
data.actor = actorData;
|
||||
data.data = actorData.data;
|
||||
|
||||
// Owned Items
|
||||
data.items = actorData.items;
|
||||
for ( let i of data.items ) {
|
||||
const item = this.actor.items.get(i.data._id);
|
||||
i.labels = item.labels;
|
||||
}
|
||||
data.items.sort((a, b) => (a.sort || 0) - (b.sort || 0));
|
||||
data.data = data.actor.data;
|
||||
|
||||
// Labels and filters
|
||||
data.labels = this.actor.labels || {};
|
||||
data.filters = this._filters;
|
||||
|
||||
// Ability Scores
|
||||
for ( let [a, abl] of Object.entries(data.actor.data.abilities)) {
|
||||
for ( let [a, abl] of Object.entries(actorData.data.abilities)) {
|
||||
abl.icon = this._getProficiencyIcon(abl.proficient);
|
||||
abl.hover = CONFIG.SW5E.proficiencyLevels[abl.proficient];
|
||||
abl.label = CONFIG.SW5E.abilities[a];
|
||||
}
|
||||
|
||||
// Skills
|
||||
if (data.actor.data.skills) {
|
||||
for ( let [s, skl] of Object.entries(data.actor.data.skills)) {
|
||||
if (actorData.data.skills) {
|
||||
for ( let [s, skl] of Object.entries(actorData.data.skills)) {
|
||||
skl.ability = CONFIG.SW5E.abilityAbbreviations[skl.ability];
|
||||
skl.icon = this._getProficiencyIcon(skl.value);
|
||||
skl.hover = CONFIG.SW5E.proficiencyLevels[skl.value];
|
||||
|
@ -99,19 +117,19 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
}
|
||||
|
||||
// Movement speeds
|
||||
data.movement = this._getMovementSpeed(data.actor);
|
||||
data.movement = this._getMovementSpeed(actorData);
|
||||
|
||||
// Senses
|
||||
data.senses = this._getSenses(data.actor);
|
||||
data.senses = this._getSenses(actorData);
|
||||
|
||||
// Update traits
|
||||
this._prepareTraits(data.actor.data.traits);
|
||||
this._prepareTraits(actorData.data.traits);
|
||||
|
||||
// Prepare owned items
|
||||
this._prepareItems(data);
|
||||
|
||||
// Prepare active effects
|
||||
data.effects = prepareActiveEffectCategories(this.entity.effects);
|
||||
data.effects = prepareActiveEffectCategories(this.actor.effects);
|
||||
|
||||
// Return data to the sheet
|
||||
return data
|
||||
|
@ -222,7 +240,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
* @private
|
||||
*/
|
||||
_preparePowerbook(data, powers) {
|
||||
const owner = this.actor.owner;
|
||||
const owner = this.actor.isOwner;
|
||||
const levels = data.data.powers;
|
||||
const powerbook = {};
|
||||
|
||||
|
@ -275,11 +293,14 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
}
|
||||
|
||||
// Pact magic users have cantrips and a pact magic section
|
||||
// TODO: Check if this is needed, we've removed pacts everywhere else
|
||||
if ( levels.pact && levels.pact.max ) {
|
||||
if ( !powerbook["0"] ) registerSection("power0", 0, CONFIG.SW5E.powerLevels[0]);
|
||||
const l = levels.pact;
|
||||
const config = CONFIG.SW5E.powerPreparationModes.pact;
|
||||
registerSection("pact", sections.pact, config, {
|
||||
const level = game.i18n.localize(`SW5E.PowerLevel${levels.pact.level}`);
|
||||
const label = `${config} — ${level}`;
|
||||
registerSection("pact", sections.pact, label, {
|
||||
prepMode: "pact",
|
||||
value: l.value,
|
||||
max: l.max,
|
||||
|
@ -424,11 +445,11 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
html.find('.slot-max-override').click(this._onPowerSlotOverride.bind(this));
|
||||
|
||||
// Active Effect management
|
||||
html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.entity));
|
||||
html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.actor));
|
||||
}
|
||||
|
||||
// Owner Only Listeners
|
||||
if ( this.actor.owner ) {
|
||||
if ( this.actor.isOwner ) {
|
||||
|
||||
// Ability Checks
|
||||
html.find('.ability-name').click(this._onRollAbilityTest.bind(this));
|
||||
|
@ -495,17 +516,25 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onConfigMenu(event) {
|
||||
event.preventDefault();
|
||||
const button = event.currentTarget;
|
||||
let app;
|
||||
switch ( button.dataset.action ) {
|
||||
case "hit-dice":
|
||||
app = new ActorHitDiceConfig(this.object);
|
||||
break;
|
||||
case "movement":
|
||||
new ActorMovementConfig(this.object).render(true);
|
||||
app = new ActorMovementConfig(this.object);
|
||||
break;
|
||||
case "flags":
|
||||
new ActorSheetFlags(this.object).render(true);
|
||||
app = new ActorSheetFlags(this.object);
|
||||
break;
|
||||
case "senses":
|
||||
new ActorSensesConfig(this.object).render(true);
|
||||
app = new ActorSensesConfig(this.object);
|
||||
break;
|
||||
case "type":
|
||||
new ActorTypeConfig(this.object).render(true);
|
||||
break;
|
||||
}
|
||||
app?.render(true);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -539,7 +568,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/** @override */
|
||||
async _onDropActor(event, data) {
|
||||
const canPolymorph = game.user.isGM || (this.actor.owner && game.settings.get('sw5e', 'allowPolymorphing'));
|
||||
const canPolymorph = game.user.isGM || (this.actor.isOwner && game.settings.get('sw5e', 'allowPolymorphing'));
|
||||
if ( !canPolymorph ) return false;
|
||||
|
||||
// Get the target actor
|
||||
|
@ -614,15 +643,41 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
/** @override */
|
||||
async _onDropItemCreate(itemData) {
|
||||
|
||||
// Check to make sure items of this type are allowed on this actor
|
||||
if ( this.constructor.unsupportedItemTypes.has(itemData.type) ) {
|
||||
return ui.notifications.warn(game.i18n.format("SW5E.ActorWarningInvalidItem", {
|
||||
itemType: game.i18n.localize(CONFIG.Item.typeLabels[itemData.type]),
|
||||
actorType: game.i18n.localize(CONFIG.Actor.typeLabels[this.actor.type])
|
||||
}));
|
||||
}
|
||||
|
||||
// Create a Consumable power scroll on the Inventory tab
|
||||
// TODO: This is pretty non functional as the base items for the scrolls, and the powers, are not defined, maybe consider using holocrons
|
||||
if ( (itemData.type === "power") && (this._tabs[0].active === "inventory") ) {
|
||||
const scroll = await Item5e.createScrollFromPower(itemData);
|
||||
itemData = scroll.data;
|
||||
}
|
||||
|
||||
// Ignore certain statuses
|
||||
if ( itemData.data ) {
|
||||
["attunement", "equipped", "proficient", "prepared"].forEach(k => delete itemData.data[k]);
|
||||
// Ignore certain statuses
|
||||
["equipped", "proficient", "prepared"].forEach(k => delete itemData.data[k]);
|
||||
|
||||
// Downgrade ATTUNED to REQUIRED
|
||||
itemData.data.attunement = Math.min(itemData.data.attunement, CONFIG.SW5E.attunementTypes.REQUIRED);
|
||||
}
|
||||
|
||||
// Stack identical consumables
|
||||
if ( itemData.type === "consumable" && itemData.flags.core?.sourceId ) {
|
||||
const similarItem = this.actor.items.find(i => {
|
||||
const sourceId = i.getFlag("core", "sourceId");
|
||||
return sourceId && (sourceId === itemData.flags.core?.sourceId) &&
|
||||
(i.type === "consumable");
|
||||
});
|
||||
if ( similarItem ) {
|
||||
return similarItem.update({
|
||||
'data.quantity': similarItem.data.data.quantity + Math.max(itemData.data.quantity, 1)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create the owned item as normal
|
||||
|
@ -663,7 +718,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
async _onUsesChange(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const item = this.actor.items.get(itemId);
|
||||
const uses = Math.clamped(0, parseInt(event.target.value), item.data.data.uses.max);
|
||||
event.target.value = uses;
|
||||
return item.update({ 'data.uses.value': uses });
|
||||
|
@ -678,7 +733,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onItemRoll(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const item = this.actor.items.get(itemId);
|
||||
return item.roll();
|
||||
}
|
||||
|
||||
|
@ -692,7 +747,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onItemRecharge(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const item = this.actor.items.get(itemId);
|
||||
return item.rollRecharge();
|
||||
};
|
||||
|
||||
|
@ -705,8 +760,8 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onItemSummary(event) {
|
||||
event.preventDefault();
|
||||
let li = $(event.currentTarget).parents(".item"),
|
||||
item = this.actor.getOwnedItem(li.data("item-id")),
|
||||
chatData = item.getChatData({secrets: this.actor.owner});
|
||||
item = this.actor.items.get(li.data("item-id")),
|
||||
chatData = item.getChatData({secrets: this.actor.isOwner});
|
||||
|
||||
// Toggle summary
|
||||
if ( li.hasClass("expanded") ) {
|
||||
|
@ -737,10 +792,10 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
const itemData = {
|
||||
name: game.i18n.format("SW5E.ItemNew", {type: type.capitalize()}),
|
||||
type: type,
|
||||
data: duplicate(header.dataset)
|
||||
data: foundry.utils.deepClone(header.dataset)
|
||||
};
|
||||
delete itemData.data["type"];
|
||||
return this.actor.createEmbeddedEntity("OwnedItem", itemData);
|
||||
return this.actor.createEmbeddedDocuments("Item", [itemData]);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -753,8 +808,8 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onItemEdit(event) {
|
||||
event.preventDefault();
|
||||
const li = event.currentTarget.closest(".item");
|
||||
const item = this.actor.getOwnedItem(li.dataset.itemId);
|
||||
item.sheet.render(true);
|
||||
const item = this.actor.items.get(li.dataset.itemId);
|
||||
return item.sheet.render(true);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -766,8 +821,8 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
*/
|
||||
_onItemDelete(event) {
|
||||
event.preventDefault();
|
||||
const li = event.currentTarget.closest(".item");
|
||||
this.actor.deleteOwnedItem(li.dataset.itemId);
|
||||
const item = this.actor.items.get(li.dataset.itemId);
|
||||
if ( item ) return item.delete();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -780,7 +835,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onRollAbilityTest(event) {
|
||||
event.preventDefault();
|
||||
let ability = event.currentTarget.parentElement.dataset.ability;
|
||||
this.actor.rollAbility(ability, {event: event});
|
||||
return this.actor.rollAbility(ability, {event: event});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -793,7 +848,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onRollSkillCheck(event) {
|
||||
event.preventDefault();
|
||||
const skill = event.currentTarget.parentElement.dataset.skill;
|
||||
this.actor.rollSkill(skill, {event: event});
|
||||
return this.actor.rollSkill(skill, {event: event});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -806,7 +861,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onToggleAbilityProficiency(event) {
|
||||
event.preventDefault();
|
||||
const field = event.currentTarget.previousElementSibling;
|
||||
this.actor.update({[field.name]: 1 - parseInt(field.value)});
|
||||
return this.actor.update({[field.name]: 1 - parseInt(field.value)});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -823,7 +878,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
const filter = li.dataset.filter;
|
||||
if ( set.has(filter) ) set.delete(filter);
|
||||
else set.add(filter);
|
||||
this.render();
|
||||
return this.render();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -839,7 +894,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
const label = a.parentElement.querySelector("label");
|
||||
const choices = CONFIG.SW5E[a.dataset.options];
|
||||
const options = { name: a.dataset.target, title: label.innerText, choices };
|
||||
new TraitSelector(this.actor, options).render(true)
|
||||
return new TraitSelector(this.actor, options).render(true)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -847,15 +902,14 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
/** @override */
|
||||
_getHeaderButtons() {
|
||||
let buttons = super._getHeaderButtons();
|
||||
|
||||
// Add button to revert polymorph
|
||||
if ( !this.actor.isPolymorphed || this.actor.isToken ) return buttons;
|
||||
buttons.unshift({
|
||||
label: 'SW5E.PolymorphRestoreTransformation',
|
||||
class: "restore-transformation",
|
||||
icon: "fas fa-backward",
|
||||
onclick: ev => this.actor.revertOriginalForm()
|
||||
});
|
||||
if ( this.actor.isPolymorphed ) {
|
||||
buttons.unshift({
|
||||
label: 'SW5E.PolymorphRestoreTransformation',
|
||||
class: "restore-transformation",
|
||||
icon: "fas fa-backward",
|
||||
onclick: () => this.actor.revertOriginalForm()
|
||||
});
|
||||
}
|
||||
return buttons;
|
||||
}
|
||||
}
|
|
@ -76,7 +76,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
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.img = item.img || CONST.DEFAULT_TOKEN;
|
||||
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
||||
item.attunement = {
|
||||
[CONFIG.SW5E.attunementTypes.REQUIRED]: {
|
||||
|
@ -100,6 +100,9 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
// Item toggle state
|
||||
this._prepareItemToggleState(item);
|
||||
|
||||
// Primary Class
|
||||
if ( item.type === "class" ) item.isOriginalClass = ( item.data._id === this.actor.data.data.details.originalClass );
|
||||
|
||||
// Classify items into types
|
||||
if ( item.type === "power" ) arr[1].push(item);
|
||||
else if ( item.type === "feat" ) arr[2].push(item);
|
||||
|
@ -151,7 +154,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
if ( f.data.activation.type ) features.active.items.push(f);
|
||||
else features.passive.items.push(f);
|
||||
}
|
||||
classes.sort((a, b) => b.levels - a.levels);
|
||||
classes.sort((a, b) => b.data.levels - a.data.levels);
|
||||
features.classes.items = classes;
|
||||
features.classfeatures.items = classfeatures;
|
||||
features.archetype.items = archetypes;
|
||||
|
@ -243,7 +246,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
_onToggleItem(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const item = this.actor.items.get(itemId);
|
||||
const attr = item.data.type === "power" ? "data.preparation.prepared" : "data.equipped";
|
||||
return item.update({[attr]: !getProperty(item.data, attr)});
|
||||
}
|
||||
|
@ -293,6 +296,6 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
}
|
||||
|
||||
// Default drop handling if levels were not added
|
||||
super._onDropItemCreate(itemData);
|
||||
return super._onDropItemCreate(itemData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,11 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
static unsupportedItemTypes = new Set(["class"]);
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Organize Owned Items for rendering the NPC sheet
|
||||
* @private
|
||||
|
@ -34,7 +39,7 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
|
|||
|
||||
// Start by classifying items into groups for rendering
|
||||
let [powers, other] = data.items.reduce((arr, item) => {
|
||||
item.img = item.img || DEFAULT_TOKEN;
|
||||
item.img = item.img || CONST.DEFAULT_TOKEN;
|
||||
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
||||
item.hasUses = item.data.uses && (item.data.uses.max > 0);
|
||||
item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false);
|
||||
|
@ -70,14 +75,17 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
const data = super.getData();
|
||||
/** @inheritdoc */
|
||||
getData(options) {
|
||||
const data = super.getData(options);
|
||||
|
||||
// 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;
|
||||
|
||||
// Creature Type
|
||||
data.labels["type"] = this.actor.labels.creatureType;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -86,7 +94,7 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_updateObject(event, formData) {
|
||||
async _updateObject(event, formData) {
|
||||
|
||||
// Format NPC Challenge Rating
|
||||
const crs = {"1/8": 0.125, "1/4": 0.25, "1/2": 0.5};
|
||||
|
@ -96,7 +104,7 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
|
|||
if ( cr ) formData[crv] = cr < 1 ? cr : parseInt(cr);
|
||||
|
||||
// Parent ActorSheet update steps
|
||||
super._updateObject(event, formData);
|
||||
return super._updateObject(event, formData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
@ -20,6 +20,12 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
static unsupportedItemTypes = new Set(["class"]);
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new cargo entry for a vehicle Actor.
|
||||
*/
|
||||
|
@ -206,24 +212,39 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
}
|
||||
};
|
||||
|
||||
// Classify items owned by the vehicle and compute total cargo weight
|
||||
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') {
|
||||
|
||||
// Handle cargo explicitly
|
||||
const isCargo = item.flags.sw5e?.vehicleCargo === true;
|
||||
if ( isCargo ) {
|
||||
totalWeight += (item.data.weight || 0) * item.data.quantity;
|
||||
cargo.cargo.items.push(item);
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
|
||||
// Handle non-cargo item types
|
||||
switch ( item.type ) {
|
||||
case "weapon":
|
||||
features.weapons.items.push(item);
|
||||
break;
|
||||
case "equipment":
|
||||
features.equipment.items.push(item);
|
||||
break;
|
||||
case "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);
|
||||
break;
|
||||
default:
|
||||
totalWeight += (item.data.weight || 0) * item.data.quantity;
|
||||
cargo.cargo.items.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the rendering context data
|
||||
data.features = Object.values(features);
|
||||
data.cargo = Object.values(cargo);
|
||||
data.data.attributes.encumbrance = this._computeEncumbrance(totalWeight, data);
|
||||
|
@ -272,7 +293,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
const property = row.classList.contains('crew') ? 'crew' : 'passengers';
|
||||
|
||||
// Get the cargo entry
|
||||
const cargo = duplicate(this.actor.data.data.cargo[property]);
|
||||
const cargo = foundry.utils.deepClone(this.actor.data.data.cargo[property]);
|
||||
const entry = cargo[idx];
|
||||
if (!entry) return null;
|
||||
|
||||
|
@ -322,7 +343,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
const target = event.currentTarget;
|
||||
const type = target.dataset.type;
|
||||
if (type === 'crew' || type === 'passengers') {
|
||||
const cargo = duplicate(this.actor.data.data.cargo[type]);
|
||||
const cargo = foundry.utils.deepClone(this.actor.data.data.cargo[type]);
|
||||
cargo.push(this.constructor.newCargo);
|
||||
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
||||
}
|
||||
|
@ -343,7 +364,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
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);
|
||||
const cargo = foundry.utils.deepClone(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx);
|
||||
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
||||
}
|
||||
|
||||
|
@ -352,6 +373,16 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
async _onDropItemCreate(itemData) {
|
||||
const cargoTypes = ["weapon", "equipment", "consumable", "tool", "loot", "backpack"];
|
||||
const isCargo = cargoTypes.includes(itemData.type) && (this._tabs[0].active === "cargo");
|
||||
foundry.utils.setProperty(itemData, "flags.sw5e.vehicleCargo", isCargo);
|
||||
return super._onDropItemCreate(itemData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Special handling for editing HP to clamp it within appropriate range.
|
||||
* @param event {Event}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue