Merge branch 'Develop' into cbnathanael

This commit is contained in:
Nathanael Phillips 2020-11-17 08:50:06 -07:00
commit 2d8cc8cda2
58 changed files with 1414 additions and 1704 deletions

View file

@ -263,6 +263,11 @@
"SW5E.FeatureActionRecharge": "Action Recharge",
"SW5E.Flaws": "Flaws",
"SW5E.EffectCreate": "Create Effect",
"SW5E.EffectToggle": "Toggle Effect",
"SW5E.EffectEdit": "Edit Effect",
"SW5E.EffectDelete": "Delete Effect",
"SW5E.ItemTypeArchetype": "Archetype",
"SW5E.ItemTypeBackground": "Background",
"Sw5E.ItemTypeBackgroundPl": "Backgrounds",
@ -332,6 +337,12 @@
"SW5E.FlagsRemarkableAthleteHint": "Half-Proficiency (rounded-up) to physical Ability Checks and Initiative.",
"SW5E.FlagsCritThreshold": "Critical Hit Threshold",
"SW5E.FlagsCritThresholdHint": "Allow for expanded critical range; for example Improved or Superior Critical",
"SW5E.FlagsWeaponCritThreshold": "Weapon Critical Hit Threshold",
"SW5E.FlagsWeaponCritThresholdHint": "An expanded critical hit threshold for weapon attacks.",
"SW5E.FlagsPowerCritThreshold": "Power Critical Hit Threshold",
"SW5E.FlagsPowerCritThresholdHint": "An expanded critical hit threshold for power attacks.",
"SW5E.FlagsMeleeCriticalDice": "Melee Critical Damage Dice",
"SW5E.FlagsMeleeCriticalDiceHint": "A number of additional damage dice added to melee weapon critical hits.",
"SW5E.Flat": "Flat",
"SW5E.Formula": "Formula",
@ -582,6 +593,17 @@
"SW5E.RollMode": "Roll Mode",
"SW5E.RollSituationalBonus": "Situational Bonus?",
"SW5E.Save": "Save",
"SW5E.MovementConfig": "Configure Movement Speed",
"SW5E.MovementConfigHint": "Configure the movement speed and special movement attributes of this creature.",
"SW5E.MovementWalk": "Walk",
"SW5E.MovementBurrow": "Burrow",
"SW5E.MovementClimb": "Climb",
"SW5E.MovementHover": "Hover",
"SW5E.MovementFly": "Fly",
"SW5E.MovementSwim": "Swim",
"SW5E.MovementUnits": "Units",
"SW5E.SheetClassCharacter": "Default Character Sheet",
"SW5E.SheetClassCharacterOld": "Old Character Sheet",
"SW5E.SheetClassNPC": "Default NPC Sheet",
@ -837,6 +859,7 @@
"SW5E.available": "available",
"SW5E.description": "A comprehensive game system for running games of Star Wars 5th Edition in the Foundry VTT environment.",
"SW5E.of": "of",
"SW5E.per": "per",
"SW5E.power": "power",
"SETTINGS.5eAllowPolymorphingL": "Allow players to polymorph their own actors.",
"SETTINGS.5eAllowPolymorphingN": "Allow Polymorphing",

View file

@ -90,6 +90,7 @@
.russoOne(14px);
color: @colorOlive;
border-bottom: 1px solid @colorFaint;
white-space: nowrap;
}
/* ----------------------------------------- */
@ -142,6 +143,7 @@
font-family: "Signika", sans-serif;
font-size: 12px;
font-weight: 400;
white-space: nowrap;
}
}
}
@ -413,46 +415,18 @@
}
}
// Inventory item lists
.inventory-list {
list-style: none;
margin: 0;
padding: 0 5px;
overflow-y: auto;
scrollbar-width: thin;
color: @colorTan;
// Inventory Item
.item {
line-height: 30px;
padding: 0 2px; // to align with the header border
border-bottom: 1px solid @colorFaint;
&:last-child { border-bottom: none; }
// Item Header Name
.item-name {
cursor: pointer;
max-height: 30px;
overflow: hidden;
.item-image {
flex: 0 0 30px;
background-size: 30px;
margin-right: 5px;
}
h4 {
margin: 0;
white-space: nowrap;
overflow-x: hidden;
}
&.rollable:hover .item-image {
background-image: url("../../icons/svg/d20-grey.svg") !important;
}
&.rollable .item-image:hover {
background-image: url("../../icons/svg/d20-black.svg") !important;
}
i.attuned {
color: @colorTan;
}
@ -474,49 +448,26 @@
flex: 0 0 80px;
text-align: right;
font-size: 11px;
color: @colorTan;
white-space: nowrap;
}
}
// Inventory Header
.inventory-header {
margin: 2px 0;
padding: 0;
align-items: center;
background: rgba(0, 0, 0, 0.05);
border: @borderGroove;
font-weight: bold;
line-height: 24px;
h3 {
margin: 0 -5px 0 0;
padding-left: 5px;
.russoOne();
font-size: 16px;
}
.item-controls a.item-create {
flex: 0 0 100%;
}
}
// Item names
.item-name {
color: @colorDark;
}
// Item Detail Sections
.item-detail {
flex: 0 0 70px;
font-size: 12px;
color: @colorTan;
text-align: center;
border-right: 1px solid @colorFaint;
word-break: break-word;
white-space: nowrap;
overflow: hidden;
&:last-child { border-right: none; }
&.item-action {flex: 0 0 100px}
}
@ -527,24 +478,9 @@
border-right: 1px solid @colorFaint;
}
.item-list {
list-style: none;
margin: 0;
padding: 0;
}
// Item Control Buttons
.item-controls {
flex: 0 0 44px;
.flexrow();
justify-content: flex-end;
a {
flex: 0 0 22px;
font-size: 12px;
text-align: center;
color: @colorTan;
}
}
// Item Dropdown Summary
@ -553,6 +489,7 @@
font-size: 12px;
line-height: 16px;
padding: 0.25em 0.5em;
color: @colorDark;
border-top: 1px solid @colorFaint;
}
}
@ -693,44 +630,6 @@
// Empty powerbook controls
.powerbook-empty .item-controls { flex: 1; }
/* ----------------------------------------- */
/* Active Effects */
/* ----------------------------------------- */
.effects {
.effect-name{
flex: 2;
align-items: center;
color: @colorDark;
h4 { margin: 0; }
}
.effect-icon {
flex: 0 0 30px;
height: 30px;
margin-right: 5px;
border: none;
}
.effect-source,
.effect-duration {
text-align: center;
border-left: 1px solid @colorFaint;
border-right: 1px solid @colorFaint;
}
.effect-controls {
flex: 0 0 60px;
text-align: right;
}
.effect {
align-items: center;
border-bottom: 1px solid @colorFaint;
&:last-child { border-bottom: none; }
}
}
/* ----------------------------------------- */
/* TinyMCE */
/* ----------------------------------------- */
@ -739,3 +638,18 @@
padding: 0 8px;
}
}
#actor-flags {
.window-content {
overflow-y: hidden;
}
form {
height: 100%;
}
.form-body {
height: calc(100% - 40px);
padding-right: 8px;
margin-bottom: 4px;
overflow-y: auto;
}
}

View file

@ -362,6 +362,108 @@
padding: 0;
}
}
/* ----------------------------------------- */
/* Items Lists */
/* ----------------------------------------- */
.items-list {
list-style: none;
margin: 0;
padding: 0;
overflow-y: auto;
scrollbar-width: thin;
color: @colorTan;
// Child lists
.item-list {
list-style: none;
margin: 0;
padding: 0;
}
// Individual Item
.item {
align-items: center;
padding: 0 2px; // to align with the header border
border-bottom: 1px solid @colorFaint;
&:last-child { border-bottom: none; }
.item-name {
color: @colorDark;
.item-image {
flex: 0 0 30px;
height: 30px;
background-size: 30px;
border: none;
margin-right: 5px;
}
h4 {
margin: 0;
white-space: nowrap;
overflow-x: hidden;
}
}
}
// Section Header
.items-header {
height: 28px;
margin: 2px 0;
padding: 0;
align-items: center;
background: rgba(0, 0, 0, 0.05);
border: @borderGroove;
font-weight: bold;
> * {
font-size: 12px;
text-align: center;
}
.item-name {
padding-left: 5px;
.modesto();
font-size: 16px;
}
}
// Item Name
.item-name {
flex: 2;
margin: 0;
overflow: hidden;
font-size: 13px;
text-align: left;
align-items: center;
}
// Control Buttons
.item-controls {
flex: 0 0 60px;
justify-content: space-between;
a {
font-size: 12px;
text-align: center;
}
}
}
/* ----------------------------------------- */
/* Active Effects */
/* ----------------------------------------- */
.effects .item {
.effect-source,
.effect-duration,
.effect-controls {
text-align: center;
border-left: 1px solid @colorFaint;
border-right: 1px solid @colorFaint;
font-size: 12px;
}
.effect-controls {
border: none;
}
}
}

View file

@ -116,17 +116,17 @@
}
.form-group.uses-per {
.form-fields {
flex-wrap: nowrap;
}
input {
flex: 1;
flex: 0 0 32px;
}
span {
flex: 0 0 16px;
}
select {
flex: 3;
margin: 0 4px 0 0;
}
}
span.sep {
flex: 0 0 8px;
}

View file

@ -6,7 +6,7 @@ import AbilityTemplate from "../pixi/ability-template.js";
import {SW5E} from '../config.js';
/**
* Extend the base Actor class to implement additional logic specialized for SW5e.
* Extend the base Actor class to implement additional system-specific logic for SW5e.
*/
export default class Actor5e extends Actor {
@ -20,48 +20,6 @@ export default class Actor5e extends Actor {
/* -------------------------------------------- */
/**
* @override
* TODO: This becomes unnecessary after 0.7.x is released
*/
initialize() {
try {
this.prepareData();
} catch(err) {
console.error(`Failed to initialize data for ${this.constructor.name} ${this.id}:`);
console.error(err);
}
}
/* -------------------------------------------- */
/**
* @override
* TODO: This becomes unnecessary after 0.7.x is released
*/
prepareData() {
const is07x = !isNewerVersion("0.7.1", game.data.version);
if ( is07x ) this.data = duplicate(this._data);
if (!this.data.img) this.data.img = CONST.DEFAULT_TOKEN;
if ( !this.data.name ) this.data.name = "New " + this.entity;
this.prepareBaseData();
this.prepareEmbeddedEntities();
if ( is07x ) this.applyActiveEffects();
this.prepareDerivedData();
}
/* -------------------------------------------- */
/**
* @override
* TODO: This becomes unnecessary after 0.7.x is released
*/
applyActiveEffects() {
if (!isNewerVersion("0.7.1", game.data.version)) return super.applyActiveEffects();
}
/* -------------------------------------------- */
/** @override */
prepareBaseData() {
switch ( this.data.type ) {
@ -116,6 +74,11 @@ export default class Actor5e extends Actor {
abl.save = Math.max(abl.save, originalSaves[id].save);
}
}
// Inventory encumbrance
data.attributes.encumbrance = this._computeEncumbrance(actorData);
// Prepare skills
this._prepareSkills(actorData, bonuses, checkBonus, originalSkills);
// Determine Initiative Modifier
@ -126,7 +89,8 @@ export default class Actor5e extends Actor {
if ( joat ) init.prof = Math.floor(0.5 * data.attributes.prof);
else if ( athlete ) init.prof = Math.ceil(0.5 * data.attributes.prof);
else init.prof = 0;
init.bonus = Number(init.value + (flags.initiativeAlert ? 5 : 0));
init.value = init.value ?? 0;
init.bonus = init.value + (flags.initiativeAlert ? 5 : 0);
init.total = init.mod + init.prof + init.bonus;
// Prepare power-casting data
@ -169,7 +133,7 @@ export default class Actor5e extends Actor {
}
return obj;
}, {});
data.prof = this.data.data.attributes.prof;
data.prof = this.data.data.attributes.prof || 0;
return data;
}
@ -177,33 +141,36 @@ export default class Actor5e extends Actor {
/**
* Return the features which a character is awarded for each class level
* @param cls {Object} Data object for class, equivalent to Item5e.data or raw compendium entry
* @param {string} className The class name being added
* @param {string} subclassName The subclass of the class being added, if any
* @param {number} level The number of levels in the added class
* @param {number} priorLevel The previous level of the added class
* @return {Promise<Item5e[]>} Array of Item5e entities
*/
static async getClassFeatures(cls) {
const level = cls.data.levels;
const className = cls.name.toLowerCase();
static async getClassFeatures({className="", subclassName="", level=1, priorLevel=0}={}) {
className = className.toLowerCase();
subclassName = subclassName.slugify();
// Get the configuration of features which may be added
const clsConfig = CONFIG.SW5E.classFeatures[className];
if (!clsConfig) return [];
let featureIDs = clsConfig["features"][level] || [];
const subclassName = cls.data.subclass.toLowerCase().slugify();
// Identify subclass features
if ( subclassName !== "" ) {
const subclassConfig = clsConfig["subclasses"][subclassName];
if ( subclassConfig !== undefined ) {
const subclassFeatureIDs = subclassConfig["features"][level];
if ( subclassFeatureIDs ) {
featureIDs = featureIDs.concat(subclassFeatureIDs);
// Acquire class features
let ids = [];
for ( let [l, f] of Object.entries(clsConfig.features || {}) ) {
l = parseInt(l);
if ( (l <= level) && (l > priorLevel) ) ids = ids.concat(f);
}
}
else console.warn("Invalid subclass: " + subclassName);
// Acquire subclass features
const subConfig = clsConfig.subclasses[subclassName] || {};
for ( let [l, f] of Object.entries(subConfig.features || {}) ) {
l = parseInt(l);
if ( (l <= level) && (l > priorLevel) ) ids = ids.concat(f);
}
// Load item data for all identified features
const features = await Promise.all(featureIDs.map(id => fromUuid(id)));
const features = await Promise.all(ids.map(id => fromUuid(id)));
// Class powers should always be prepared
for ( const feature of features ) {
@ -237,34 +204,27 @@ export default class Actor5e extends Actor {
for (let u of updated instanceof Array ? updated : [updated]) {
const item = this.items.get(u._id);
if (!item || (item.data.type !== "class")) continue;
const classData = duplicate(item.data);
let changed = false;
const updateData = expandObject(u);
const config = {
className: updateData.name || item.data.name,
subclassName: updateData.data.subclass || item.data.data.subclass,
level: getProperty(updateData, "data.levels"),
priorLevel: item ? item.data.data.levels : 0
}
// Get and create features for an increased class level
const newLevels = getProperty(u, "data.levels");
if (newLevels && (newLevels > item.data.data.levels)) {
classData.data.levels = newLevels;
changed = true;
}
let changed = false;
if ( config.level && (config.level > config.priorLevel)) changed = true;
if ( config.subclassName !== item.data.data.subclass ) changed = true;
// Get features for a newly changed subclass
const newSubclass = getProperty(u, "data.subclass");
if (newSubclass && (newSubclass !== item.data.data.subclass)) {
classData.data.subclass = newSubclass;
changed = true;
}
// Get the new features
// Get features to create
if ( changed ) {
const features = await Actor5e.getClassFeatures(classData);
if ( features.length ) toCreate.push(...features);
}
}
// De-dupe created items with ones that already exist (by name)
if ( toCreate.length ) {
const existing = new Set(this.items.map(i => i.name));
toCreate = toCreate.filter(c => !existing.has(c.name));
const features = await Actor5e.getClassFeatures(config);
for ( let f of features ) {
if ( !existing.has(f.name) ) toCreate.push(f);
}
}
}
return toCreate
}
@ -301,9 +261,6 @@ export default class Actor5e extends Actor {
const required = xp.max - prior;
const pct = Math.round((xp.value - prior) * 100 / required);
xp.pct = Math.clamped(pct, 0, 100);
// Inventory encumbrance
data.attributes.encumbrance = this._computeEncumbrance(actorData);
}
/* -------------------------------------------- */
@ -369,17 +326,17 @@ export default class Actor5e extends Actor {
}
if ( joat && (skl.value === 0 ) ) multi = 0.5;
// Retain the maximum skill proficiency when skill proficiencies are merged
if ( originalSkills ) {
skl.value = Math.max(skl.value, originalSkills[id].value);
}
// Compute modifier
skl.bonus = checkBonus + skillBonus;
skl.mod = data.abilities[skl.ability].mod;
skl.prof = round(multi * data.attributes.prof);
skl.total = skl.mod + skl.prof + skl.bonus;
// If we merged skills when transforming, take the highest bonus here.
if (originalSkills && skl.value > 0.5) {
skl.total = Math.max(skl.total, originalSkills[id].total);
}
// Compute passive bonus
const passive = observant && (feats.observantFeat.skills.includes(id)) ? 5 : 0;
skl.passive = 10 + skl.total + passive;
@ -489,8 +446,8 @@ export default class Actor5e extends Actor {
else powers.pact.max = Math.max(1, Math.min(pl, 2), Math.min(pl - 8, 3), Math.min(pl - 13, 4));
powers.pact.value = Math.min(powers.pact.value, powers.pact.max);
} else {
powers.pact.level = 0;
powers.pact.max = 0;
powers.pact.max = parseInt(powers.pact.override) || 0
powers.pact.level = powers.pact.max > 0 ? 1 : 0;
}
}
@ -513,14 +470,14 @@ export default class Actor5e extends Actor {
if ( !physicalItems.includes(i.type) ) return weight;
const q = i.data.quantity || 0;
const w = i.data.weight || 0;
return weight + Math.round(q * w * 10) / 10;
return weight + (q * w);
}, 0);
// [Optional] add Currency Weight
if ( game.settings.get("sw5e", "currencyWeight") ) {
const currency = actorData.data.currency;
const numCoins = Object.values(currency).reduce((val, denom) => val += Math.max(denom, 0), 0);
weight += Math.round((numCoins * 10) / CONFIG.SW5E.encumbrance.currencyPerWeight) / 10;
weight += numCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
}
// Determine the encumbrance size class
@ -535,9 +492,10 @@ export default class Actor5e extends Actor {
if ( this.getFlag("sw5e", "powerfulBuild") ) mod = Math.min(mod * 2, 8);
// Compute Encumbrance percentage
weight = weight.toNearest(0.1);
const max = actorData.data.abilities.str.value * CONFIG.SW5E.encumbrance.strMultiplier * mod;
const pct = Math.clamped((weight* 100) / max, 0, 100);
return { value: weight, max, pct, encumbered: pct > (2/3) };
const pct = Math.clamped((weight * 100) / max, 0, 100);
return { value: weight.toNearest(0.1), max, pct, encumbered: pct > (2/3) };
}
/* -------------------------------------------- */
@ -564,9 +522,6 @@ export default class Actor5e extends Actor {
/** @override */
async update(data, options={}) {
// TODO: 0.7.1 compatibility - remove when stable
if ( !data.hasOwnProperty("data") ) data = expandObject(data);
// Apply changes in Actor size to Token width/height
const newSize = getProperty(data, "data.traits.size");
if ( newSize && (newSize !== getProperty(this.data, "data.traits.size")) ) {
@ -1429,6 +1384,7 @@ export default class Actor5e extends Actor {
if ( !original ) return;
// Get the Tokens which represent this actor
if ( canvas.ready ) {
const tokens = this.getActiveTokens(true);
const tokenUpdates = tokens.map(t => {
const tokenData = duplicate(original.data.token);
@ -1437,6 +1393,7 @@ export default class Actor5e extends Actor {
return tokenData;
});
canvas.scene.updateEmbeddedEntity("Token", tokenUpdates);
}
// Delete the polymorphed Actor and maybe re-render the original sheet
const isRendered = this.sheet.rendered;

View file

@ -1,10 +1,12 @@
import Item5e from "../../item/entity.js";
import TraitSelector from "../../apps/trait-selector.js";
import ActorSheetFlags from "../../apps/actor-flags.js";
import MovementConfig from "../../apps/movement-config.js";
import {SW5E} from '../../config.js';
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../effects.js";
/**
* Extend the basic ActorSheet class to do all the SW5e things!
* Extend the basic ActorSheet class to suppose SW5e-specific logic and functionality.
* This sheet is an Abstract layer which is not used.
* @extends {ActorSheet}
*/
@ -94,6 +96,9 @@ export default class ActorSheet5e extends ActorSheet {
}
}
// Movement speeds
data.movement = this._getMovementSpeed(data.actor);
// Update traits
this._prepareTraits(data.actor.data.traits);
@ -101,7 +106,7 @@ export default class ActorSheet5e extends ActorSheet {
this._prepareItems(data);
// Prepare active effects
this._prepareEffects(data);
data.effects = prepareActiveEffectCategories(this.entity.effects);
// Return data to the sheet
return data
@ -109,6 +114,28 @@ export default class ActorSheet5e extends ActorSheet {
/* -------------------------------------------- */
/**
* Prepare the display of movement speed data for the Actor
* @param {object} actorData
* @returns {{primary: string, special: string}}
* @private
*/
_getMovementSpeed(actorData) {
const movement = actorData.data.attributes.movement;
const speeds = [
[movement.burrow, `${game.i18n.localize("SW5E.MovementBurrow")} ${movement.burrow}`],
[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.swim, `${game.i18n.localize("SW5E.MovementSwim")} ${movement.swim}`]
].filter(s => !!s[0]).sort((a, b) => b[0] - a[0]);
return {
primary: `${movement.walk || 0} ${movement.units}`,
special: speeds.length ? speeds.map(s => s[1]).join(", ") : ""
}
}
/* -------------------------------------------- */
/**
* Prepare the data structure for traits data like languages, resistances & vulnerabilities, and proficiencies
* @param {object} traits The raw traits data object from the actor data
@ -147,43 +174,6 @@ export default class ActorSheet5e extends ActorSheet {
/* -------------------------------------------- */
/**
* Prepare the data structure for Active Effects which are currently applied to the Actor.
* @param {object} data The object of rendering data which is being prepared
* @private
*/
_prepareEffects(data) {
// Define effect header categories
const categories = {
temporary: {
label: "Temporary Effects",
effects: []
},
passive: {
label: "Passive Effects",
effects: []
},
inactive: {
label: "Inactive Effects",
effects: []
}
};
// Iterate over active effects, classifying them into categories
for ( let e of this.actor.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);
}
// Add the prepared categories of effects to the rendering data
return data.effects = categories;
}
/* -------------------------------------------- */
/**
* Insert a power into the powerbook object when rendering the character sheet
* @param {Object} data The Actor data being prepared
@ -363,7 +353,7 @@ export default class ActorSheet5e extends ActorSheet {
filterLists.on("click", ".filter-item", this._onToggleFilter.bind(this));
// Item summaries
html.find('.item .item-name h4').click(event => this._onItemSummary(event));
html.find('.item .item-name.rollable h4').click(event => this._onItemSummary(event));
// Editable Only Listeners
if ( this.isEditable ) {
@ -383,6 +373,7 @@ export default class ActorSheet5e extends ActorSheet {
html.find('.trait-selector').click(this._onTraitSelector.bind(this));
// Configure Special Flags
html.find('.configure-movement').click(this._onMovementConfig.bind(this));
html.find('.configure-flags').click(this._onConfigureFlags.bind(this));
// Owned Item management
@ -393,8 +384,7 @@ export default class ActorSheet5e extends ActorSheet {
html.find('.slot-max-override').click(this._onPowerSlotOverride.bind(this));
// Active Effect management
html.find(".effect-control").click(this._onManageActiveEffect.bind(this));
html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.entity));
}
// Owner Only Listeners
@ -576,9 +566,7 @@ export default class ActorSheet5e extends ActorSheet {
}
// Create the owned item as normal
// TODO remove conditional logic in 0.7.x
if (isNewerVersion(game.data.version, "0.6.9")) return super._onDropItemCreate(itemData);
else return this.actor.createEmbeddedEntity("OwnedItem", itemData);
return super._onDropItemCreate(itemData);
}
/* -------------------------------------------- */
@ -731,28 +719,6 @@ export default class ActorSheet5e extends ActorSheet {
/* -------------------------------------------- */
/**
* Manage Active Effect instances through the Actor Sheet via effect control buttons.
* @param {MouseEvent} event The left-click event on the effect control
* @private
*/
_onManageActiveEffect(event) {
event.preventDefault();
const a = event.currentTarget;
const li = a.closest(".effect");
const effect = this.actor.effects.get(li.dataset.effectId);
switch ( a.dataset.action ) {
case "edit":
return effect.sheet.render(true);
case "delete":
return effect.delete();
case "toggle":
return effect.update({disabled: !effect.data.disabled});
}
}
/* -------------------------------------------- */
/**
* Handle rolling an Ability check, either a test or a saving throw
* @param {Event} event The originating click event
@ -825,6 +791,18 @@ export default class ActorSheet5e extends ActorSheet {
/* -------------------------------------------- */
/**
* Handle spawning the TraitSelector application which allows a checkbox of multiple trait options
* @param {Event} event The click event which originated the selection
* @private
*/
_onMovementConfig(event) {
event.preventDefault();
new MovementConfig(this.object).render(true);
}
/* -------------------------------------------- */
/** @override */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
@ -839,90 +817,4 @@ export default class ActorSheet5e extends ActorSheet {
});
return buttons;
}
/* -------------------------------------------- */
/* DEPRECATED */
/* -------------------------------------------- */
/**
* TODO: Remove once 0.7.x is release
* @deprecated since 0.7.0
*/
async _onDrop (event) {
event.preventDefault();
// Get dropped data
let data;
try {
data = JSON.parse(event.dataTransfer.getData('text/plain'));
} catch (err) {
return false;
}
if ( !data ) return false;
// Handle the drop with a Hooked function
const allowed = Hooks.call("dropActorSheetData", this.actor, this, data);
if ( allowed === false ) return;
// Case 1 - Dropped Item
if ( data.type === "Item" ) {
return this._onDropItem(event, data);
}
// Case 2 - Dropped Actor
if ( data.type === "Actor" ) {
return this._onDropActor(event, data);
}
}
/* -------------------------------------------- */
/**
* TODO: Remove once 0.7.x is release
* @deprecated since 0.7.0
*/
async _onDropItem(event, data) {
if ( !this.actor.owner ) return false;
let itemData = await this._getItemDropData(event, data);
// Handle item sorting within the same Actor
const actor = this.actor;
let sameActor = (data.actorId === actor._id) || (actor.isToken && (data.tokenId === actor.token.id));
if (sameActor) return this._onSortItem(event, itemData);
// Create a new item
this._onDropItemCreate(itemData);
}
/* -------------------------------------------- */
/**
* TODO: Remove once 0.7.x is release
* @deprecated since 0.7.0
*/
async _getItemDropData(event, data) {
let itemData = null;
// Case 1 - Import from a Compendium pack
if (data.pack) {
const pack = game.packs.get(data.pack);
if (pack.metadata.entity !== "Item") return;
itemData = await pack.getEntry(data.id);
}
// Case 2 - Data explicitly provided
else if (data.data) {
itemData = data.data;
}
// Case 3 - Import from World entity
else {
let item = game.items.get(data.id);
if (!item) return;
itemData = item.data;
}
// Return a copy of the extracted data
return duplicate(itemData);
}
}

View file

@ -109,7 +109,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
for ( let i of items ) {
i.data.quantity = i.data.quantity || 0;
i.data.weight = i.data.weight || 0;
i.totalWeight = Math.round(i.data.quantity * i.data.weight * 10) / 10;
i.totalWeight = (i.data.quantity * i.data.weight).toNearest(0.1);
inventory[i.type].items.push(i);
}
@ -189,9 +189,6 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
super.activateListeners(html);
if ( !this.options.editable ) return;
// Inventory Functions
html.find(".currency-convert").click(this._onConvertCurrency.bind(this));
// Item State Toggling
html.find('.item-toggle').click(this._onToggleItem.bind(this));
@ -199,8 +196,8 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
html.find('.short-rest').click(this._onShortRest.bind(this));
html.find('.long-rest').click(this._onLongRest.bind(this));
// Death saving throws
html.find('.death-save').click(this._onDeathSave.bind(this));
// Rollable sheet actions
html.find(".rollable[data-action]").click(this._onSheetAction.bind(this));
}
/* -------------------------------------------- */
@ -210,14 +207,19 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
* @param {MouseEvent} event The originating click event
* @private
*/
_onDeathSave(event) {
_onSheetAction(event) {
event.preventDefault();
const button = event.currentTarget;
switch( button.dataset.action ) {
case "rollDeathSave":
return this.actor.rollDeathSave({event: event});
case "rollInitiative":
return this.actor.rollInitiative({createCombatants: true});
}
}
/* -------------------------------------------- */
/**
* Handle toggling the state of an Owned Item within the Actor
* @param {Event} event The triggering click event
@ -259,53 +261,39 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
/* -------------------------------------------- */
/**
* Handle mouse click events to convert currency to the highest possible denomination
* @param {MouseEvent} event The originating click event
* @private
*/
async _onConvertCurrency(event) {
event.preventDefault();
return Dialog.confirm({
title: `${game.i18n.localize("SW5E.CurrencyConvert")}`,
content: `<p>${game.i18n.localize("SW5E.CurrencyConvertHint")}</p>`,
yes: () => this.actor.convertCurrency()
});
}
/* -------------------------------------------- */
/** @override */
async _onDropItemCreate(itemData) {
let addLevel = false;
// Upgrade the number of class levels a character has and add features
if ( itemData.type === "class" ) {
const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name);
const classWasAlreadyPresent = !!cls;
let priorLevel = cls?.data.data.levels ?? 0;
const hasClass = !!cls;
// Add new features for class level
if ( !classWasAlreadyPresent ) {
Actor5e.getClassFeatures(itemData).then(features => {
this.actor.createEmbeddedEntity("OwnedItem", features);
// Increment levels instead of creating a new item
if ( hasClass ) {
const next = Math.min(priorLevel + 1, 20 + priorLevel - this.actor.data.data.details.level);
if ( next > priorLevel ) {
itemData.levels = next;
await cls.update({"data.levels": next});
addLevel = true;
}
}
// Add class features
if ( !hasClass || addLevel ) {
const features = await Actor5e.getClassFeatures({
className: itemData.name,
subclassName: itemData.data.subclass,
level: itemData.levels,
priorLevel: priorLevel
});
}
// If the actor already has the class, increment the level instead of creating a new item
// then add new features as long as level increases
if ( classWasAlreadyPresent ) {
const lvl = cls.data.data.levels;
const newLvl = Math.min(lvl + 1, 20 + lvl - this.actor.data.data.details.level);
if ( !(lvl === newLvl) ) {
cls.update({"data.levels": newLvl});
itemData.data.levels = newLvl;
Actor5e.getClassFeatures(itemData).then(features => {
this.actor.createEmbeddedEntity("OwnedItem", features);
});
}
return
await this.actor.createEmbeddedEntity("OwnedItem", features);
}
}
super._onDropItemCreate(itemData);
// Default drop handling if levels were not added
if ( !addLevel ) super._onDropItemCreate(itemData);
}
}

View file

@ -106,7 +106,7 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
/** @override */
activateListeners(html) {
super.activateListeners(html);
html.find(".health .rollable").click(this._onRollHealthFormula.bind(this));
html.find(".health .rollable").click(this._onRollHPFormula.bind(this));
}
/* -------------------------------------------- */
@ -116,7 +116,7 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
* @param {Event} event The original click event
* @private
*/
_onRollHealthFormula(event) {
_onRollHPFormula(event) {
event.preventDefault();
const formula = this.actor.data.data.attributes.hp.formula;
if ( !formula ) return;

View file

@ -49,12 +49,9 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
totalWeight /= CONFIG.SW5E.encumbrance.vehicleWeightMultiplier;
// Compute overall encumbrance
const enc = {
max: actorData.data.attributes.capacity.cargo,
value: Math.round(totalWeight * 10) / 10
};
enc.pct = Math.min(enc.value * 100 / enc.max, 99);
return enc;
const max = actorData.data.attributes.capacity.cargo;
const pct = Math.clamped((totalWeight * 100) / max, 0, 100);
return {value: totalWeight.toNearest(0.1), max, pct};
}
/* -------------------------------------------- */
@ -89,6 +86,13 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
/* -------------------------------------------- */
/** @override */
_getMovementSpeed(actorData) {
return {primary: "", special: ""};
}
/* -------------------------------------------- */
/**
* Organize Owned Items for rendering the Vehicle sheet.
* @private

View file

@ -1,6 +1,6 @@
/**
* An application class which provides advanced configuration for special character flags which modify an Actor
* @extends {BaseEntitySheet}
* @implements {BaseEntitySheet}
*/
export default class ActorSheetFlags extends BaseEntitySheet {
static get defaultOptions() {
@ -16,22 +16,16 @@ export default class ActorSheetFlags extends BaseEntitySheet {
/* -------------------------------------------- */
/**
* Configure the title of the special traits selection window to include the Actor name
* @type {String}
*/
/** @override */
get title() {
return `${game.i18n.localize('SW5E.FlagsTitle')}: ${this.object.name}`;
}
/* -------------------------------------------- */
/**
* Prepare data used to render the special Actor traits selection UI
* @return {Object}
*/
/** @override */
getData() {
const data = super.getData();
const data = {};
data.actor = this.object;
data.flags = this._getFlags();
data.bonuses = this._getBonuses();
@ -43,17 +37,18 @@ export default class ActorSheetFlags extends BaseEntitySheet {
/**
* Prepare an object of flags data which groups flags by section
* Add some additional data for rendering
* @return {Object}
* @return {object}
*/
_getFlags() {
const flags = {};
const baseData = this.entity._data;
for ( let [k, v] of Object.entries(CONFIG.SW5E.characterFlags) ) {
if ( !flags.hasOwnProperty(v.section) ) flags[v.section] = {};
let flag = duplicate(v);
flag.type = v.type.name;
flag.isCheckbox = v.type === Boolean;
flag.isSelect = v.hasOwnProperty('choices');
flag.value = this.entity.getFlag("sw5e", k);
flag.value = getProperty(baseData.flags, `sw5e.${k}`);
flags[v.section][`flags.sw5e.${k}`] = flag;
}
return flags;
@ -63,7 +58,7 @@ export default class ActorSheetFlags extends BaseEntitySheet {
/**
* Get the bonuses fields and their localization strings
* @return {Array}
* @return {Array<object>}
* @private
*/
_getBonuses() {
@ -82,17 +77,14 @@ export default class ActorSheetFlags extends BaseEntitySheet {
{name: "data.bonuses.power.dc", label: "SW5E.BonusPowerDC"}
];
for ( let b of bonuses ) {
b.value = getProperty(this.object.data, b.name) || "";
b.value = getProperty(this.object._data, b.name) || "";
}
return bonuses;
}
/* -------------------------------------------- */
/**
* Update the Actor using the configured flags
* Remove/unset any flags which are no longer configured
*/
/** @override */
async _updateObject(event, formData) {
const actor = this.object;
let updateData = expandObject(formData);
@ -100,10 +92,12 @@ export default class ActorSheetFlags extends BaseEntitySheet {
// Unset any flags which are "false"
let unset = false;
const flags = updateData.flags.sw5e;
//clone flags to dnd5e for module compatability
updateData.flags.dnd5e = updateData.flags.sw5e
for ( let [k, v] of Object.entries(flags) ) {
if ( [undefined, null, "", false, 0].includes(v) ) {
delete flags[k];
if ( hasProperty(actor.data.flags, `sw5e.${k}`) ) {
if ( hasProperty(actor._data.flags, `sw5e.${k}`) ) {
unset = true;
flags[`-=${k}`] = null;
}
@ -118,10 +112,6 @@ export default class ActorSheetFlags extends BaseEntitySheet {
}
// Diff the data against any applied overrides and apply
// TODO: Remove this logical gate once 0.7.x is release channel
if ( !isNewerVersion("0.7.1", game.data.version) ){
updateData = diffObject(this.object.data, updateData);
}
await actor.update(updateData, {diff: false});
}
}

View file

@ -1,102 +0,0 @@
/**
* A specialized Dialog subclass for casting a cast item at a certain level
* @type {Dialog}
*/
export class CastDialog extends Dialog {
constructor(actor, item, dialogData={}, options={}) {
super(dialogData, options);
this.options.classes = ["sw5e", "dialog"];
/**
* Store a reference to the Actor entity which is casting the cast
* @type {Actor5e}
*/
this.actor = actor;
/**
* Store a reference to the Item entity which is the cast being cast
* @type {Item5e}
*/
this.item = item;
}
/* -------------------------------------------- */
/* Rendering */
/* -------------------------------------------- */
/**
* A constructor function which displays the Cast Cast Dialog app for a given Actor and Item.
* Returns a Promise which resolves to the dialog FormData once the workflow has been completed.
* @param {Actor5e} actor
* @param {Item5e} item
* @return {Promise}
*/
static async create(actor, item) {
const ad = actor.data.data;
const id = item.data.data;
// Determine whether the cast may be upcast
const lvl = id.level;
const canUpcast = (lvl > 0) && CONFIG.SW5E.castUpcastModes.includes(id.preparation.mode);
// Determine the levels which are feasible
let lmax = 0;
const castLevels = Array.fromRange(10).reduce((arr, i) => {
if ( i < lvl ) return arr;
const l = ad.casts["cast"+i] || {max: 0, override: null};
let max = parseInt(l.override || l.max || 0);
let slots = Math.clamped(parseInt(l.value || 0), 0, max);
if ( max > 0 ) lmax = i;
arr.push({
level: i,
label: i > 0 ? `${CONFIG.SW5E.castLevels[i]} (${slots} Slots)` : CONFIG.SW5E.castLevels[i],
canCast: canUpcast && (max > 0),
hasSlots: slots > 0
});
return arr;
}, []).filter(sl => sl.level <= lmax);
const pact = ad.casts.pact;
if (pact.level >= lvl) {
// If this character has pact slots, present them as an option for
// casting the cast.
castLevels.push({
level: 'pact',
label: game.i18n.localize('SW5E.CastLevelPact')
+ ` (${game.i18n.localize('SW5E.Level')} ${pact.level}) `
+ `(${pact.value} ${game.i18n.localize('SW5E.Slots')})`,
canCast: canUpcast,
hasSlots: pact.value > 0
});
}
const canCast = castLevels.some(l => l.hasSlots);
// Render the Cast casting template
const html = await renderTemplate("systems/sw5e/templates/apps/cast-cast.html", {
item: item.data,
canCast: canCast,
canUpcast: canUpcast,
castLevels,
hasPlaceableTemplate: game.user.can("TEMPLATE_CREATE") && item.hasAreaTarget
});
// Create the Dialog and return as a Promise
return new Promise((resolve, reject) => {
const dlg = new this(actor, item, {
title: `${item.name}: Cast Configuration`,
content: html,
buttons: {
cast: {
icon: '<i class="fas fa-magic"></i>',
label: "Cast",
callback: html => resolve(new FormData(html[0].querySelector("#cast-config-form")))
}
},
default: "cast",
close: reject
});
dlg.render(true);
});
}
}

View file

@ -0,0 +1,32 @@
/**
* A simple form to set actor movement speeds
* @implements {BaseEntitySheet}
*/
export default class MovementConfig extends BaseEntitySheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
title: "SW5E.MovementConfig",
classes: ["sw5e"],
template: "systems/sw5e/templates/apps/movement-config.html",
width: 240,
height: "auto"
});
}
/* -------------------------------------------- */
/** @override */
getData(options) {
const data = {
movement: duplicate(this.entity._data.data.attributes.movement),
units: CONFIG.SW5E.movementUnits
}
for ( let [k, v] of Object.entries(data.movement) ) {
if ( ["units", "hover"].includes(k) ) continue;
data.movement[k] = Number.isNumeric(v) ? v.toNearest(0.1) : 0;
}
return data;
}
}

View file

@ -11,14 +11,16 @@ export const highlightCriticalSuccessFailure = function(message, html, data) {
const d = roll.dice[0];
// Ensure it is an un-modified d20 roll
const isD20 = (d.faces === 20) && ( d.results.length === 1 );
const isD20 = (d.faces === 20) && ( d.values.length === 1 );
if ( !isD20 ) return;
const isModifiedRoll = ("success" in d.results[0]) || d.options.marginSuccess || d.options.marginFailure;
if ( isModifiedRoll ) return;
// Highlight successes and failures
if ( d.options.critical && (d.total >= d.options.critical) ) html.find(".dice-total").addClass("critical");
else if ( d.options.fumble && (d.total <= d.options.fumble) ) html.find(".dice-total").addClass("fumble");
const critical = d.options.critical || 20;
const fumble = d.options.fumble || 1;
if ( d.total >= critical ) html.find(".dice-total").addClass("critical");
else if ( d.total <= fumble ) html.find(".dice-total").addClass("fumble");
else if ( d.options.target ) {
if ( roll.total >= d.options.target ) html.find(".dice-total").addClass("success");
else html.find(".dice-total").addClass("failure");
@ -33,7 +35,8 @@ export const highlightCriticalSuccessFailure = function(message, html, data) {
export const displayChatActionButtons = function(message, html, data) {
const chatCard = html.find(".sw5e.chat-card");
if ( chatCard.length > 0 ) {
html.find(".flavor-text").remove();
const flavor = html.find(".flavor-text");
if ( flavor.text() === html.find(".item-name").text() ) flavor.remove();
// If the user is the message author or the actor owner, proceed
let actor = game.actors.get(data.message.speaker.actor);

View file

@ -27,41 +27,14 @@ export const _getInitiativeFormula = function(combatant) {
return parts.filter(p => p !== null).join(" + ");
};
/* -------------------------------------------- */
/**
* TODO: A temporary shim until 0.7.x becomes stable
* @override
* When the Combat encounter updates - re-render open Actor sheets for combatants in the encounter.
*/
TokenConfig.getTrackedAttributes = function(data, _path=[]) {
// Track the path and record found attributes
const attributes = {
"bar": [],
"value": []
};
// Recursively explore the object
for ( let [k, v] of Object.entries(data) ) {
let p = _path.concat([k]);
// Check objects for both a "value" and a "max"
if ( v instanceof Object ) {
const isBar = ("value" in v) && ("max" in v);
if ( isBar ) attributes.bar.push(p);
else {
const inner = this.getTrackedAttributes(data[k], p);
attributes.bar.push(...inner.bar);
attributes.value.push(...inner.value);
Hooks.on("updateCombat", (combat, data, options, userId) => {
const updateTurn = ("turn" in data) || ("round" in data);
if ( !updateTurn ) return;
for ( let t of combat.turns ) {
const a = t.actor;
if ( t.actor ) t.actor.sheet.render(false);
}
}
// Otherwise identify values which are numeric or null
else if ( Number.isNumeric(v) || (v === null) ) {
attributes.value.push(p);
}
}
return attributes;
};
});

View file

@ -325,17 +325,31 @@ SW5E.armorPropertiesTypes = {
"Versatile": "SW5E.ArmorProperVersatile"
};
/* -------------------------------------------- */
/**
* The valid units of measure for movement distances in the game system.
* By default this uses the imperial units of feet and miles.
* @type {Object<string,string>}
*/
SW5E.movementUnits = {
"ft": "SW5E.DistFt",
"mi": "SW5E.DistMi"
}
/**
* The valid units of measure for the range of an action or effect.
* This object automatically includes the movement units from SW5E.movementUnits
* @type {Object<string,string>}
*/
SW5E.distanceUnits = {
"none": "SW5E.None",
"self": "SW5E.DistSelf",
"touch": "SW5E.DistTouch",
"ft": "SW5E.DistFt",
"mi": "SW5E.DistMi",
"spec": "SW5E.Special",
"any": "SW5E.DistAny"
};
for ( let [k, v] of Object.entries(SW5E.movementUnits) ) {
SW5E.distanceUnits[k] = v;
}
/* -------------------------------------------- */
@ -413,7 +427,7 @@ SW5E.healingTypes = {
* Enumerate the denominations of hit dice which can apply to classes in the SW5E system
* @type {Array.<string>}
*/
SW5E.hitDieTypes = ["d4", "d6", "d8", "d10", "d12"];
SW5E.hitDieTypes = ["d4", "d6", "d8", "d10", "d12", "d20"];
/* -------------------------------------------- */
@ -461,15 +475,14 @@ SW5E.skills = {
/* -------------------------------------------- */
SW5E.powerPreparationModes = {
"prepared": "SW5E.PowerPrepPrepared",
"always": "SW5E.PowerPrepAlways",
"atwill": "SW5E.PowerPrepAtWill",
"innate": "SW5E.PowerPrepInnate",
"prepared": "SW5E.PowerPrepPrepared"
"innate": "SW5E.PowerPrepInnate"
};
SW5E.powerUpcastModes = ["always", "pact", "prepared"];
SW5E.powerProgression = {
"none": "SW5E.PowerNone",
"full": "SW5E.PowerProgFull",
@ -901,15 +914,27 @@ SW5E.characterFlags = {
type: Boolean
},
"weaponCriticalThreshold": {
name: "SW5E.FlagsCritThreshold",
hint: "SW5E.FlagsCritThresholdHint",
name: "SW5E.FlagsWeaponCritThreshold",
hint: "SW5E.FlagsWeaponCritThresholdHint",
section: "Feats",
type: Number,
placeholder: 20
},
"powerCriticalThreshold": {
name: "SW5E.FlagsPowerCritThreshold",
hint: "SW5E.FlagsPowerCritThresholdHint",
section: "Feats",
type: Number,
placeholder: 20
},
"meleeCriticalDamageDice": {
name: "SW5E.FlagsMeleeCriticalDice",
hint: "SW5E.FlagsMeleeCriticalDiceHint",
section: "Feats",
type: Number,
placeholder: 0
}
};
// Configure allowed status flags
SW5E.allowedActorFlags = [
"isPolymorphed", "originalActor"
].concat(Object.keys(SW5E.characterFlags));
SW5E.allowedActorFlags = ["isPolymorphed", "originalActor"].concat(Object.keys(SW5E.characterFlags));

View file

@ -1,4 +1,4 @@
/**
/**
* A standardized helper function for managing core 5e "d20 rolls"
*
* Holding SHIFT, ALT, or CTRL when the attack is rolled will "fast-forward".
@ -28,7 +28,7 @@
*
* @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({parts=[], data={}, event={}, 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,
@ -173,12 +173,12 @@ async function _d20RollDialog({template, title, parts, data, rollMode, dialogOpt
close: () => resolve(null)
}, dialogOptions).render(true);
});
}
}
/* -------------------------------------------- */
/* -------------------------------------------- */
/**
/**
* A standardized helper function for managing core 5e "d20 rolls"
*
* Holding SHIFT, ALT, or CTRL when the attack is rolled will "fast-forward".
@ -195,6 +195,8 @@ async function _d20RollDialog({template, title, parts, data, rollMode, dialogOpt
* @param {string} flavor Flavor text to use in the posted chat message
* @param {boolean} allowCritical Allow the opportunity for a critical hit to be rolled
* @param {Boolean} critical Flag this roll as a critical hit for the purposes of fast-forward rolls
* @param {number} criticalBonusDice A number of bonus damage dice that are added for critical hits
* @param {number} criticalMultiplier A critical hit multiplier which is applied to critical hits
* @param {Boolean} fastForward Allow fast-forward advantage selection
* @param {Function} onClose Callback for actions to take when the dialog form is closed
* @param {Object} dialogOptions Modal dialog options
@ -203,8 +205,9 @@ async function _d20RollDialog({template, title, parts, data, rollMode, dialogOpt
*
* @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,
allowCritical=true, critical=false, fastForward=null, dialogOptions, chatMessage=true, messageData={}}={}) {
export async function damageRoll({parts, 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
messageData.flavor = flavor || title;
@ -228,11 +231,11 @@ async function _d20RollDialog({template, title, parts, data, rollMode, dialogOpt
// Modify the damage formula for critical hits
if ( crit === true ) {
let add = (actor && actor.getFlag("sw5e", "savageAttacks")) ? 1 : 0;
let mult = 2;
// TODO Backwards compatibility - REMOVE LATER
if (isNewerVersion(game.data.version, "0.6.9")) roll.alter(mult, add);
else roll.alter(add, mult);
roll.alter(criticalMultiplier, 0); // Multiply all dice
if ( roll.terms[0] instanceof Die ) { // Add bonus dice for only the main dice term
roll.terms[0].alter(1, criticalBonusDice);
roll._formula = roll.formula;
}
messageData.flavor += ` (${game.i18n.localize("SW5E.Critical")})`;
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].critical = true;
}

63
module/effects.js vendored Normal file
View file

@ -0,0 +1,63 @@
/**
* Manage Active Effect instances through the Actor Sheet via effect control buttons.
* @param {MouseEvent} event The left-click event on the effect control
* @param {Actor|Item} owner The owning entity which manages this effect
*/
export function onManageActiveEffect(event, owner) {
event.preventDefault();
const a = event.currentTarget;
const li = a.closest("li");
const effect = li.dataset.effectId ? owner.effects.get(li.dataset.effectId) : null;
switch ( a.dataset.action ) {
case "create":
return ActiveEffect.create({
label: "New Effect",
icon: "icons/svg/aura.svg",
origin: owner.uuid,
"duration.rounds": li.dataset.effectType === "temporary" ? 1 : undefined,
disabled: li.dataset.effectType === "inactive"
}, owner).create();
case "edit":
return effect.sheet.render(true);
case "delete":
return effect.delete();
case "toggle":
return effect.update({disabled: !effect.data.disabled});
}
}
/**
* Prepare the data structure for Active Effects which are currently applied to an Actor or Item.
* @param {ActiveEffect[]} effects The array of Active Effect instances to prepare sheet data for
* @return {object} Data for rendering
*/
export function prepareActiveEffectCategories(effects) {
// Define effect header categories
const categories = {
temporary: {
type: "temporary",
label: "Temporary Effects",
effects: []
},
passive: {
type: "passive",
label: "Passive Effects",
effects: []
},
inactive: {
type: "inactive",
label: "Inactive 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;
}

View file

@ -161,6 +161,7 @@ export default class Item5e extends Item {
// Power Level, School, and Components
if ( itemData.type === "power" ) {
data.preparation.mode = data.preparation.mode || "prepared";
labels.level = C.powerLevels[data.level];
labels.school = C.powerSchools[data.school];
labels.components = Object.entries(data.components).reduce((arr, c) => {
@ -190,23 +191,23 @@ export default class Item5e extends Item {
}
// Background Items
else if ( itemData.type === "background" ) {
// labels.background = C.background[data.background];
}
// Class Feature Items
else if ( itemData.type === "classfeature" ) {
// labels.classFeature = C.classFeature[data.classFeature];
}
// Fighting Style Items
else if ( itemData.type === "fightingstyle" ) {
// labels.fightingstyle = C.fightingstyle[data.fightingstyle];
}
// Fighting Mastery Items
else if ( itemData.type === "fightingmastery" ) {
// labels.fightingmastery = C.fightingmastery[data.fightingmastery];
}
// Lightsaber Form Items
else if ( itemData.type === "lightsaberform" ) {
// labels.lightsaberform = C.lightsaberform[data.lightsaberform];
}
// Equipment Items
@ -322,7 +323,7 @@ export default class Item5e extends Item {
user: game.user._id,
type: CONST.CHAT_MESSAGE_TYPES.OTHER,
content: html,
flavor: this.name,
flavor: this.data.data.chatFlavor || this.name,
speaker: {
actor: this.actor._id,
token: this.actor.token,
@ -367,7 +368,6 @@ export default class Item5e extends Item {
if ( !consume.type ) return true;
const actor = this.actor;
const typeLabel = CONFIG.SW5E.abilityConsumptionTypes[consume.type];
const amount = parseInt(consume.amount || 1);
// Only handle certain types for certain actions
if ( ((consume.type === "ammo") && !isAttack ) || ((consume.type !== "ammo") && !isCard) ) return true;
@ -380,6 +380,7 @@ export default class Item5e extends Item {
// Identify the consumed resource and it's quantity
let consumed = null;
let amount = parseInt(consume.amount || 1);
let quantity = 0;
switch ( consume.type ) {
case "attribute":
@ -393,7 +394,13 @@ export default class Item5e extends Item {
break;
case "charges":
consumed = actor.items.get(consume.target);
quantity = consumed ? consumed.data.data.uses.value : 0;
if ( !consumed ) break;
const uses = consumed.data.data.uses;
if ( uses.per && uses.max ) quantity = uses.value;
else if ( consumed.data.data.recharge?.value ) {
quantity = consumed.data.data.recharge.charged ? 1 : 0;
amount = 1;
}
break;
}
@ -418,7 +425,11 @@ export default class Item5e extends Item {
await consumed.update({"data.quantity": remaining});
break;
case "charges":
await consumed.update({"data.uses.value": remaining});
const uses = consumed.data.data.uses || {};
const recharge = consumed.data.data.recharge || {};
if ( uses.per && uses.max ) await consumed.update({"data.uses.value": remaining});
else if ( recharge.value ) await consumed.update({"data.recharge.charged": false});
break;
}
return true;
}
@ -436,7 +447,7 @@ export default class Item5e extends Item {
// Configure whether to consume a limited use or to place a template
const charge = this.data.data.recharge;
const uses = this.data.data.uses;
let usesCharges = !!uses.per && (uses.max > 0);
let usesCharges = !!uses.per && !!uses.max;
let placeTemplate = false;
let consume = charge.value || usesCharges;
@ -637,11 +648,9 @@ export default class Item5e extends Item {
}
// Attack Bonus
if ( itemData.attackBonus ) parts.push(itemData.attackBonus);
const actorBonus = actorData?.bonuses?.[itemData.actionType] || {};
if ( itemData.attackBonus || actorBonus.attack ) {
parts.push("@atk");
rollData["atk"] = [itemData.attackBonus, actorBonus.attack].filterJoin(" + ");
}
if ( actorBonus.attack ) parts.push(actorBonus.attack);
// Ammunition Bonus
delete this._ammo;
@ -652,16 +661,14 @@ export default class Item5e extends Item {
const q = ammo.data.data.quantity;
const consumeAmount = consume.amount ?? 0;
if ( q && (q - consumeAmount >= 0) ) {
this._ammo = ammo;
let ammoBonus = ammo.data.data.attackBonus;
if ( ammoBonus ) {
parts.push("@ammo");
rollData["ammo"] = ammoBonus;
title += ` [${ammo.name}]`;
this._ammo = ammo;
}
}
//}else{
// ui.notifications.error(game.i18n.format("SW5E.ConsumeWarningNoResource", {name: this.name, type: typeLabel}));
}
}
@ -671,6 +678,7 @@ export default class Item5e extends Item {
actor: this.actor,
data: rollData,
title: title,
flavor: title,
speaker: ChatMessage.getSpeaker({actor: this.actor}),
dialogOptions: {
width: 400,
@ -681,9 +689,11 @@ export default class Item5e extends Item {
}, options);
rollConfig.event = options.event;
// Expanded weapon critical threshold
// Expanded critical hit thresholds
if (( this.data.type === "weapon" ) && flags.weaponCriticalThreshold) {
rollConfig.critical = parseInt(flags.weaponCriticalThreshold);
} else if (( this.data.type === "power" ) && flags.powerCriticalThreshold) {
rollConfig.critical = parseInt(flags.powerCriticalThreshold);
}
// Elven Accuracy
@ -710,28 +720,41 @@ export default class Item5e extends Item {
/**
* Place a damage roll using an item (weapon, feat, power, or equipment)
* Rely upon the damageRoll logic for the core implementation
*
* Rely upon the damageRoll logic for the core implementation.
* @param {MouseEvent} [event] An event which triggered this roll, if any
* @param {number} [powerLevel] If the item is a power, override the level for damage scaling
* @param {boolean} [versatile] If the item is a weapon, roll damage using the versatile formula
* @param {object} [options] Additional options passed to the damageRoll function
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
*/
rollDamage({event, powerLevel=null, versatile=false}={}) {
rollDamage({event, powerLevel=null, versatile=false, options={}}={}) {
if ( !this.hasDamage ) throw new Error("You may not make a Damage Roll with this Item.");
const itemData = this.data.data;
const actorData = this.actor.data.data;
if ( !this.hasDamage ) {
throw new Error("You may not make a Damage Roll with this Item.");
}
const messageData = {"flags.sw5e.roll": {type: "damage", itemId: this.id }};
// Get roll data
const parts = itemData.damage.parts.map(d => d[0]);
const rollData = this.getRollData();
if ( powerLevel ) rollData.item.level = powerLevel;
// Get message labels
// Configure the damage roll
const title = `${this.name} - ${game.i18n.localize("SW5E.DamageRoll")}`;
let flavor = this.labels.damageTypes.length ? `${title} (${this.labels.damageTypes})` : title;
// Define Roll parts
const parts = itemData.damage.parts.map(d => d[0]);
const rollConfig = {
event: event,
parts: parts,
actor: this.actor,
data: rollData,
title: title,
flavor: this.labels.damageTypes.length ? `${title} (${this.labels.damageTypes})` : title,
speaker: ChatMessage.getSpeaker({actor: this.actor}),
dialogOptions: {
width: 400,
top: event ? event.clientY - 80 : null,
left: window.innerWidth - 710
},
messageData: messageData
};
// Adjust damage from versatile usage
if ( versatile && itemData.damage.versatile ) {
@ -751,37 +774,27 @@ export default class Item5e extends Item {
}
}
// Define Roll Data
// Add damage bonus formula
const actorBonus = getProperty(actorData, `bonuses.${itemData.actionType}`) || {};
if ( actorBonus.damage && parseInt(actorBonus.damage) !== 0 ) {
parts.push("@dmg");
rollData["dmg"] = actorBonus.damage;
if ( actorBonus.damage && (parseInt(actorBonus.damage) !== 0) ) {
parts.push(actorBonus.damage);
}
// Ammunition Damage
// Add ammunition damage
if ( this._ammo ) {
parts.push("@ammo");
rollData["ammo"] = this._ammo.data.data.damage.parts.map(p => p[0]).join("+");
flavor += ` [${this._ammo.name}]`;
rollConfig.flavor += ` [${this._ammo.name}]`;
delete this._ammo;
}
// Scale melee critical hit damage
if ( itemData.actionType === "mwak" ) {
rollConfig.criticalBonusDice = this.actor.getFlag("sw5e", "meleeCriticalDamageDice") ?? 0;
}
// Call the roll helper utility
return damageRoll({
event: event,
parts: parts,
actor: this.actor,
data: rollData,
title: title,
flavor: flavor,
speaker: ChatMessage.getSpeaker({actor: this.actor}),
dialogOptions: {
width: 400,
top: event ? event.clientY - 80 : null,
left: window.innerWidth - 710
},
messageData
});
return damageRoll(mergeObject(rollConfig, options));
}
/* -------------------------------------------- */
@ -793,22 +806,7 @@ export default class Item5e extends Item {
_scaleAtWillDamage(parts, scale, level, rollData) {
const add = Math.floor((level + 1) / 6);
if ( add === 0 ) return;
// FUTURE SOLUTION - 0.7.0 AND LATER
if (isNewerVersion(game.data.version, "0.6.9")) {
this._scaleDamage(parts, scale || parts.join(" + "), add, rollData)
}
// LEGACY SOLUTION - 0.6.x AND OLDER
// TODO: Deprecate the legacy solution one FVTT 0.7.x is RELEASE
else {
if ( scale && (scale !== parts[0]) ) {
parts[0] = parts[0] + " + " + scale.replace(new RegExp(Roll.diceRgx, "g"), (match, nd, d) => `${add}d${d}`);
} else {
parts[0] = parts[0].replace(new RegExp(Roll.diceRgx, "g"), (match, nd, d) => `${parseInt(nd)+add}d${d}`);
}
}
this._scaleDamage(parts, scale || parts.join(" + "), add, rollData);
}
/* -------------------------------------------- */
@ -826,22 +824,9 @@ export default class Item5e extends Item {
_scalePowerDamage(parts, baseLevel, powerLevel, formula, rollData) {
const upcastLevels = Math.max(powerLevel - baseLevel, 0);
if ( upcastLevels === 0 ) return parts;
// FUTURE SOLUTION - 0.7.0 AND LATER
if (isNewerVersion(game.data.version, "0.6.9")) {
this._scaleDamage(parts, formula, upcastLevels, rollData);
}
// LEGACY SOLUTION - 0.6.x AND OLDER
// TODO: Deprecate the legacy solution one FVTT 0.7.x is RELEASE
else {
const bonus = new Roll(formula);
bonus.alter(0, upcastLevels);
parts.push(bonus.formula);
}
return parts;
}
/* -------------------------------------------- */
/**
@ -899,7 +884,7 @@ export default class Item5e extends Item {
const roll = new Roll(rollData.item.formula, rollData).roll();
roll.toMessage({
speaker: ChatMessage.getSpeaker({actor: this.actor}),
flavor: this.data.data.chatFlavor || title,
flavor: title,
rollMode: game.settings.get("core", "rollMode"),
messageData: {"flags.sw5e.roll": {type: "other", itemId: this.id }}
});
@ -1021,7 +1006,7 @@ export default class Item5e extends Item {
template: "systems/sw5e/templates/chat/tool-roll-dialog.html",
title: title,
speaker: ChatMessage.getSpeaker({actor: this.actor}),
flavor: `${this.name} - ${game.i18n.localize("SW5E.ToolCheck")}`,
flavor: title,
dialogOptions: {
width: 400,
top: options.event ? options.event.clientY - 80 : null,
@ -1055,8 +1040,8 @@ export default class Item5e extends Item {
}
// Include a proficiency score
const prof = "proficient" in rollData.item ? (rollData.item.proficient || 0) : 1;
rollData["prof"] = Math.floor(prof * rollData.attributes.prof);
const prof = ("proficient" in rollData.item) ? (rollData.item.proficient || 0) : 1;
rollData["prof"] = Math.floor(prof * (rollData.attributes.prof || 0));
return rollData;
}

View file

@ -1,4 +1,5 @@
import TraitSelector from "../apps/trait-selector.js";
import {onManageActiveEffect, prepareActiveEffectCategories} from "../effects.js";
/**
* Override and extend the core ItemSheet implementation to handle specific item types
@ -7,8 +8,11 @@ import TraitSelector from "../apps/trait-selector.js";
export default class ItemSheet5e extends ItemSheet {
constructor(...args) {
super(...args);
// Expand the default size of the class sheet
if ( this.object.data.type === "class" ) {
this.options.width = 600;
this.options.width = this.position.width = 600;
this.options.height = this.position.height = 680;
}
}
@ -18,7 +22,7 @@ export default class ItemSheet5e extends ItemSheet {
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
width: 560,
height: "auto",
height: 400,
classes: ["sw5e", "sheet", "item"],
resizable: true,
scrollY: [".tab.details"],
@ -37,13 +41,13 @@ export default class ItemSheet5e extends ItemSheet {
/* -------------------------------------------- */
/** @override */
getData() {
const data = super.getData();
async getData(options) {
const data = super.getData(options);
data.labels = this.item.labels;
data.config = CONFIG.SW5E;
// Item Type, Status, and Details
data.itemType = data.item.type.titleCase();
data.itemType = game.i18n.localize(`ITEM.Type${data.item.type.titleCase()}`);
data.itemStatus = this._getItemStatus(data.item);
data.itemProperties = this._getItemProperties(data.item);
data.isPhysical = data.item.data.hasOwnProperty("quantity");
@ -60,6 +64,9 @@ export default class ItemSheet5e extends ItemSheet {
// Vehicles
data.isCrewed = data.item.data.activation?.type === 'crew';
data.isMountable = this._isItemMountable(data.item);
// Prepare Active Effects
data.effects = prepareActiveEffectCategories(this.entity.effects);
return data;
}
@ -109,6 +116,8 @@ export default class ItemSheet5e extends ItemSheet {
// Charges
else if ( consume.type === "charges" ) {
return actor.items.reduce((obj, i) => {
// Limited-use items
const uses = i.data.data.uses || {};
if ( uses.per && uses.max ) {
const label = uses.per === "charges" ?
@ -116,6 +125,10 @@ export default class ItemSheet5e extends ItemSheet {
` (${game.i18n.format("SW5E.AbilityUseConsumableLabel", {max: uses.max, per: uses.per})})`;
obj[i.id] = i.name + label;
}
// Recharging items
const recharge = i.data.data.recharge || {};
if ( recharge.value ) obj[i.id] = `${i.name} (${game.i18n.format("SW5E.Recharge")})`;
return obj;
}, {})
}
@ -177,30 +190,25 @@ export default class ItemSheet5e extends ItemSheet {
}
else if ( item.type === "species" ) {
//props.push(labels.species);
}
else if ( item.type === "archetype" ) {
//props.push(labels.archetype);
}
else if ( item.type === "background" ) {
//props.push(labels.background);
}
else if ( item.type === "classfeature" ) {
//props.push(labels.classfeature);
}
else if ( item.type === "fightingmastery" ) {
//props.push(labels.fightingmastery);
}
else if ( item.type === "fightingstyle" ) {
//props.push(labels.fightingstyle);
}
else if ( item.type === "lightsaberform" ) {
//props.push(labels.lightsaberform);
}
// Action type
@ -240,8 +248,8 @@ export default class ItemSheet5e extends ItemSheet {
/** @override */
setPosition(position={}) {
if ( !this._minimized ) {
position.height = this._tabs[0].active === "details" ? "auto" : this.options.height;
if ( !(this._minimized || position.height) ) {
position.height = (this._tabs[0].active === "details") ? "auto" : this.options.height;
}
return super.setPosition(position);
}
@ -251,17 +259,20 @@ export default class ItemSheet5e extends ItemSheet {
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
_getSubmitData(updateData={}) {
// TODO: This can be removed once 0.7.x is release channel
if ( !formData.data ) formData = expandObject(formData);
// Create the expanded update data object
const fd = new FormDataExtended(this.form, {editors: this.editors});
let data = fd.toObject();
if ( updateData ) data = mergeObject(data, updateData);
else data = expandObject(data);
// Handle Damage Array
const damage = formData.data?.damage;
// Handle Damage array
const damage = data.data?.damage;
if ( damage ) damage.parts = Object.values(damage?.parts || {}).map(d => [d[0] || "", d[1] || ""]);
// Update the Item
super._updateObject(event, formData);
// Return the flattened submission data
return flattenObject(data);
}
/* -------------------------------------------- */
@ -269,8 +280,14 @@ export default class ItemSheet5e extends ItemSheet {
/** @override */
activateListeners(html) {
super.activateListeners(html);
if ( this.isEditable ) {
html.find(".damage-control").click(this._onDamageControl.bind(this));
html.find('.trait-selector.class-skills').click(this._onConfigureClassSkills.bind(this));
html.find(".effect-control").click(ev => {
if ( this.item.isOwned ) return ui.notifications.warn("Managing Active Effects within an Owned Item is not currently supported and will be added in a subsequent update.")
onManageActiveEffect(ev, this.item)
});
}
}
/* -------------------------------------------- */
@ -328,4 +345,12 @@ export default class ItemSheet5e extends ItemSheet {
maximum: skills.number
}).render(true)
}
/* -------------------------------------------- */
/** @override */
async _onSubmit(...args) {
if ( this._tabs[0].active === "details" ) this.position.height = "auto";
await super._onSubmit(...args);
}
}

View file

@ -14,6 +14,7 @@ export const migrateWorld = async function() {
await a.update(updateData, {enforceTypes: false});
}
} catch(err) {
err.message = `Failed sw5e system migration for Actor ${a.name}: ${err.message}`;
console.error(err);
}
}
@ -27,6 +28,7 @@ export const migrateWorld = async function() {
await i.update(updateData, {enforceTypes: false});
}
} catch(err) {
err.message = `Failed sw5e system migration for Item ${i.name}: ${err.message}`;
console.error(err);
}
}
@ -40,15 +42,15 @@ export const migrateWorld = async function() {
await s.update(updateData, {enforceTypes: false});
}
} catch(err) {
err.message = `Failed sw5e system migration for Scene ${s.name}: ${err.message}`;
console.error(err);
}
}
// Migrate World Compendium Packs
const packs = game.packs.filter(p => {
return (p.metadata.package === "world") && ["Actor", "Item", "Scene"].includes(p.metadata.entity)
});
for ( let p of packs ) {
for ( let p of game.packs ) {
if ( p.metadata.package !== "world" ) continue;
if ( !["Actor", "Item", "Scene"].includes(p.metadata.entity) ) continue;
await migrateCompendium(p);
}
@ -68,27 +70,46 @@ export const migrateCompendium = async function(pack) {
const entity = pack.metadata.entity;
if ( !["Actor", "Item", "Scene"].includes(entity) ) return;
// Unlock the pack for editing
const wasLocked = pack.locked;
await pack.configure({locked: false});
// Begin by requesting server-side data model migration and get the migrated content
await pack.migrate();
const content = await pack.getContent();
// Iterate over compendium entries - applying fine-tuned migration functions
for ( let ent of content ) {
let updateData = {};
try {
let updateData = null;
if (entity === "Item") updateData = migrateItemData(ent.data);
else if (entity === "Actor") updateData = migrateActorData(ent.data);
else if ( entity === "Scene" ) updateData = migrateSceneData(ent.data);
if (!isObjectEmpty(updateData)) {
expandObject(updateData);
switch (entity) {
case "Actor":
updateData = migrateActorData(ent.data);
break;
case "Item":
updateData = migrateItemData(ent.data);
break;
case "Scene":
updateData = migrateSceneData(ent.data);
break;
}
if ( isObjectEmpty(updateData) ) continue;
// Save the entry, if data was changed
updateData["_id"] = ent._id;
await pack.updateEntity(updateData);
console.log(`Migrated ${entity} entity ${ent.name} in Compendium ${pack.collection}`);
}
} catch(err) {
// Handle migration failures
catch(err) {
err.message = `Failed sw5e system migration for entity ${ent.name} in pack ${pack.collection}: ${err.message}`;
console.error(err);
}
}
// Apply the original locked status for the pack
pack.configure({locked: wasLocked});
console.log(`Migrated all ${entity} entities from Compendium ${pack.collection}`);
};
@ -107,9 +128,7 @@ export const migrateActorData = function(actor) {
// Actor Data Updates
_migrateActorBonuses(actor, updateData);
// Remove deprecated fields
_migrateRemoveDeprecated(actor, updateData);
_migrateActorMovement(actor, updateData);
// Migrate Owned Items
if ( !actor.items ) return updateData;
@ -172,11 +191,6 @@ function cleanActorData(actorData) {
*/
export const migrateItemData = function(item) {
const updateData = {};
// Remove deprecated fields
_migrateRemoveDeprecated(item, updateData);
// Return the migrated update data
return updateData;
};
@ -225,31 +239,17 @@ function _migrateActorBonuses(actor, updateData) {
}
}
/* -------------------------------------------- */
/**
* A general migration to remove all fields from the data model which are flagged with a _deprecated tag
* Migrate the actor bonuses object
* @private
*/
const _migrateRemoveDeprecated = function(ent, updateData) {
const flat = flattenObject(ent.data);
// Identify objects to deprecate
const toDeprecate = Object.entries(flat).filter(e => e[0].endsWith("_deprecated") && (e[1] === true)).map(e => {
let parent = e[0].split(".");
parent.pop();
return parent.join(".");
});
// Remove them
for ( let k of toDeprecate ) {
let parts = k.split(".");
parts[parts.length-1] = "-=" + parts[parts.length-1];
updateData[`data.${parts.join(".")}`] = null;
}
};
function _migrateActorMovement(actor, updateData) {
if ( actor.data.attributes?.movement?.walk !== undefined ) return;
const s = (actor.data.attributes?.speed?.value || "").split(" ");
if ( s.length > 0 ) updateData["data.attributes.movement.walk"] = Number.isNumeric(s[0]) ? parseInt(s[0]) : null;
}
/* -------------------------------------------- */
@ -280,3 +280,24 @@ export async function purgeFlags(pack) {
}
await pack.configure({locked: true});
}
/* -------------------------------------------- */
/**
* Purge the data model of any inner objects which have been flagged as _deprecated.
* @param {object} data The data to clean
* @private
*/
export function removeDeprecatedObjects(data) {
for ( let [k, v] of Object.entries(data) ) {
if ( getType(v) === "Object" ) {
if (v._deprecated === true) {
console.log(`Deleting deprecated object key ${k}`);
delete data[k];
}
else removeDeprecatedObjects(v);
}
}
return data;
}

View file

@ -7,8 +7,8 @@ export const registerSystemSettings = function() {
name: "System Migration Version",
scope: "world",
config: false,
type: Number,
default: 0
type: String,
default: ""
});
/**
@ -82,18 +82,6 @@ export const registerSystemSettings = function() {
type: Boolean,
});
/**
* Option to automatically create Power Measured Template on roll
*/
game.settings.register("sw5e", "alwaysPlacePowerTemplate", {
name: "SETTINGS.5eAutoPowerTemplateN",
hint: "SETTINGS.5eAutoPowerTemplateL",
scope: "client",
config: true,
default: false,
type: Boolean
});
/**
* Option to automatically collapse Item Card descriptions
*/

View file

@ -4,17 +4,16 @@
* @return {Promise}
*/
export const preloadHandlebarsTemplates = async function() {
return loadTemplates([
// Define template paths to load
const templatePaths = [
// Shared Partials
"systems/sw5e/templates/actors/parts/active-effects.html",
// Actor Sheet Partials
"systems/sw5e/templates/actors/oldActor/parts/actor-traits.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-powerbook.html",
"systems/sw5e/templates/actors/oldActor/parts/actor-effects.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-biography.html",
"systems/sw5e/templates/actors/newActor/parts/swalt-core.html",
@ -30,8 +29,5 @@ export const preloadHandlebarsTemplates = async function() {
"systems/sw5e/templates/items/parts/item-activation.html",
"systems/sw5e/templates/items/parts/item-description.html",
"systems/sw5e/templates/items/parts/item-mountable.html"
];
// Load the template parts
return loadTemplates(templatePaths);
]);
};

257
sw5e.css
View file

@ -231,6 +231,12 @@
/* ----------------------------------------- */
/* Trait Lists */
/* ----------------------------------------- */
/* ----------------------------------------- */
/* Items Lists */
/* ----------------------------------------- */
/* ----------------------------------------- */
/* Active Effects */
/* ----------------------------------------- */
}
.sw5e.sheet .window-content {
overflow-y: hidden;
@ -404,6 +410,88 @@
margin: 0;
padding: 0;
}
.sw5e.sheet .items-list {
list-style: none;
margin: 0;
padding: 0;
overflow-y: auto;
scrollbar-width: thin;
color: #7a7971;
}
.sw5e.sheet .items-list .item-list {
list-style: none;
margin: 0;
padding: 0;
}
.sw5e.sheet .items-list .item {
align-items: center;
padding: 0 2px;
border-bottom: 1px solid #c9c7b8;
}
.sw5e.sheet .items-list .item:last-child {
border-bottom: none;
}
.sw5e.sheet .items-list .item .item-name {
color: #191813;
}
.sw5e.sheet .items-list .item .item-name .item-image {
flex: 0 0 30px;
height: 30px;
background-size: 30px;
border: none;
margin-right: 5px;
}
.sw5e.sheet .items-list .item .item-name h4 {
margin: 0;
white-space: nowrap;
overflow-x: hidden;
}
.sw5e.sheet .items-list .items-header {
height: 28px;
margin: 2px 0;
padding: 0;
align-items: center;
background: rgba(0, 0, 0, 0.05);
border: 2px groove #eeede0;
font-weight: bold;
}
.sw5e.sheet .items-list .items-header > * {
font-size: 12px;
text-align: center;
}
.sw5e.sheet .items-list .items-header .item-name {
padding-left: 5px;
font-family: 'Russo One';
font-size: 14px;
font-weight: 400;
}
.sw5e.sheet .items-list .item-name {
flex: 2;
margin: 0;
overflow: hidden;
font-size: 13px;
text-align: left;
align-items: center;
}
.sw5e.sheet .items-list .item-controls {
flex: 0 0 60px;
justify-content: space-between;
}
.sw5e.sheet .items-list .item-controls a {
font-size: 12px;
text-align: center;
}
.sw5e.sheet .effects .item .effect-source,
.sw5e.sheet .effects .item .effect-duration,
.sw5e.sheet .effects .item .effect-controls {
text-align: center;
border-left: 1px solid #c9c7b8;
border-right: 1px solid #c9c7b8;
font-size: 12px;
}
.sw5e.sheet .effects .item .effect-controls {
border: none;
}
/* ----------------------------------------- */
/* Trait Selector
/* ----------------------------------------- */
@ -447,9 +535,6 @@
/* Powerbook */
/* ----------------------------------------- */
/* ----------------------------------------- */
/* Active Effects */
/* ----------------------------------------- */
/* ----------------------------------------- */
/* TinyMCE */
/* ----------------------------------------- */
}
@ -515,6 +600,7 @@
font-weight: 400;
color: #4b4a44;
border-bottom: 1px solid #c9c7b8;
white-space: nowrap;
}
.sw5e.sheet.actor .tab.attributes {
overflow: hidden;
@ -559,6 +645,7 @@
font-family: "Signika", sans-serif;
font-size: 12px;
font-weight: 400;
white-space: nowrap;
}
.sw5e.sheet.actor .ability-scores {
flex: 0 0 100px;
@ -771,35 +858,10 @@
border-bottom: 2px groove #eeede0;
}
.sw5e.sheet.actor .inventory-list {
list-style: none;
margin: 0;
padding: 0 5px;
overflow-y: auto;
scrollbar-width: thin;
color: #7a7971;
}
.sw5e.sheet.actor .inventory-list .item {
line-height: 30px;
padding: 0 2px;
border-bottom: 1px solid #c9c7b8;
}
.sw5e.sheet.actor .inventory-list .item:last-child {
border-bottom: none;
}
.sw5e.sheet.actor .inventory-list .item .item-name {
cursor: pointer;
max-height: 30px;
overflow: hidden;
}
.sw5e.sheet.actor .inventory-list .item .item-name .item-image {
flex: 0 0 30px;
background-size: 30px;
margin-right: 5px;
}
.sw5e.sheet.actor .inventory-list .item .item-name h4 {
margin: 0;
white-space: nowrap;
overflow-x: hidden;
}
.sw5e.sheet.actor .inventory-list .item .item-name.rollable:hover .item-image {
background-image: url("../../icons/svg/d20-grey.svg") !important;
@ -821,36 +883,14 @@
flex: 0 0 80px;
text-align: right;
font-size: 11px;
color: #7a7971;
white-space: nowrap;
}
.sw5e.sheet.actor .inventory-list .inventory-header {
margin: 2px 0;
padding: 0;
align-items: center;
background: rgba(0, 0, 0, 0.05);
border: 2px groove #eeede0;
font-weight: bold;
line-height: 24px;
}
.sw5e.sheet.actor .inventory-list .inventory-header h3 {
margin: 0 -5px 0 0;
padding-left: 5px;
font-family: 'Russo One';
font-size: 20px;
font-weight: 400;
font-size: 16px;
}
.sw5e.sheet.actor .inventory-list .inventory-header .item-controls a.item-create {
flex: 0 0 100%;
}
.sw5e.sheet.actor .inventory-list .item-name {
color: #191813;
}
.sw5e.sheet.actor .inventory-list .item-detail {
flex: 0 0 70px;
font-size: 12px;
color: #7a7971;
text-align: center;
border-right: 1px solid #c9c7b8;
word-break: break-word;
@ -868,45 +908,15 @@
border-left: 1px solid #c9c7b8;
border-right: 1px solid #c9c7b8;
}
.sw5e.sheet.actor .inventory-list .item-list {
list-style: none;
margin: 0;
padding: 0;
}
.sw5e.sheet.actor .inventory-list .item-controls {
flex: 0 0 44px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
justify-content: flex-end;
}
.sw5e.sheet.actor .inventory-list .item-controls > * {
flex: 1;
}
.sw5e.sheet.actor .inventory-list .item-controls .flex1 {
flex: 1;
}
.sw5e.sheet.actor .inventory-list .item-controls .flex2 {
flex: 2;
}
.sw5e.sheet.actor .inventory-list .item-controls .flex3 {
flex: 3;
}
.sw5e.sheet.actor .inventory-list .item-controls .flex4 {
flex: 4;
}
.sw5e.sheet.actor .inventory-list .item-controls a {
flex: 0 0 22px;
font-size: 12px;
text-align: center;
color: #7a7971;
}
.sw5e.sheet.actor .inventory-list .item-summary {
flex: 0 0 100%;
font-size: 12px;
line-height: 16px;
padding: 0.25em 0.5em;
color: #191813;
border-top: 1px solid #c9c7b8;
}
.sw5e.sheet.actor .encumbrance {
@ -1034,42 +1044,25 @@
.sw5e.sheet.actor .powerbook-empty .item-controls {
flex: 1;
}
.sw5e.sheet.actor .effects .effect-name {
flex: 2;
align-items: center;
color: #191813;
}
.sw5e.sheet.actor .effects .effect-name h4 {
margin: 0;
}
.sw5e.sheet.actor .effects .effect-icon {
flex: 0 0 30px;
height: 30px;
margin-right: 5px;
border: none;
}
.sw5e.sheet.actor .effects .effect-source,
.sw5e.sheet.actor .effects .effect-duration {
text-align: center;
border-left: 1px solid #c9c7b8;
border-right: 1px solid #c9c7b8;
}
.sw5e.sheet.actor .effects .effect-controls {
flex: 0 0 60px;
text-align: right;
}
.sw5e.sheet.actor .effects .effect {
align-items: center;
border-bottom: 1px solid #c9c7b8;
}
.sw5e.sheet.actor .effects .effect:last-child {
border-bottom: none;
}
.sw5e.sheet.actor .editor {
padding: 0 8px;
}
#actor-flags .window-content {
overflow-y: hidden;
}
#actor-flags form {
height: 100%;
}
#actor-flags .form-body {
height: calc(100% - 40px);
padding-right: 8px;
margin-bottom: 4px;
overflow-y: auto;
}
.sw5e.sheet.item {
min-height: 420px;
min-height: 400px;
max-height: 95%;
min-width: 480px;
/* ----------------------------------------- */
/* Sheet Header */
/* ----------------------------------------- */
@ -1090,7 +1083,7 @@
border: 2px solid #000;
}
.sw5e.sheet.item .sheet-header .item-subtitle {
flex: 0 0 80px;
flex: 0 0 100px;
height: 60px;
margin: 0;
padding: 5px;
@ -1165,14 +1158,15 @@
.sw5e.sheet.item .details .form-group.input-select-select select {
flex: 1.5;
}
.sw5e.sheet.item .details .form-group.uses-per .form-fields {
flex-wrap: nowrap;
}
.sw5e.sheet.item .details .form-group.uses-per input {
flex: 1;
flex: 0 0 32px;
}
.sw5e.sheet.item .details .form-group.uses-per span {
flex: 0 0 16px;
}
.sw5e.sheet.item .details .form-group.uses-per select {
flex: 3;
margin: 0 4px 0 0;
}
.sw5e.sheet.item .details span.sep {
flex: 0 0 8px;
@ -1544,30 +1538,3 @@
max-width: 40px;
text-align: right;
}
input[type="number"] {
width: calc(100% - 2px);
min-width: 20px;
height: 26px;
background: rgba(0, 0, 0, 0.05);
padding: 1px 3px;
margin: 0;
color: #191813;
font-family: inherit;
font-size: inherit;
text-align: inherit;
line-height: inherit;
border: 1px solid #7a7971;
border-radius: 3px;
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
-moz-appearance: textfield;
}
input[type="number"]:focus {
box-shadow: 0 0 5px red;
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
}

51
sw5e.js
View file

@ -30,6 +30,7 @@ import ActorSheet5eNPCNew from "./module/actor/sheets/newSheet/npc.js";
import ItemSheet5e from "./module/item/sheet.js";
import ShortRestDialog from "./module/apps/short-rest.js";
import TraitSelector from "./module/apps/trait-selector.js";
import MovementConfig from "./module/apps/movement-config.js";
// Import Helpers
import * as chat from "./module/chat.js";
@ -56,7 +57,8 @@ Hooks.once("init", function() {
ActorSheet5eVehicle,
ItemSheet5e,
ShortRestDialog,
TraitSelector
TraitSelector,
MovementConfig
},
canvas: {
AbilityTemplate
@ -76,7 +78,7 @@ Hooks.once("init", function() {
CONFIG.SW5E = SW5E;
CONFIG.Actor.entityClass = Actor5e;
CONFIG.Item.entityClass = Item5e;
if ( CONFIG.time ) CONFIG.time.roundTime = 6; // TODO remove conditional after 0.7.x
CONFIG.time.roundTime = 6;
// Add DND5e namespace for module compatability
game.dnd5e = game.sw5e;
@ -139,18 +141,18 @@ Hooks.once("setup", function() {
// Localize CONFIG objects once up-front
const toLocalize = [
"abilities", "abilityAbbreviations", "alignments", "conditionTypes", "consumableTypes", "currencies",
"damageTypes", "damageResistanceTypes", "distanceUnits", "equipmentTypes", "healingTypes", "itemActionTypes",
"limitedUsePeriods", "senses", "skills", "powerComponents", "powerLevels", "powerPreparationModes", "powerSchools",
"powerScalingModes", "targetTypes", "timePeriods", "weaponProperties", "weaponTypes", "languages",
"polymorphSettings", "armorProficiencies", "weaponProficiencies", "toolProficiencies", "abilityActivationTypes",
"abilityConsumptionTypes", "actorSizes", "proficiencyLevels", "armorPropertiesTypes", "cover"
"abilities", "abilityAbbreviations", "abilityActivationTypes", "abilityConsumptionTypes", "actorSizes", "alignments",
"armorProficiencies", "armorPropertiesTypes", "conditionTypes", "consumableTypes", "cover", "currencies", "damageResistanceTypes",
"damageTypes", "distanceUnits", "equipmentTypes", "healingTypes", "itemActionTypes", "languages",
"limitedUsePeriods", "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
const noSort = [
"abilities", "alignments", "currencies", "distanceUnits", "itemActionTypes", "proficiencyLevels",
"limitedUsePeriods", "powerComponents", "powerLevels", "weaponTypes"
"abilities", "alignments", "currencies", "distanceUnits", "movementUnits", "itemActionTypes", "proficiencyLevels",
"limitedUsePeriods", "powerComponents", "powerLevels", "powerPreparationModes", "weaponTypes"
];
// Localize and sort CONFIG objects
@ -178,22 +180,23 @@ Hooks.once("setup", function() {
*/
Hooks.once("ready", function() {
// Determine whether a system migration is required and feasible
const currentVersion = game.settings.get("sw5e", "systemMigrationVersion");
const NEEDS_MIGRATION_VERSION = 0.84;
const COMPATIBLE_MIGRATION_VERSION = 0.80;
let needMigration = (currentVersion < NEEDS_MIGRATION_VERSION) || (currentVersion === null);
// Perform the migration
if ( needMigration && game.user.isGM ) {
if ( currentVersion && (currentVersion < COMPATIBLE_MIGRATION_VERSION) ) {
ui.notifications.error(`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.`, {permanent: true});
}
migrations.migrateWorld();
}
// 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));
// Determine whether a system migration is required and feasible
if ( !game.user.isGM ) return;
const currentVersion = game.settings.get("sw5e", "systemMigrationVersion");
const NEEDS_MIGRATION_VERSION = "1.1.0";
const COMPATIBLE_MIGRATION_VERSION = 0.80;
const needsMigration = currentVersion && isNewerVersion(NEEDS_MIGRATION_VERSION, currentVersion);
if ( !needsMigration ) return;
// Perform the migration
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.`;
ui.notifications.error(warning, {permanent: true});
}
migrations.migrateWorld();
});
/* -------------------------------------------- */

View file

@ -2,7 +2,7 @@
"name": "sw5e",
"title": "SW 5th Edition",
"description": "A comprehensive game system for running games of SW 5th Edition in the Foundry VTT environment.",
"version": 0.98,
"version": "1.1.1",
"author": "Dev Team",
"scripts": [],
"esmodules": ["sw5e.js"],
@ -135,8 +135,8 @@
"gridUnits": "ft",
"primaryTokenAttribute": "attributes.hp",
"secondaryTokenAttribute": null,
"minimumCoreVersion": "0.5.6",
"compatibleCoreVersion": "0.7.5",
"minimumCoreVersion": "0.7.6",
"compatibleCoreVersion": "0.7.6",
"url": "https://github.com/unrealkakeman89/sw5e",
"manifest": "https://raw.githubusercontent.com/unrealkakeman89/sw5e/master/system.json",
"download": "https://github.com/unrealkakeman89/sw5e/archive/master.zip"

View file

@ -76,10 +76,18 @@
},
"creature": {
"attributes": {
"movement": {
"burrow": 0,
"climb": 0,
"fly": 0,
"swim": 0,
"walk": 30,
"units": "ft",
"hover": false
},
"powercasting": "int",
"speed": {
"value": "30 ft",
"special": ""
"_deprecated": true
}
},
"details": {

View file

@ -82,25 +82,25 @@
</li>
<li class="attribute">
<h4 class="attribute-name box-title">{{ localize "SW5E.Speed" }}</h4>
<h4 class="attribute-name box-title">
{{ localize "SW5E.Speed" }}
</h4>
<div class="attribute-value">
<input name="data.attributes.speed.value" type="text"
value="{{data.attributes.speed.value}}" placeholder="0"/>
<span>{{movement.primary}}</span>
</div>
<footer class="attribute-footer">
<input type="text" class="speed" name="data.attributes.speed.special"
value="{{data.attributes.speed.special}}" placeholder="{{ localize 'SW5E.SpeedSpecial' }}"/>
<span>{{movement.special}}</span>
</footer>
</li>
<li class="attribute initiative">
<h4 class="attribute-name box-title">{{ localize "SW5E.Initiative" }}</h4>
<h4 class="attribute-name box-title rollable" data-action="rollInitiative">{{ localize "SW5E.Initiative" }}</h4>
<div class="attribute-value">
<span>{{numberFormat data.attributes.init.total decimals=0 sign=true}}</span>
</div>
<footer class="attribute-footer">
<span>{{ localize "SW5E.Modifier" }}</span>
<input name="data.attributes.init.value" type="number" placeholder="0"
<input name="data.attributes.init.value" type="text" data-dtype="Number" placeholder="0"
value="{{numberFormat data.attributes.init.value decimals=0 sign=true}}"/>
</footer>
</li>
@ -108,7 +108,7 @@
</section>
</header>
{{!-- NPC Sheet Navigation --}}
{{!-- Character Sheet Navigation --}}
<nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="attributes">{{ localize "SW5E.Attributes" }}</a>
<a class="item" data-tab="inventory">{{ localize "SW5E.Inventory" }}</a>
@ -118,7 +118,7 @@
<a class="item" data-tab="biography">{{ localize "SW5E.Biography" }}</a>
</nav>
{{!-- NPC Sheet Body --}}
{{!-- Character Sheet Body --}}
<section class="sheet-body">
<div class="tab attributes flexrow" data-group="primary" data-tab="attributes">
@ -183,7 +183,7 @@
{{!-- Counters --}}
<div class="counters">
<div class="counter flexrow death-saves">
<h4 class="death-save rollable">{{ localize "SW5E.DeathSave" }}</h4>
<h4 class="rollable" data-action="rollDeathSave">{{ localize "SW5E.DeathSave" }}</h4>
<div class="counter-value">
<i class="fas fa-check"></i>
<input name="data.attributes.death.success" type="number" placeholder="0"
@ -231,7 +231,7 @@
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/oldActor/parts/actor-effects.html"}}
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
{{!-- Biography Tab --}}

View file

@ -62,14 +62,14 @@
</li>
<li class="attribute">
<h4 class="attribute-name box-title">{{ localize "SW5E.Speed" }}</h4>
<h4 class="attribute-name box-title">
{{ localize "SW5E.Speed" }}
</h4>
<div class="attribute-value">
<input name="data.attributes.speed.value" type="text"
value="{{data.attributes.speed.value}}" placeholder="0"/>
<span>{{movement.primary}}</span>
</div>
<footer class="attribute-footer">
<input type="text" class="speed" name="data.attributes.speed.special"
value="{{data.attributes.speed.special}}" placeholder="{{ localize 'SW5E.SpeedSpecial' }}"/>
<span>{{movement.special}}</span>
</footer>
</li>
</ul>
@ -81,6 +81,7 @@
<a class="item active" data-tab="attributes">{{ localize "SW5E.Attributes" }}</a>
<a class="item" data-tab="features">{{ localize "SW5E.Features" }}</a>
<a class="item" data-tab="powerbook">{{ localize "SW5E.Powerbook" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
<a class="item" data-tab="biography">{{ localize "SW5E.Biography" }}</a>
</nav>
@ -163,6 +164,11 @@
{{> "systems/sw5e/templates/actors/oldActor/parts/actor-powerbook.html"}}
</div>
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
{{!-- Biography Tab --}}
<div class="tab biography flexcol" data-group="primary" data-tab="biography">
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable}}

View file

@ -1,28 +0,0 @@
<ol class="inventory-list">
{{#each effects as |section sid|}}
<li class="inventory-header flexrow">
<h3 class="effect-name flexrow">{{localize section.label}}</h3>
<div class="effect-source">Source</div>
<div class="effect-source">Duration</div>
<div class="effect-controls"></div>
</li>
<ol class="effects-list item-list">
{{#each section.effects as |effect|}}
<li class="effect flexrow" data-effect-id="{{effect.id}}">
<div class="effect-name flexrow">
<img class="effect-icon" src="{{effect.data.icon}}"/>
<h4>{{effect.data.label}}</h4>
</div>
<div class="effect-source">{{effect.sourceName}}</div>
<div class="effect-duration">{{effect.duration.label}}</div>
<div class="effect-controls">
<a class="effect-control" data-action="toggle"><i class="fas fa-circle-notch"></i></a>
<a class="effect-control" data-action="edit"><i class="fas fa-edit"></i></a>
<a class="effect-control" data-action="delete"><i class="fas fa-trash"></i></a>
</div>
</li>
{{/each}}
</ol>
{{/each}}
</ol>

View file

@ -8,9 +8,9 @@
</div>
{{/unless}}
<ol class="inventory-list">
<ol class="items-list inventory-list">
{{#each sections as |section sid|}}
<li class="inventory-header flexrow">
<li class="items-header flexrow">
<h3 class="item-name flexrow">{{localize section.label}}</h3>
{{#if section.hasActions}}
@ -25,7 +25,7 @@
{{/if}}
{{#if ../owner}}
<div class="item-controls">
<div class="item-controls flexrow">
<a class="item-control item-create" title="{{localize 'SW5E.FeatureAdd'}}" {{#each section.dataset as |v k|}}data-{{k}}="{{v}}"{{/each}}>
<i class="fas fa-plus"></i> {{localize "SW5E.Add"}}
</a>
@ -119,7 +119,7 @@
{{/if}}
{{#if ../../owner}}
<div class="item-controls">
<div class="item-controls flexrow">
{{#if section.crewable}}
<a class="item-control item-toggle {{item.toggleClass}}"
title="{{item.toggleTitle}}">

View file

@ -4,7 +4,6 @@
<ol class="currency flexrow">
<h3>
{{localize "SW5E.Currency"}}
<a class="currency-convert" title="Convert Currency"><i class="fas fa-coins"></i></a>
</h3>
{{#each data.currency as |v k|}}
<label class="denomination {{k}}">{{ lookup ../config.currencies k }}</label>
@ -23,9 +22,9 @@
{{/unless}}
</div>
<ol class="inventory-list">
<ol class="items-list inventory-list">
{{#each sections as |section sid|}}
<li class="inventory-header flexrow">
<li class="items-header flexrow">
<h3 class="item-name flexrow">{{localize section.label}}</h3>
{{#if section.columns}}
@ -42,7 +41,7 @@
{{/if}}
{{#if ../owner}}
<div class="item-controls">
<div class="item-controls flexrow">
<a class="item-control item-create" title='{{localize "SW5E.ItemCreate"}}'
{{#each section.dataset as |v k|}}data-{{k}}="{{v}}"{{/each}}>
<i class="fas fa-plus"></i> {{localize "SW5E.Add"}}
@ -107,7 +106,7 @@
{{/if}}
{{#if ../../owner}}
<div class="item-controls">
<div class="item-controls flexrow">
{{#unless @root.isVehicle}}
<a class="item-control item-toggle {{item.toggleClass}}" title='{{item.toggleTitle}}'><i class="fas fa-shield-alt"></i></a>
{{/unless}}

View file

@ -27,12 +27,10 @@
</ul>
</div>
<ol class="inventory-list">
<ol class="items-list inventory-list">
{{#each powerbook as |section|}}
<li class="item flexrow inventory-header powerbook-header">
<div class="item-name flexrow">
<h3>{{section.label}}</h3>
</div>
<li class="items-header powerbook-header flexrow">
<h3 class="item-name flexrow">{{section.label}}</h3>
<div class="power-slots">
{{#if section.usesSlots}}
@ -46,6 +44,7 @@
<i class="fas fa-edit"></i>
</a>
{{/if}}
</span>
{{ else }}
<span>{{{section.uses}}}</span>
<span class="sep"> / </span>
@ -57,7 +56,7 @@
<div class="power-action">{{localize "SW5E.PowerUsage"}}</div>
<div class="power-target">{{localize "SW5E.PowerTarget"}}</div>
<div class="item-controls">
<div class="item-controls flexrow">
{{#if section.canCreate}}
<a class="item-control item-create" title="{{localize 'SW5E.PowerCreate'}}" {{#each section.dataset as |v k|}}data-{{k}}="{{v}}"{{/each}}>
<i class="fas fa-plus"></i> {{localize "SW5E.Add"}}
@ -91,9 +90,8 @@
{{/if}}
</div>
{{#if ../../owner}}
<div class="item-controls">
<div class="item-controls flexrow">
{{#if section.canPrepare}}
<a class="item-control item-toggle {{item.toggleClass}}" title="{{item.toggleTitle}}"><i class="fas fa-sun"></i></a>
{{/if}}
@ -110,7 +108,7 @@
<li class="item flexrow"><p class="notes">{{localize "SW5E.FilterNoPowers"}}</p></li>
{{else}}
<li class="item flexrow inventory-header powerbook-header powerbook-empty">
<div class="item-controls">
<div class="item-controls flexrow">
<a class="item-control item-create" title="{{localize 'SW5E.PowerCreate'}}" data-type="power"
data-level="{{lvl}}"><i class="fas fa-plus"></i> {{localize "SW5E.PowerAdd"}}</a>
</div>

View file

@ -11,6 +11,11 @@
</div>
{{#unless isVehicle}}
<div class="form-group ">
<label>{{localize "SW5E.MovementConfig"}}</label>
<a class="configure-movement" title="{{localize 'SW5E.MovementConfig'}}"><i class="fas fa-cog"></i></a>
</div>
<div class="form-group {{#unless data.traits.senses}}inactive{{/unless}}">
<label>{{localize "SW5E.Senses"}}</label>
<input type="text" name="data.traits.senses" value="{{data.traits.senses}}" placeholder="{{ localize 'SW5E.None' }}"/>

View file

@ -58,8 +58,7 @@
<li class="attribute">
<h4 class="attribute-name box-title">{{localize 'SW5E.Speed'}}</h4>
<div class="attribute-value">
<input name="data.attributes.speed.value" type="text" placeholder="&mdash;"
value="{{data.attributes.speed.value}}">
<input name="data.attributes.speed" type="text" placeholder="&mdash;" value="{{data.attributes.speed}}"/>
</div>
</li>
</ul>
@ -139,16 +138,16 @@
</div>
</div>
</div>
{{> 'systems/sw5e/templates/actors/parts/actor-traits.html'}}
{{> 'systems/sw5e/templates/actors/oldActor/parts/actor-traits.html'}}
</section>
</div>
<div class="tab features flexcol" data-group="primary" data-tab="features">
{{> 'systems/sw5e/templates/actors/parts/actor-features.html' sections=features}}
{{> 'systems/sw5e/templates/actors/oldActor/parts/actor-features.html' sections=features}}
</div>
<div class="tab cargo flexcol" data-group="primary" data-tab="cargo">
{{> 'systems/sw5e/templates/actors/parts/actor-inventory.html' sections=cargo}}
{{> 'systems/sw5e/templates/actors/oldActor/parts/actor-inventory.html' sections=cargo}}
</div>
<div class="tab biography flexcol" data-group="primary" data-tab="biography">

View file

@ -0,0 +1,38 @@
<ol class="items-list effects-list">
{{#each effects as |section sid|}}
<li class="items-header flexrow" data-effect-type="{{section.type}}">
<h3 class="item-name effect-name flexrow">{{localize section.label}}</h3>
<div class="effect-source">Source</div>
<div class="effect-source">Duration</div>
<div class="item-controls effect-controls flexrow">
<a class="effect-control" data-action="create" title="{{localize 'SW5E.EffectCreate'}}">
<i class="fas fa-plus"></i> {{localize "SW5E.Add"}}
</a>
</div>
</li>
<ol class="item-list">
{{#each section.effects as |effect|}}
<li class="item effect flexrow" data-effect-id="{{effect.id}}">
<div class="item-name effect-name flexrow">
<img class="item-image" src="{{effect.data.icon}}"/>
<h4>{{effect.data.label}}</h4>
</div>
<div class="effect-source">{{effect.sourceName}}</div>
<div class="effect-duration">{{effect.duration.label}}</div>
<div class="item-controls effect-controls flexrow">
<a class="effect-control" data-action="toggle" title="{{localize 'SW5E.EffectToggle'}}">
<i class="fas fa-circle-notch"></i>
</a>
<a class="effect-control" data-action="edit" title="{{localize 'SW5E.EffectEdit'}}">
<i class="fas fa-edit"></i>
</a>
<a class="effect-control" data-action="delete" title="{{localize 'SW5E.EffectDelete'}}">
<i class="fas fa-trash"></i>
</a>
</div>
</li>
{{/each}}
</ol>
{{/each}}
</ol>

View file

@ -1,4 +1,5 @@
<form class="{{cssClass}}" autocomplete="off">
<section class="form-body">
<p class="notes">{{localize 'SW5E.FlagsInstructions'}}</p>
{{#each flags as |fs section|}}
@ -37,8 +38,11 @@
<input type="text" name="{{b.name}}" value="{{b.value}}"/>
</div>
{{/each}}
</section>
<footer class="form-footer">
<button type="submit" name="submit">
<i class="far fa-save"></i> {{localize 'SW5E.FlagsSave'}}
</button>
</footer>
</form>

View file

@ -1,34 +0,0 @@
<form id="cast-config-form">
<p>{{ localize "SW5E.SpellCastHint" }} <strong>{{item.name}}</strong> {{ localize "SW5E.cast" }}.</p>
{{#unless canCast}}
<p class="notification error">{{ localize "SW5E.SpellCastNoSlots" }}</p>
{{/unless}}
<div class="form-group">
<label>{{ localize "SW5E.SpellCastUpcast" }}</label>
<div class="form-fields">
<select name="level">
{{#select item.data.level}}
{{#each castLevels as |l|}}
<option value="{{l.level}}" {{#unless l.canCast}}disabled{{/unless}}>{{l.label}}</option>
{{/each}}
{{/select}}
</select>
</div>
</div>
<div class="form-group">
{{#if canUpcast}}
<label class="checkbox">{{ localize "SW5E.SpellCastConsume" }} <input type="checkbox" name="consume" {{checked canCast}}/></label>
{{/if}}
{{#if hasPlaceableTemplate}}
<div class="form-group">
<label class="checkbox">{{ localize "SW5E.PlaceTemplate" }}
<input type="checkbox" name="placeTemplate" checked/>
</label>
</div>
{{/if}}
</div>
</form>

View file

@ -0,0 +1,34 @@
<form autocomplete="off">
<p class="notes">{{localize "SW5E.MovementConfigHint"}}</p>
<div class="form-group">
<label>{{localize "SW5E.MovementWalk"}}</label>
<input name="data.attributes.movement.walk" type="number" step="0.1" value="{{movement.walk}}"/>
</div>
<div class="form-group">
<label>{{localize "SW5E.MovementBurrow"}}</label>
<input name="data.attributes.movement.burrow" type="number" step="0.1" value="{{movement.burrow}}"/>
</div>
<div class="form-group">
<label>{{localize "SW5E.MovementClimb"}}</label>
<input name="data.attributes.movement.climb" type="number" step="0.1" value="{{movement.climb}}"/>
</div>
<div class="form-group">
<label>{{localize "SW5E.MovementFly"}}</label>
<input name="data.attributes.movement.fly" type="number" step="0.1" value="{{movement.fly}}"/>
</div>
<div class="form-group">
<label>{{localize "SW5E.MovementSwim"}}</label>
<input name="data.attributes.movement.swim" type="number" step="0.1" value="{{movement.swim}}"/>
</div>
<div class="form-group">
<label>{{localize "SW5E.MovementUnits"}}</label>
<select name="data.attributes.movement.units">
{{selectOptions units selected=movement.units}}
</select>
</div>
<div class="form-group">
<label>{{localize "SW5E.MovementHover"}}</label>
<input name="data.attributes.movement.hover" type="checkbox" {{checked movement.hover}}/>
</div>
<button type="submit" name="submit" value="1"><i class="far fa-save"></i> {{ localize "Submit"}}</button>
</form>

View file

@ -15,5 +15,5 @@
<input type="text" name="custom" value="{{custom}}" data-dtype="String"/>
</div>
{{/if}}
<button type="submit" name="submit" value="1"><i class="far fa-save"></i> {{ localize "SW5E.Save"}}</button>
<button type="submit" name="submit" value="1"><i class="far fa-save"></i> {{ localize "SW5E.TraitSave"}}</button>
</form>

View file

@ -1,7 +1,7 @@
<div class="sw5e chat-card item-card" data-actor-id="{{actor._id}}" data-item-id="{{item._id}}" {{#if tokenId}}data-token-id="{{tokenId}}"{{/if}}>
<header class="card-header flexrow">
<img src="{{item.img}}" title="{{item.name}}" width="36" height="36"/>
<h3>{{item.name}}</h3>
<h3 class="item-name">{{item.name}}</h3>
</header>
<div class="card-content">{{{data.description.value}}}</div>

View file

@ -31,6 +31,7 @@
{{!-- Item Sheet Navigation --}}
<nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
<!-- <a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a> -->
</nav>
@ -41,5 +42,11 @@
<div class="tab description" data-group="primary" data-tab="description">
{{editor content=data.description.value target="data.description.value" button=true owner=owner editable=editable}}
</div>
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
</section>
</form>

View file

@ -24,6 +24,7 @@
{{!-- Item Sheet Navigation --}}
<nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
<!-- <a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a> -->
</nav>
@ -32,68 +33,42 @@
{{!-- Description Tab --}}
<div class="tab description" data-group="primary" data-tab="description">
<div class="background">{{editor content=data.flavorText.value target="data.flavorText.value" button=true editable=editable}}</div>
<div class="background"><p><strong>Skill Proficiencies:</strong> {{{data.skillProficiencies.value}}}</p></div>
<div class="background"><p><strong>Tool Proficiencies:</strong> {{{data.toolProficiencies.value}}}</p></div>
<div class="background"><p><strong>Languages:</strong> {{{data.languages.value}}}</p></div>
<div class="background"><p><strong>Equipment:</strong> {{{data.equipment.value}}}</p></div>
<div class="background"><h3>{{{data.flavorName.value}}}</h3></div>
<div class="background"><p>{{{data.flavorDescription.value}}}</p></div>
<div class="smalltable"><p>{{{data.flavorOptions.value}}}</p></div>
<div class="background"><h2>Feature: {{{data.featureName.value}}}</h2></div>
<div class="background"><p>{{{data.featureText.value}}}</p></div>
<h2>Background Feat</h2>
<p>As a further embodiment of the experience and training of your background, you can choose from the following feats:</p>
<div class="smalltable"><p>{{{data.featOptions.value}}}</p></div>
<h3>Suggested Characteristics</h3>
<div class="background"><p>{{{data.suggestedCharacteristics.value}}}</p></div>
<div class="medtable"><p>{{{data.personalityTraitOptions.value}}}</p></div><p>&nbsp;</p>
<div class="medtable"><p>{{{data.idealOptions.value}}}</p></div><p>&nbsp;</p>
<div class="medtable"><p>{{{data.flawOptions.value}}}</p></div><p>&nbsp;</p>
<div class="medtable"><p>{{{data.bondOptions.value}}}</p></div>
<div class="background">{{editor content=data.flavorText.value target="data.flavorText.value" button=true editable=editable}}</div>
<div class="background"><p><strong>Skill Proficiencies:</strong> {{{data.skillProficiencies.value}}}</p></div>
<div class="background"><p><strong>Tool Proficiencies:</strong> {{{data.toolProficiencies.value}}}</p></div>
<div class="background"><p><strong>Languages:</strong> {{{data.languages.value}}}</p></div>
<div class="background"><p><strong>Equipment:</strong> {{{data.equipment.value}}}</p></div>
<div class="background"><h3>{{{data.flavorName.value}}}</h3></div>
<div class="background"><p>{{{data.flavorDescription.value}}}</p></div>
<div class="smalltable"><p>{{{data.flavorOptions.value}}}</p></div>
<div class="background"><h2>Feature: {{{data.featureName.value}}}</h2></div>
<div class="background"><p>{{{data.featureText.value}}}</p></div>
<h2>Background Feat</h2>
<p>As a further embodiment of the experience and training of your background, you can choose from the following feats:</p>
<div class="smalltable"><p>{{{data.featOptions.value}}}</p></div>
<h3>Suggested Characteristics</h3>
<div class="background"><p>{{{data.suggestedCharacteristics.value}}}</p></div>
<div class="medtable"><p>{{{data.personalityTraitOptions.value}}}</p></div><p>&nbsp;</p>
<div class="medtable"><p>{{{data.idealOptions.value}}}</p></div><p>&nbsp;</p>
<div class="medtable"><p>{{{data.flawOptions.value}}}</p></div><p>&nbsp;</p>
<div class="medtable"><p>{{{data.bondOptions.value}}}</p></div>
<script>
let nullField = document.querySelectorAll('.background > div');
nullField.forEach(function(element) {
<script>
let nullField = document.querySelectorAll('.background > div');
nullField.forEach(function(element) {
if (element.value === null) {
element.previousElementSibling.style.display = 'none';
element.style.display = 'none';
}
});
</script>
});
</script>
</div>
<!-- {{> "systems/sw5e/templates/items/parts/item-description.html"}}
{{!-- Details Tab --}}
<div class="tab details" data-group="primary" data-tab="details">
<h3 class="form-header">{{ localize "SW5E.FeatureUsage" }}</h3>
{{!-- Item Activation Template --}}
{{> "systems/sw5e/templates/items/parts/item-activation.html"}}
{{!-- Recharge Requirement --}}
{{#if data.activation.type}}
<div class="form-group recharge">
<label>{{ localize "SW5E.FeatureActionRecharge" }}</label>
<div class="form-fields">
<span>{{ localize "SW5E.FeatureRechargeOn" }}</span>
<input type="text" name="data.recharge.value" value="{{data.recharge.value}}"
data-dtype="Number" placeholder="{{ localize 'SW5E.FeatureRechargeResult' }}"/>
<label class="checkbox">
{{ localize "SW5E.Charged" }}
<input type="checkbox" name="data.recharge.charged" {{checked data.recharge.charged}}/>
</label>
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
</div>
{{/if}}
<h3 class="form-header">{{ localize "SW5E.FeatureAttack" }}</h3>
{{!-- Item Action Template --}}
{{> "systems/sw5e/templates/items/parts/item-action.html"}}
</div> -->
</section>
</form>

View file

@ -1,151 +0,0 @@
<form class="{{cssClass}} flexcol" autocomplete="off">
{{!-- Item Sheet Header --}}
<header class="sheet-header flexrow">
<img class="profile" src="{{item.img}}" title="{{item.name}}" data-edit="img"/>
<div class="header-details flexrow">
<h1 class="charname">
<input name="name" type="text" value="{{item.name}}" placeholder="{{ localize 'SW5E.CastName' }}"/>
</h1>
<div class="item-subtitle">
<h4 class="item-type">{{itemType}}</h4>
<span class="item-status">{{itemStatus}}</span>
</div>
<ul class="summary">
<li>
{{labels.level}}
</li>
<li>
{{labels.school}}
</li>
<li>
<input type="text" name="data.source" value="{{data.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
</li>
</ul>
</div>
</header>
{{!-- Item Sheet Navigation --}}
<nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a>
<a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a>
</nav>
{{!-- Item Sheet Body --}}
<section class="sheet-body">
{{!-- Description Tab --}}
{{> "systems/sw5e/templates/items/parts/item-description.html"}}
{{!-- Details Tab --}}
<div class="tab details" data-group="primary" data-tab="details">
<h3 class="form-header">{{ localize "SW5E.CastDetails" }}</h3>
{{!-- Cast Level --}}
<div class="form-group">
<label>{{ localize "SW5E.CastLevel" }}</label>
<select name="data.level" data-dtype="Number">
{{#select data.level}}
{{#each config.castLevels as |name lvl|}}
<option value="{{lvl}}">{{name}}</option>
{{/each}}
{{/select}}
</select>
</div>
{{!-- Cast School --}}
<div class="form-group">
<label>{{ localize "SW5E.CastSchool" }}</label>
<select name="data.school">
{{#select data.school}}
{{#each config.castSchools as |name sch|}}
<option value="{{sch}}">{{name}}</option>
{{/each}}
{{/select}}
</select>
</div>
{{!-- Cast Components --}}
<div class="cast-components form-group stacked">
<label>{{ localize "SW5E.CastComponents" }}</label>
<label class="checkbox">
<input type="checkbox" name="data.components.vocal" {{checked data.components.vocal}}/> {{ localize "SW5E.ComponentVerbal" }}
</label>
<label class="checkbox">
<input type="checkbox" name="data.components.somatic" {{checked data.components.somatic}}/> {{ localize "SW5E.ComponentSomatic" }}
</label>
<label class="checkbox">
<input type="checkbox" name="data.components.material" {{checked data.components.material}}/> {{ localize "SW5E.ComponentMaterial" }}
</label>
<label class="checkbox">
<input type="checkbox" name="data.components.concentration" {{checked data.components.concentration}}/> {{ localize "SW5E.Concentration" }}
</label>
<label class="checkbox">
<input type="checkbox" name="data.components.ritual" {{checked data.components.ritual}}/> {{ localize "SW5E.Ritual" }}
</label>
</div>
{{!-- Material Components --}}
<div class="form-group stacked">
<label>{{ localize "SW5E.CastMaterials" }}</label>
<input class="materials" type="text" name="data.materials.value" value="{{data.materials.value}}"/>
{{#if data.materials.value}}
<div class="cast-materials flexrow">
<label>{{ localize "SW5E.Supply" }}</label>
<input type="text" name="data.materials.supply" value="{{data.materials.supply}}" data-dtype="Number" Placeholder="0"/>
<label>{{ localize "SW5E.CostGP" }}</label>
<input type="text" name="data.materials.cost" value="{{data.materials.cost}}" data-dtype="Number" Placeholder="-"/>
<label>{{ localize "SW5E.Consumed" }}</label>
<input type="checkbox" name="data.materials.consumed" {{checked data.materials.consumed}}/>
</div>
{{/if}}
</div>
{{!-- Preparation Mode --}}
<div class="form-group input-select">
<label>{{ localize "SW5E.CastPreparationMode" }}</label>
<div class="form-fields">
<label class="checkbox prepared">
{{ localize "SW5E.CastPrepared" }} <input type="checkbox" name="data.preparation.prepared" {{checked data.preparation.prepared}}/>
</label>
<select name="data.preparation.mode">
{{#select data.preparation.mode}}
<option value=""></option>
{{#each config.castPreparationModes as |name key|}}
<option value="{{key}}">{{name}}</option>
{{/each}}
{{/select}}
</select>
</div>
</div>
<h3 class="form-header">{{ localize "SW5E.CastingHeader" }}</h3>
{{!-- Item Activation Template --}}
{{> "systems/sw5e/templates/items/parts/item-activation.html"}}
<h3 class="form-header">{{ localize "SW5E.CastEffects" }}</h3>
{{!-- Item Action Template --}}
{{> "systems/sw5e/templates/items/parts/item-action.html"}}
{{!-- Cast Level Scaling --}}
<div class="form-group">
<label>{{ localize "SW5E.LevelScaling" }}</label>
<div class="form-fields">
<select name="data.scaling.mode">
{{#select data.scaling.mode}}
{{#each config.castScalingModes as |name key|}}
<option value="{{key}}">{{name}}</option>
{{/each}}
{{/select}}
</select>
<input type="text" name="data.scaling.formula" value="{{data.scaling.formula}}" placeholder="{{ localize 'SW5E.ScalingFormula' }}"/>
</div>
</div>
</div>
</section>
</form>

View file

@ -32,6 +32,7 @@
<nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a>
<a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
</nav>
{{!-- Item Sheet Body --}}
@ -69,5 +70,11 @@
{{!-- Item Action Template --}}
{{> "systems/sw5e/templates/items/parts/item-action.html"}}
</div>
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
</section>
</form>

View file

@ -32,6 +32,7 @@
<nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a>
<a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
</nav>
{{!-- Item Sheet Body --}}
@ -86,5 +87,10 @@
{{!-- Item Action Template --}}
{{> "systems/sw5e/templates/items/parts/item-action.html"}}
</div>
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
</section>
</form>

View file

@ -32,6 +32,7 @@
<nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a>
<a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
</nav>
{{!-- Item Sheet Body --}}
@ -142,5 +143,10 @@
{{!-- Item Action Template --}}
{{> "systems/sw5e/templates/items/parts/item-action.html"}}
</div>
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
</section>
</form>

View file

@ -32,6 +32,7 @@
<nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a>
<a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
</nav>
{{!-- Item Sheet Body --}}
@ -69,5 +70,11 @@
{{!-- Item Action Template --}}
{{> "systems/sw5e/templates/items/parts/item-action.html"}}
</div>
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
</section>
</form>

View file

@ -24,6 +24,7 @@
{{!-- Item Sheet Navigation --}}
<nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
<!-- <a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a> -->
</nav>
@ -36,36 +37,11 @@
{{editor content=data.description.value target="data.description.value" button=true editable=editable}}
</div>
<!-- {{> "systems/sw5e/templates/items/parts/item-description.html"}}
{{!-- Details Tab --}}
<div class="tab details" data-group="primary" data-tab="details">
<h3 class="form-header">{{ localize "SW5E.FeatureUsage" }}</h3>
{{!-- Item Activation Template --}}
{{> "systems/sw5e/templates/items/parts/item-activation.html"}}
{{!-- Recharge Requirement --}}
{{#if data.activation.type}}
<div class="form-group recharge">
<label>{{ localize "SW5E.FeatureActionRecharge" }}</label>
<div class="form-fields">
<span>{{ localize "SW5E.FeatureRechargeOn" }}</span>
<input type="text" name="data.recharge.value" value="{{data.recharge.value}}"
data-dtype="Number" placeholder="{{ localize 'SW5E.FeatureRechargeResult' }}"/>
<label class="checkbox">
{{ localize "SW5E.Charged" }}
<input type="checkbox" name="data.recharge.charged" {{checked data.recharge.charged}}/>
</label>
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
</div>
{{/if}}
<h3 class="form-header">{{ localize "SW5E.FeatureAttack" }}</h3>
{{!-- Item Action Template --}}
{{> "systems/sw5e/templates/items/parts/item-action.html"}}
</div> -->
</section>
</form>

View file

@ -24,6 +24,7 @@
{{!-- Item Sheet Navigation --}}
<nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
<!-- <a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a> -->
</nav>
@ -36,36 +37,11 @@
{{editor content=data.description.value target="data.description.value" button=true editable=editable}}
</div>
<!-- {{> "systems/sw5e/templates/items/parts/item-description.html"}}
{{!-- Details Tab --}}
<div class="tab details" data-group="primary" data-tab="details">
<h3 class="form-header">{{ localize "SW5E.FeatureUsage" }}</h3>
{{!-- Item Activation Template --}}
{{> "systems/sw5e/templates/items/parts/item-activation.html"}}
{{!-- Recharge Requirement --}}
{{#if data.activation.type}}
<div class="form-group recharge">
<label>{{ localize "SW5E.FeatureActionRecharge" }}</label>
<div class="form-fields">
<span>{{ localize "SW5E.FeatureRechargeOn" }}</span>
<input type="text" name="data.recharge.value" value="{{data.recharge.value}}"
data-dtype="Number" placeholder="{{ localize 'SW5E.FeatureRechargeResult' }}"/>
<label class="checkbox">
{{ localize "SW5E.Charged" }}
<input type="checkbox" name="data.recharge.charged" {{checked data.recharge.charged}}/>
</label>
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
</div>
{{/if}}
<h3 class="form-header">{{ localize "SW5E.FeatureAttack" }}</h3>
{{!-- Item Action Template --}}
{{> "systems/sw5e/templates/items/parts/item-action.html"}}
</div> -->
</section>
</form>

View file

@ -24,6 +24,7 @@
{{!-- Item Sheet Navigation --}}
<nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
<!-- <a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a> -->
</nav>
@ -36,36 +37,11 @@
{{editor content=data.description.value target="data.description.value" button=true editable=editable}}
</div>
<!-- {{> "systems/sw5e/templates/items/parts/item-description.html"}}
{{!-- Details Tab --}}
<div class="tab details" data-group="primary" data-tab="details">
<h3 class="form-header">{{ localize "SW5E.FeatureUsage" }}</h3>
{{!-- Item Activation Template --}}
{{> "systems/sw5e/templates/items/parts/item-activation.html"}}
{{!-- Recharge Requirement --}}
{{#if data.activation.type}}
<div class="form-group recharge">
<label>{{ localize "SW5E.FeatureActionRecharge" }}</label>
<div class="form-fields">
<span>{{ localize "SW5E.FeatureRechargeOn" }}</span>
<input type="text" name="data.recharge.value" value="{{data.recharge.value}}"
data-dtype="Number" placeholder="{{ localize 'SW5E.FeatureRechargeResult' }}"/>
<label class="checkbox">
{{ localize "SW5E.Charged" }}
<input type="checkbox" name="data.recharge.charged" {{checked data.recharge.charged}}/>
</label>
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
</div>
{{/if}}
<h3 class="form-header">{{ localize "SW5E.FeatureAttack" }}</h3>
{{!-- Item Action Template --}}
{{> "systems/sw5e/templates/items/parts/item-action.html"}}
</div> -->
</section>
</form>

View file

@ -25,8 +25,26 @@
</div>
</header>
{{!-- Item Sheet Navigation --}}
<nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
<!-- <a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a> -->
</nav>
{{!-- Item Sheet Body --}}
<section class="sheet-body">
{{!-- Description Tab --}}
<div class="tab description" data-group="primary" data-tab="description">
{{> "systems/sw5e/templates/items/parts/item-description.html"}}
</div>
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
</section>
</form>

View file

@ -112,8 +112,9 @@
<label>{{ localize "SW5E.LimitedUses" }}</label>
<div class="form-fields">
<input type="text" name="data.uses.value" value="{{data.uses.value}}" data-dtype="Number"/>
<span class="sep"> {{ localize "SW5E.of" }} </span>
<span class="sep">{{ localize "SW5E.of" }}</span>
<input type="text" name="data.uses.max" value="{{data.uses.max}}" data-dtype="Number"/>
<span class="sep">{{ localize "SW5E.per" }}</span>
<select name="data.uses.per">
{{#select data.uses.per}}
<option value=""></option>

View file

@ -32,6 +32,7 @@
<nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a>
<a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
</nav>
{{!-- Item Sheet Body --}}
@ -68,6 +69,30 @@
</select>
</div>
{{!-- Power Components --}}
<div class="power-components form-group stacked">
<label>{{ localize "SW5E.PowerComponents" }}</label>
<label class="checkbox">
<input type="checkbox" name="data.components.concentration" {{checked data.components.concentration}}/> {{ localize "SW5E.Concentration" }}
</label>
</div>
{{!-- Material Components --}}
<div class="form-group stacked">
<label>{{ localize "SW5E.PowerMaterials" }}</label>
<input class="materials" type="text" name="data.materials.value" value="{{data.materials.value}}"/>
{{#if data.materials.value}}
<div class="power-materials flexrow">
<label>{{ localize "SW5E.Supply" }}</label>
<input type="text" name="data.materials.supply" value="{{data.materials.supply}}" data-dtype="Number" Placeholder="0"/>
<label>{{ localize "SW5E.CostGP" }}</label>
<input type="text" name="data.materials.cost" value="{{data.materials.cost}}" data-dtype="Number" Placeholder="-"/>
<label>{{ localize "SW5E.Consumed" }}</label>
<input type="checkbox" name="data.materials.consumed" {{checked data.materials.consumed}}/>
</div>
{{/if}}
</div>
{{!-- Preparation Mode --}}
<div class="form-group input-select">
<label>{{ localize "SW5E.PowerPreparationMode" }}</label>
@ -76,24 +101,10 @@
{{ localize "SW5E.PowerPrepared" }} <input type="checkbox" name="data.preparation.prepared" {{checked data.preparation.prepared}}/>
</label>
<select name="data.preparation.mode">
{{#select data.preparation.mode}}
<option value=""></option>
{{#each config.powerPreparationModes as |name key|}}
<option value="{{key}}">{{name}}</option>
{{/each}}
{{/select}}
{{ selectOptions config.powerPreparationModes selected=data.preparation.mode }}
</select>
</div>
</div>
{{!-- Concentration Mode --}}
<div class="form-group input-select">
<label>{{ localize "SW5E.PowerConcentrationMode" }}</label>
<div class="form-fields">
<label class="checkbox">
<input type="checkbox" name="data.components.concentration" {{checked data.components.concentration}}/> {{ localize "SW5E.Concentrated" }}
</label>
</div>
</div>
<h3 class="form-header">{{ localize "SW5E.PowerCastingHeader" }}</h3>
@ -120,5 +131,10 @@
</div>
</div>
</div>
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
</section>
</form>

View file

@ -26,6 +26,7 @@
<nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">Description</a>
<a class="item" data-tab="species-traits">Species Traits</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
</nav>
{{!-- Item Sheet Body --}}
@ -111,7 +112,10 @@
</span>
</div>
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
</section>
</form>

View file

@ -32,6 +32,7 @@
<nav class="sheet-navigation tabs" data-group="primary">
<a class="item active" data-tab="description">{{ localize "SW5E.Description" }}</a>
<a class="item" data-tab="details">{{ localize "SW5E.Details" }}</a>
<a class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</a>
</nav>
{{!-- Item Sheet Body --}}
@ -84,7 +85,6 @@
<label class="checkbox">
<input type="checkbox" name="data.properties.{{prop}}" {{checked (lookup ../data.properties prop)}}/> {{ name }}
</label>
<label class="text"> <input type="text" maxlength="2" name="wpNum"/></label>
{{/each}}
</div>
@ -110,5 +110,10 @@
{{!-- Item Action Template --}}
{{> "systems/sw5e/templates/items/parts/item-action.html"}}
</div>
{{!-- Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
</div>
</section>
</form>